diff --git a/.cargo/config.toml b/.cargo/config.toml index 5a4d9f339b8..00caf4aa916 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,4 +4,4 @@ rustflags = ["-Cforce-unwind-tables=y"] [target.'cfg(target_arch = "x86_64")'] -rustflags = ["-Ctarget-feature=+sse4.1,+sse4.2", "-Cforce-unwind-tables=y"] +rustflags = ["-Ctarget-feature=+sse2,+ssse3,+sse4.1,+sse4.2,+sha", "-Cforce-unwind-tables=y"] diff --git a/.config/nextest.toml b/.config/nextest.toml index 942db78f988..05578830b75 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,5 +1,5 @@ [profile.default] -slow-timeout = { period = "60s", terminate-after = 2, grace-period = "0s" } +slow-timeout = { period = "60s", terminate-after = 3, grace-period = "0s" } [[profile.default.overrides]] filter = 'test(test_full_estimator)' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1c950ed94e..f3098dd09d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -478,3 +478,14 @@ jobs: files: py-upgradability.json fail_ci_if_error: true flags: pytests,upgradability,linux + + windows_public_libraries_check: + name: "Windows check for building public libraries" + runs-on: "windows-latest" + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: taiki-e/install-action@9b5b983efc779f85e5e5d11539f005e85ccb27ff + with: + tool: just + - run: just check_build_public_libraries diff --git a/.github/workflows/near_crates_publish.yml b/.github/workflows/near_crates_publish.yml index 95f0b048c1f..68075740d1f 100644 --- a/.github/workflows/near_crates_publish.yml +++ b/.github/workflows/near_crates_publish.yml @@ -1,6 +1,8 @@ name: Near Crates Publish on: + release: + types: [released] workflow_dispatch: inputs: branch: @@ -19,12 +21,31 @@ jobs: steps: - name: Checkout near/nearcore's ${{ github.event.inputs.branch }} branch + if: ${{ github.event_name == 'workflow_dispatch'}} uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event.inputs.branch }} + + - name: Checkout nearcore repository + if: ${{ github.event_name != 'workflow_dispatch'}} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up git user uses: fregante/setup-git-user@v2 + + - name: Check if version is already published + run: | + PACKAGE_NAME="near-primitives" + VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.metadata.workspaces.version') + PUBLISHED=$(curl -s https://crates.io/api/v1/crates/$PACKAGE_NAME/versions | jq -r '.versions[] | select(.num=="'"$VERSION"'") | .num') + if [ "$PUBLISHED" == "$VERSION" ]; then + echo "Version $VERSION of $PACKAGE_NAME is already published." + exit 1 + fi + - name: Publish near-workspaces on crates.io env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} @@ -40,5 +61,3 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git push --no-follow-tags https://github.com/near/nearcore.git tag 'crates-*' - - diff --git a/.github/workflows/neard_custom_release.yml b/.github/workflows/neard_custom_release.yml index 2022732309e..afd48c79bc4 100644 --- a/.github/workflows/neard_custom_release.yml +++ b/.github/workflows/neard_custom_release.yml @@ -3,6 +3,10 @@ name: Neard binary image - custom build on: # Run when a new release or rc is created + push: + branches: + - statelessnet_master + - statelessnet_latest workflow_dispatch: inputs: release: @@ -42,9 +46,19 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + + - name: Set vars for workflow + if: ${{ github.event_name == 'push'}} + run: | + echo "RELEASE_NAME=statelessnet-release" >> $GITHUB_ENV + + - name: Set vars for workflow + if: ${{ github.event_name == 'workflow_dispatch'}} + run: | + echo "RELEASE_NAME=${{ github.event.inputs.release }}" >> $GITHUB_ENV - name: Neard binary build and upload to S3 - run: ./scripts/binary_release.sh ${{ github.event.inputs.release }} + run: ./scripts/binary_release.sh ${{ env.RELEASE_NAME }} - name: Update latest version metadata in S3 run: | @@ -54,7 +68,7 @@ jobs: if [ -z "$BRANCH" ]; then BRANCH=$(git branch -r --contains=${{ github.ref_name }} | head -n1 | cut -c3- | cut -d / -f 2) fi - aws s3 cp --acl public-read latest s3://build.nearprotocol.com/nearcore/$(uname)/${BRANCH}/${{ github.event.inputs.release }}/latest + aws s3 cp --acl public-read latest s3://build.nearprotocol.com/nearcore/$(uname)/${BRANCH}/${{ env.RELEASE_NAME }}/latest docker-release: name: "Build and publish nearcore Docker image" @@ -98,4 +112,4 @@ jobs: then docker tag nearcore nearprotocol/nearcore:latest docker push nearprotocol/nearcore:latest - fi \ No newline at end of file + fi diff --git a/.github/workflows/neard_release.yml b/.github/workflows/neard_release.yml index 3afefcf7d54..9647fd3bac6 100644 --- a/.github/workflows/neard_release.yml +++ b/.github/workflows/neard_release.yml @@ -62,6 +62,23 @@ jobs: fi aws s3 cp --acl public-read latest s3://build.nearprotocol.com/nearcore/$(uname)/${BRANCH}/latest + - name: Trigger packer image creation workflow + if: github.event_name != 'workflow_dispatch' && github.event_name == 'release' + run: | + SHORT_SHA=$(git rev-parse --short HEAD) + COMMIT=$(git rev-parse HEAD) + BRANCH=$(git branch --show-current) + # in case of Release triggered run, branch is empty + if [ -z "$BRANCH" ]; then + BRANCH=$(git branch -r --contains=${{ github.ref_name }} | head -n1 | cut -c3- | cut -d / -f 2) + fi + + curl -L -X POST -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.PAGODAPLATFORM_GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/PagodaPlatform/pkr-node/dispatches \ + -d '{"event_type":"packer-build","client_payload":{"image-name":"near-node-${BRANCH}-${SHORT_SHA}","neard-binary-s3-uri":"s3://build.nearprotocol.com/nearcore/Linux/${BRANCH}/${COMMIT}/neard"}}' + docker-release: name: "Build and publish nearcore Docker image" runs-on: "ubuntu-20.04-16core" diff --git a/.gitignore b/.gitignore index 739aaf308d7..4b625cc7ae2 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ rusty-tags.vi costs-*.txt names-to-stats.txt data_dump_*.bin +.venv diff --git a/CHANGELOG.md b/CHANGELOG.md index 97a3f82d467..20588ac599a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,13 @@ ## [unreleased] ### Protocol Changes +* Congestion Control [NEP-0539](https://github.com/near/NEPs/pull/539) +* Stateless Validation [NEP-0509](https://github.com/near/NEPs/pull/509) ### Non-protocol Changes +* Enforce rate limits to received network messages [#11617](https://github.com/near/nearcore/issues/11617). Rate limits are configured by default, but they can be overridden through the experimental configuration option `received_messages_rate_limits`. + ## 1.40.0 ### Protocol Changes diff --git a/Cargo.lock b/Cargo.lock index a76c055410d..4598c4f3703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,124 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint 0.4.3", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.103", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.3", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.103", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint 0.4.3", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.103", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -557,7 +675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0137a412059ef8c93654805c6639229cd2e21ecd9bdabe2be2a912f7bb819b5" dependencies = [ "base64 0.21.0", - "borsh 1.0.0", + "borsh 1.2.0", "bs58 0.5.1", "hex", "primitive-types 0.12.2", @@ -801,13 +919,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.9.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "crypto-mac", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.7", ] [[package]] @@ -853,6 +969,18 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "blst" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bolero" version = "0.10.0" @@ -949,11 +1077,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6cb63579996213e822f6d828b0a47e1d23b1e8708f52d18a6b1af5670dd207" +checksum = "bf617fabf5cdbdc92f774bfe5062d870f228b80056d41180797abf48bed4056e" dependencies = [ - "borsh-derive 1.0.0", + "borsh-derive 1.2.1", "cfg_aliases", ] @@ -972,12 +1100,12 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b4db62e0515621636e47f425d78a40bdea94c2d23713428fb12194cf5459a4" +checksum = "478b41ff04256c5c8330f3dfdaaae2a5cc976a8e75088bafa4625b0d0208de8c" dependencies = [ "once_cell", - "proc-macro-crate 1.3.1", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", "syn 2.0.32", @@ -1359,7 +1487,7 @@ name = "cold-store-tool" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "clap", "near-chain-configs", "near-epoch-manager", @@ -1794,16 +1922,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.6", + "digest 0.10.7", "fiat-crypto", - "platforms", "rand_core 0.6.4", "rustc_version 0.4.0", "subtle", @@ -1919,6 +2046,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.103", +] + [[package]] name = "derive-enum-from-into" version = "0.1.1" @@ -1991,9 +2129,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.2", "crypto-common", @@ -2652,10 +2790,9 @@ dependencies = [ name = "genesis-populate" version = "0.0.0" dependencies = [ - "borsh 1.0.0", + "borsh 1.2.0", "clap", "indicatif", - "near-async", "near-chain", "near-chain-configs", "near-crypto", @@ -2663,8 +2800,10 @@ dependencies = [ "near-primitives", "near-store", "near-test-contracts", + "near-time", "near-vm-runner", "nearcore", + "node-runtime", "tempfile", ] @@ -2873,7 +3012,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3146,7 +3285,7 @@ dependencies = [ "assert_matches", "aurora-engine-transactions", "aurora-engine-types", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "chrono", "clap", @@ -3190,6 +3329,7 @@ dependencies = [ "primitive-types 0.10.1", "rand", "reed-solomon-erasure", + "regex", "rlp", "serde", "serde_json", @@ -3582,7 +3722,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b48670c893079d3c2ed79114e3644b7004df1c361a4e0ad52e2e6940d07c3d" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3723,7 +3863,6 @@ dependencies = [ "clap", "futures", "near-actix-test-utils", - "near-async", "near-chain", "near-chain-configs", "near-chunks", @@ -3738,6 +3877,7 @@ dependencies = [ "near-primitives", "near-store", "near-telemetry", + "near-time", "nearcore", "pin-project", "rand", @@ -3779,7 +3919,7 @@ version = "1.0.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d10d45a9c49c3e975c362cf4d1dc1d7b72a716b30394bea56ee2a8fb225f50b7" dependencies = [ - "borsh 1.0.0", + "borsh 1.2.0", "serde", ] @@ -3797,13 +3937,13 @@ name = "near-amend-genesis" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "clap", - "near-async", "near-chain-configs", "near-crypto", "near-primitives", "near-primitives-core", + "near-time", "num-rational 0.3.2", "serde", "serde_json", @@ -3822,6 +3962,7 @@ dependencies = [ "near-async-derive", "near-o11y", "near-performance-metrics", + "near-time", "once_cell", "serde", "serde_json", @@ -3846,7 +3987,7 @@ name = "near-cache" version = "0.0.0" dependencies = [ "bencher", - "lru 0.7.8", + "lru 0.12.3", "rand", ] @@ -3856,7 +3997,7 @@ version = "0.0.0" dependencies = [ "actix", "assert_matches", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "chrono", "crossbeam-channel", @@ -3865,7 +4006,7 @@ dependencies = [ "insta", "itertools", "itoa", - "lru 0.7.8", + "lru 0.12.3", "near-async", "near-cache", "near-chain-configs", @@ -3907,12 +4048,12 @@ dependencies = [ "bytesize", "chrono", "derive_more", - "near-async", "near-config-utils", "near-crypto", "near-o11y", "near-parameters", "near-primitives", + "near-time", "num-rational 0.3.2", "once_cell", "serde", @@ -3927,9 +4068,9 @@ dependencies = [ name = "near-chain-primitives" version = "0.0.0" dependencies = [ - "near-async", "near-crypto", "near-primitives", + "near-time", "thiserror", "time", "tracing", @@ -3941,13 +4082,13 @@ version = "0.0.0" dependencies = [ "actix", "assert_matches", - "borsh 1.0.0", + "borsh 1.2.0", "chrono", "derive-enum-from-into", "derive_more", "futures", "itertools", - "lru 0.7.8", + "lru 0.12.3", "near-async", "near-chain", "near-chain-configs", @@ -3986,14 +4127,14 @@ dependencies = [ "anyhow", "assert_matches", "async-trait", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "chrono", "cloud-storage", "derive_more", "futures", "itertools", - "lru 0.7.8", + "lru 0.12.3", "near-actix-test-utils", "near-async", "near-cache", @@ -4042,12 +4183,12 @@ version = "0.0.0" dependencies = [ "actix", "chrono", - "near-async", "near-chain-configs", "near-chain-primitives", "near-chunks-primitives", "near-crypto", "near-primitives", + "near-time", "serde", "serde_json", "strum", @@ -4073,7 +4214,7 @@ version = "0.0.0" dependencies = [ "blake2", "bolero", - "borsh 1.0.0", + "borsh 1.2.0", "bs58 0.4.0", "curve25519-dalek", "derive_more", @@ -4099,7 +4240,7 @@ name = "near-database-tool" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "clap", "indicatif", @@ -4123,10 +4264,11 @@ name = "near-dyn-configs" version = "0.0.0" dependencies = [ "anyhow", - "near-async", "near-chain-configs", + "near-crypto", "near-o11y", "near-primitives", + "near-time", "once_cell", "prometheus", "serde", @@ -4140,7 +4282,7 @@ dependencies = [ name = "near-epoch-manager" version = "0.0.0" dependencies = [ - "borsh 1.0.0", + "borsh 1.2.0", "chrono", "itertools", "near-cache", @@ -4180,7 +4322,7 @@ name = "near-flat-storage" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "clap", "near-chain", "near-chain-configs", @@ -4323,11 +4465,11 @@ dependencies = [ "arbitrary", "awc", "libfuzzer-sys", - "near-async", "near-jsonrpc", "near-jsonrpc-primitives", "near-jsonrpc-tests", "near-primitives", + "near-time", "once_cell", "serde", "serde_json", @@ -4356,7 +4498,7 @@ version = "0.0.0" dependencies = [ "actix", "awc", - "borsh 1.0.0", + "borsh 1.2.0", "futures", "near-actix-test-utils", "near-async", @@ -4369,6 +4511,7 @@ dependencies = [ "near-o11y", "near-primitives", "near-store", + "near-time", "once_cell", "serde", "serde_json", @@ -4391,7 +4534,7 @@ dependencies = [ "actix", "anyhow", "async-trait", - "borsh 1.0.0", + "borsh 1.2.0", "bs58 0.4.0", "clap", "ed25519-dalek", @@ -4437,19 +4580,21 @@ dependencies = [ "assert_matches", "async-trait", "bolero", - "borsh 1.0.0", + "borsh 1.2.0", "bytes", "bytesize", "chrono", "criterion", "crossbeam-channel", "derive_more", + "enum-map", "futures", "futures-util", "im", "itertools", - "lru 0.7.8", + "lru 0.12.3", "near-async", + "near-chain-configs", "near-crypto", "near-fmt", "near-o11y", @@ -4470,6 +4615,7 @@ dependencies = [ "reed-solomon-erasure", "rlimit", "serde", + "serde_json", "sha2 0.10.6", "smart-default", "strum", @@ -4520,7 +4666,7 @@ name = "near-parameters" version = "0.0.0" dependencies = [ "assert_matches", - "borsh 1.0.0", + "borsh 1.2.0", "clap", "enum-map", "insta", @@ -4566,11 +4712,11 @@ dependencies = [ "anyhow", "chrono", "clap", - "near-async", "near-jsonrpc", "near-network", "near-o11y", "near-primitives", + "near-time", "once_cell", "prometheus", "tokio", @@ -4581,7 +4727,7 @@ dependencies = [ name = "near-pool" version = "0.0.0" dependencies = [ - "borsh 1.0.0", + "borsh 1.2.0", "near-crypto", "near-o11y", "near-primitives", @@ -4598,7 +4744,7 @@ dependencies = [ "base64 0.21.0", "bencher", "bolero", - "borsh 1.0.0", + "borsh 1.2.0", "bytes", "bytesize", "cfg-if 1.0.0", @@ -4610,24 +4756,25 @@ dependencies = [ "hex", "insta", "itertools", - "near-async", "near-crypto", "near-fmt", "near-parameters", + "near-primitives", "near-primitives-core", "near-rpc-error-macro", "near-stdx", - "near-vm-runner", + "near-time", "num-rational 0.3.2", "once_cell", + "ordered-float", "primitive-types 0.10.1", "rand", "rand_chacha", "reed-solomon-erasure", + "regex", "serde", "serde_json", "serde_with", - "serde_yaml", "sha3", "smart-default", "strum", @@ -4642,7 +4789,7 @@ version = "0.0.0" dependencies = [ "arbitrary", "base64 0.21.0", - "borsh 1.0.0", + "borsh 1.2.0", "bs58 0.4.0", "derive_more", "enum-map", @@ -4672,7 +4819,6 @@ dependencies = [ "insta", "near-account-id", "near-actix-test-utils", - "near-async", "near-chain-configs", "near-client", "near-client-primitives", @@ -4681,6 +4827,7 @@ dependencies = [ "near-o11y", "near-parameters", "near-primitives", + "near-time", "node-runtime", "paperclip", "serde", @@ -4718,12 +4865,12 @@ dependencies = [ "anyhow", "chrono", "clap", - "near-async", "near-jsonrpc", "near-network", "near-o11y", "near-ping", "near-primitives", + "near-time", "once_cell", "sha2 0.10.6", "time", @@ -4738,7 +4885,7 @@ dependencies = [ "actix", "actix-web", "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "clap", "cloud-storage", "near-client", @@ -4767,7 +4914,7 @@ dependencies = [ "anyhow", "assert_matches", "bencher", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "crossbeam", "derive-where", @@ -4777,8 +4924,7 @@ dependencies = [ "insta", "itertools", "itoa", - "lru 0.7.8", - "near-async", + "lru 0.12.3", "near-chain", "near-chain-configs", "near-chunks", @@ -4788,6 +4934,7 @@ dependencies = [ "near-parameters", "near-primitives", "near-stdx", + "near-time", "near-vm-runner", "num_cpus", "once_cell", @@ -4836,6 +4983,17 @@ dependencies = [ "wat", ] +[[package]] +name = "near-time" +version = "0.0.0" +dependencies = [ + "once_cell", + "serde", + "serde_json", + "time", + "tokio", +] + [[package]] name = "near-undo-block" version = "0.0.0" @@ -4929,12 +5087,19 @@ version = "0.0.0" dependencies = [ "anyhow", "arbitrary", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", "assert_matches", "base64 0.21.0", + "blst", "bolero", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "cov-mark", + "csv", "ed25519-dalek", "enum-map", "expect-test", @@ -5099,7 +5264,7 @@ dependencies = [ "anyhow", "awc", "bencher", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "chrono", "cloud-storage", @@ -5234,7 +5399,7 @@ name = "node-runtime" version = "0.0.0" dependencies = [ "assert_matches", - "borsh 1.0.0", + "borsh 1.2.0", "enum-map", "hex", "near-chain-configs", @@ -5595,7 +5760,10 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" dependencies = [ + "borsh 1.2.0", "num-traits", + "rand", + "serde", ] [[package]] @@ -5918,12 +6086,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" -[[package]] -name = "platforms" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" - [[package]] name = "powerfmt" version = "0.2.0" @@ -6217,6 +6379,7 @@ dependencies = [ "libc", "rand_chacha", "rand_core 0.6.4", + "serde", ] [[package]] @@ -6245,6 +6408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.9", + "serde", ] [[package]] @@ -6495,7 +6659,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1facec54cb5e0dc08553501fa740091086d0259ad0067e0d4103448e4cb22ed3" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -6569,7 +6733,7 @@ name = "runtime-params-estimator" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "bs58 0.4.0", "bytesize", "cfg-if 1.0.0", @@ -7030,11 +7194,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.16" +version = "0.9.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" dependencies = [ - "indexmap 1.9.2", + "indexmap 2.0.0", "itoa", "ryu", "serde", @@ -7048,7 +7212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" dependencies = [ "lazy_static", - "parking_lot 0.10.2", + "parking_lot 0.11.2", "serial_test_derive", ] @@ -7071,7 +7235,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -7101,7 +7265,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -7110,7 +7274,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] @@ -7255,15 +7419,15 @@ dependencies = [ name = "speedy_sync" version = "0.0.0" dependencies = [ - "borsh 1.0.0", + "borsh 1.2.0", "clap", - "near-async", "near-chain", "near-chain-configs", "near-chain-primitives", "near-epoch-manager", "near-primitives", "near-store", + "near-time", "nearcore", "serde", "serde_json", @@ -7299,14 +7463,13 @@ version = "0.0.0" dependencies = [ "actix", "anyhow", - "borsh 1.0.0", + "borsh 1.2.0", "bytesize", "chrono", "clap", "cloud-storage", "insta", "itertools", - "near-async", "near-chain", "near-chain-configs", "near-client", @@ -7318,6 +7481,7 @@ dependencies = [ "near-primitives-core", "near-store", "near-test-contracts", + "near-time", "nearcore", "node-runtime", "once_cell", @@ -7589,6 +7753,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tikv-jemalloc-sys" version = "0.5.2+5.3.0-patched" @@ -9104,6 +9277,20 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] [[package]] name = "zeropool-bn" diff --git a/Cargo.toml b/Cargo.toml index 4888b68098d..072e082eed8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ version = "0.0.0" # managed by cargo-workspaces, see below authors = ["Near Inc "] edition = "2021" -rust-version = "1.78.0" +rust-version = "1.79.0" repository = "https://github.com/near/nearcore" license = "MIT OR Apache-2.0" @@ -12,6 +12,8 @@ license = "MIT OR Apache-2.0" # Most crates are not stable on purpose, as maintaining API compatibility is a # significant developer time expense. Please think thoroughly before adding # anything to the list of stable crates. +# Only bump 0.x.* to 0.(x+1).0 on any nearcore release as nearcore does not guarantee +# semver compatibility. i.e. api can change without a protocol upgrade. version = "0.20.1" exclude = ["neard"] @@ -45,6 +47,7 @@ members = [ "core/primitives", "core/primitives-core", "core/store", + "core/time", "genesis-tools/genesis-csv-to-json", "genesis-tools/genesis-populate", "genesis-tools/keypair-generator", @@ -94,9 +97,7 @@ members = [ "utils/near-cache", "utils/stdx", ] -exclude = [ - "tracing", -] +exclude = ["tracing", "benchmarks"] [workspace.lints.clippy] all = { level = "allow", priority = -100 } @@ -122,6 +123,11 @@ actix-web = "4.1" anyhow = "1.0.62" arbitrary = { version = "1.2.3", features = ["derive"] } arc-swap = "1.5" +ark-bls12-381 = "0.4.0" +ark-ec = "0.4.0" +ark-ff = "0.4.0" +ark-serialize = "0.4.0" +ark-std = "0.4.0" assert_matches = "1.5.0" async-trait = "0.1.58" aurora-engine-transactions = "1.1" @@ -131,7 +137,8 @@ backtrace = "0.3" base64 = "0.21" bencher = "0.1.5" bitflags = "1.2" -blake2 = "0.9.1" +blake2 = { version = "0.10.6", features = ["reset"] } +blst = { version = "0.3.11", features = ["portable"] } bn = { package = "zeropool-bn", version = "0.5.11", default-features = false } # TODO: remove this override when https://github.com/camshaft/bolero/issues/196 is fixed upstream # Currently the changes here are: https://github.com/camshaft/bolero/compare/master...Ekleog-NEAR:bolero:reduce-list-tests-run @@ -146,22 +153,22 @@ cov-mark = "2.0.0-pre.1" cargo_metadata = "0.14.1" cc = "1.0" cfg-if = "1.0" -chrono = { version = "0.4.19", features = ["serde"] } +chrono = { version = "0.4", default-features = false, features = [ + "clock", + "alloc", + "serde", +] } clap = { version = "4.2.0", features = ["derive", "env", "string"] } cloud-storage = "0.11.1" cpu-time = "1.0" -criterion = { version = "0.5.1", default_features = false, features = [ +criterion = { version = "0.5.1", default-features = false, features = [ "html_reports", "cargo_bench_support", ] } crossbeam = "0.8" crossbeam-channel = "0.5.8" csv = "1.2.1" -curve25519-dalek = { version = "4.1.1", default-features = false, features = [ - "alloc", - "precomputed-tables", - "rand_core", -] } +curve25519-dalek = { version = "4.1.3", default-features = false } derive-enum-from-into = "0.1.1" derive_more = "0.99.9" derive-where = "1.2.7" @@ -169,10 +176,7 @@ dirs = "4" dynasm = "2.0" dynasmrt = "2.0" easy-ext = "0.2" -ed25519-dalek = { version = "2.1.0", default-features = false, features = [ - "hazmat", - "rand_core", -] } +ed25519-dalek = { version = "2.1.0", default-features = false } enum-map = "2.1.0" enumset = "1.0" ethabi = "18" @@ -200,7 +204,7 @@ lazy_static = "1.4" libc = "0.2.81" libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } log = "0.4" -lru = "0.7.2" +lru = "0.12.3" memoffset = "0.8" more-asserts = "0.2" near-account-id = { version = "1.0.0-alpha.4", features = [ @@ -224,7 +228,7 @@ near-client-primitives = { path = "chain/client-primitives" } near-cold-store-tool = { path = "tools/cold-store", package = "cold-store-tool" } near-config-utils = { path = "utils/config" } nearcore = { path = "nearcore" } -near-crypto = { path = "core/crypto" } +near-crypto = { path = "core/crypto", default-features = false } near-dyn-configs = { path = "core/dyn-configs" } near-epoch-manager = { path = "chain/epoch-manager" } near-epoch-sync-tool = { path = "tools/epoch-sync" } @@ -260,6 +264,9 @@ near-state-viewer = { path = "tools/state-viewer", package = "state-viewer" } near-store = { path = "core/store" } near-telemetry = { path = "chain/telemetry" } near-test-contracts = { path = "runtime/near-test-contracts" } +near-time = { path = "core/time", default-features = false, features = [ + "serde", +] } near-undo-block = { path = "tools/undo-block" } near-vm-test-api = { path = "runtime/near-vm/test-api" } near-vm-compiler = { path = "runtime/near-vm/compiler" } @@ -285,6 +292,7 @@ opentelemetry = { version = "0.22.0", features = ["trace"] } opentelemetry_sdk = { version = "0.22.0", features = ["rt-tokio"] } opentelemetry-otlp = "0.15.0" opentelemetry-semantic-conventions = "0.14.0" +ordered-float = { version = "4.2.0", features = ["serde", "borsh"] } paperclip = { version = "0.8.0", features = ["actix4"] } parity-wasm = { version = "0.42", default-features = false } parity-wasm_41 = { package = "parity-wasm", version = "0.41" } @@ -327,7 +335,7 @@ rusqlite = { version = "0.29.0", features = ["bundled", "chrono", "functions"] } rustc-demangle = "0.1" rust-s3 = { version = "0.32.3", features = ["blocking"] } rustix = "0.38" -secp256k1 = { version = "0.27.0", features = ["recovery", "rand-std"] } +secp256k1 = { version = "0.27.0", default-features = false } semver = "1.0.4" serde = { version = "1.0.136", features = ["alloc", "derive", "rc"] } serde_ignored = "0.1" @@ -352,15 +360,8 @@ testlib = { path = "test-utils/testlib" } test-log = { version = "0.2", default-features = false, features = ["trace"] } thiserror = "1.0.30" tikv-jemallocator = "0.5.0" -time = { version = "0.3.9", features = ["parsing", "serde"] } -tokio = { version = "1.28", features = [ - "fs", - "macros", - "net", - "rt-multi-thread", - "sync", - "time", -] } +time = { version = "0.3.9", default-features = false } +tokio = { version = "1.28", default-features = false } tokio-stream = { version = "0.1.2", features = ["net"] } tokio-util = { version = "0.7.1", features = ["codec", "io"] } toml = "0.5.8" diff --git a/Justfile b/Justfile index f198c06761d..10b9d02a9d9 100644 --- a/Justfile +++ b/Justfile @@ -8,6 +8,7 @@ with_macos_excludes := if os() == "macos" { } nightly_flags := "--features nightly,test_features" statelessnet_flags := "--features statelessnet_protocol" +public_libraries := "-p near-primitives -p near-crypto -p near-jsonrpc-primitives -p near-chain-configs -p near-primitives-core" export RUST_BACKTRACE := env("RUST_BACKTRACE", "short") ci_hack_nextest_profile := if env("CI_HACKS", "0") == "1" { "--profile ci" } else { "" } @@ -73,6 +74,11 @@ nextest-integration TYPE *FLAGS: nextest-integration TYPE *FLAGS: @echo "Nextest integration tests are currently disabled on macos!" +[windows] +nextest-integration TYPE *FLAGS: + @echo "Nextest integration tests are currently disabled on windows!" + + doctests: cargo test --doc @@ -87,10 +93,10 @@ check-cargo-fmt: cargo fmt -- --check # check clippy lints -check-cargo-clippy: +check-cargo-clippy *FLAGS: CARGO_TARGET_DIR="target/clippy" \ RUSTFLAGS="-D warnings" \ - cargo clippy --all-features --all-targets --locked + cargo clippy --all-features --all-targets --locked {{ FLAGS }} # check cargo deny lints check-cargo-deny: @@ -165,3 +171,6 @@ update-rpc-errors-schema: build-rpc-errors-schema # check chain/jsonrpc/res/rpc_errors_schema.json check-rpc-errors-schema: build-rpc-errors-schema diff target/rpc_errors_schema.json chain/jsonrpc/res/rpc_errors_schema.json + +check_build_public_libraries: + cargo check {{public_libraries}} diff --git a/Makefile b/Makefile index 87fb2ea4b58..63c562d13f3 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,6 @@ docker-nearcore-nightly: release: neard-release - cargo build -p store-validator --release - cargo build -p genesis-populate --release $(MAKE) sandbox-release neard: neard-release diff --git a/benchmarks/continous/db/Justfile b/benchmarks/continous/db/Justfile new file mode 100644 index 00000000000..e74967748d7 --- /dev/null +++ b/benchmarks/continous/db/Justfile @@ -0,0 +1,36 @@ +sql_dialect := "postgres" + +# The following parameters are defined by the `crt-benchmark` Cloud SQL instance in the +# `nearone-crt` project on GCP. They can be viewed and in some cases changed via: +# +# - GCP website: https://console.cloud.google.com/sql/instances/crt-benchmarks/overview?project=nearone-crt +# - `gcloud` CLI +db_host := "34.90.190.128" +db_name := "benchmarks" +db_port := "5432" + +# Check for SQL errors. +lint_sql path=".": + sqlfluff lint {{path}} --dialect {{sql_dialect}} + +# Automatically fix SQL errors, overwriting input files. +# If there are fixable errors, the command becomes interactive. +fix_sql path=".": + sqlfluff fix {{path}} --dialect {{sql_dialect}} + +# Connect to Cloud SQL with psql. +# +# Expects the password to be stored locally in your `~/.pgpass` file. +# For a successful connection you need a `.pgpass` with correct file permissions +# and a password entry matching your connection parameters, see +# https://www.postgresql.org/docs/15/libpq-pgpass.html +# +# Passwords of users can be found in N1's 1password. +psql username="grafana_reader" db=db_name pgpassfile="~/.pgpass": + PGSSLMODE="require" \ + PGPASSFILE={{pgpassfile}} \ + psql \ + --host={{db_host}} \ + --port={{db_port}} \ + --username={{username}} \ + --dbname={{db}} diff --git a/benchmarks/continous/db/README.md b/benchmarks/continous/db/README.md new file mode 100644 index 00000000000..1ae539f7ee0 --- /dev/null +++ b/benchmarks/continous/db/README.md @@ -0,0 +1,44 @@ +# DB for data related to continuous benchmarks + +This document describes the setup and administration of the database which stores data related to continuous benchmarks. Each benchmark, for instance ft transfer throughput, has its own table storing data like measured throughput and metadata related to the execution of the benchmark. + +## Data store selection + +The data collected for continuous benchmarks shall be displayed in Grafana dashboards to visualize how `nearcore` performance metrics evolve. Data is pushed after benchmark execution and therefore stored in a SQL database. In particular, we use a PostgreSQL database since there is prior knowledge about adding them to Grafana as a data source. + +## Cloud SQL set up and Grafana integration + +We set up the Cloud SQL instance [`crt-benchmark`](https://console.cloud.google.com/sql/instances/crt-benchmarks/overview?project=nearone-crt) in the `nearone-crt` project. Within that instance data is stored in the `benchmarks` table. + +### Network + +In Grafana the database is available as data source `crt_benchmarks`. Since the database does not contain sensitive data, we open the instance for connections from any IPv4 and delegate authentication to PostgreSQL: + +``` +gcloud sql instances patch crt-benchmarks \ + --authorized-networks=0.0.0.0/0 +``` + +This simplifies remote connections. + +### Role + +A role with read-only permissions is created for Grafana: + +```sql +create role grafana_reader login password 'store_it_in_1password'; +grant connect on database benchmarks to grafana_reader; + +-- Execute this statement when connected to the benchmarks db. +grant pg_read_all_data to grafana_reader; +``` + +The `grafana_reader` may use up to 20 connections. To verify that the PostgreSQL instance allows a sufficient number of connections you can run: + +```sql +select setting from pg_settings where name = 'max_connections'; +``` + +## Remote connection + +To connect to the database remotely, you can execute the `psql` recipe in the [`Justfile`](./Justfile). diff --git a/benchmarks/continous/db/tool/Cargo.lock b/benchmarks/continous/db/tool/Cargo.lock new file mode 100644 index 00000000000..45faae3f17f --- /dev/null +++ b/benchmarks/continous/db/tool/Cargo.lock @@ -0,0 +1,611 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "clap" +version = "4.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9689a29b593160de5bc4aacab7b5d54fb52231de70122626c178e6a368994c7" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5387378c84f6faa26890ebf9f0a92989f8873d4d380467bcd0d8d8620424df" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "orm", + "serde_json", +] + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "diesel" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b696af9ff4c0d2a507db2c5faafa8aa0205e297e5f11e203a24226d5355e7a" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "diesel_derives", + "itoa", + "pq-sys", +] + +[[package]] +name = "diesel_derives" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6fdd83d5947068817016e939596d246e5367279453f2a3433287894f2f2996" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn", +] + +[[package]] +name = "dsl_auto_type" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab32c18ea6760d951659768a3e35ea72fc1ba0916d665a88dfe048b2a41e543f" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "orm" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "diesel", + "serde", +] + +[[package]] +name = "pq-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5576e3fa8738e1a71285f7211ff83458514aa4864aa34c2bdb422445448d4c4b" +dependencies = [ + "vcpkg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/benchmarks/continous/db/tool/Cargo.toml b/benchmarks/continous/db/tool/Cargo.toml new file mode 100644 index 00000000000..0e30a3a7d65 --- /dev/null +++ b/benchmarks/continous/db/tool/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] +resolver = "2" + +members = [ + "cli", + "orm" +] + +[workspace.dependencies] +anyhow = "1.0" +chrono = { version = "0.4.38", features=["now", "serde"] } +clap = { version = "4.5.6", features = ["std", "derive"] } +diesel = { version = "2.1.1", features = ["postgres", "chrono"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/benchmarks/continous/db/tool/README.md b/benchmarks/continous/db/tool/README.md new file mode 100644 index 00000000000..23478ccb4cc --- /dev/null +++ b/benchmarks/continous/db/tool/README.md @@ -0,0 +1,47 @@ +# Requirements + +- An installation of [`libpq`](https://www.postgresql.org/docs/15/libpq.html). + - The name of the package providing `libpq` differs across operating systems and package managers. For example on Ubuntu you can install `libpq-dev`. +- A `~/.pggass` file with an entry matching the db URL (see [dbprofile](./dbprofile)). Wrong password file setup may lead to unintuitive error messages, therefore it is recommended to read [the docs](https://www.postgresql.org/docs/15/libpq-pgpass.html) to get the the following two points right: + - Format of password entries. + - `.pgpass` file permissions. + +## Running diesel commands + +Additionally requires: + +- An installation of the [`Diesel CLI`](https://diesel.rs/guides/getting-started.html). + +# Usage + +## CLI + +```bash +# Display available commands with: +cargo run -p cli -- --help + +# Show help for a specific . +cargo run -p cli -- --help +``` + +## Migrations + +`diesel-cli` can be used from the [orm](./orm) directory to run migrations. + +Generate the directories and files for a new migration with: + +``` +diesel migration generate +``` + +Write SQL in the generated `up.sql` and `down.sql`, then apply the migration with: + +``` +diesel migration run +``` + +Which executes the migration defined in `up.sql`. The file `down.sql` should contain SQL which reverts `up.sql`, to enable rolling the migration back, if needed. + +Before running a migration, consider backing up the db in Cloud SQL to recover from a faulty migration. + +More details can be found in Diesel's [getting started guide](https://diesel.rs/guides/getting-started). diff --git a/benchmarks/continous/db/tool/cli/Cargo.toml b/benchmarks/continous/db/tool/cli/Cargo.toml new file mode 100644 index 00000000000..25f6188cf78 --- /dev/null +++ b/benchmarks/continous/db/tool/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +clap.workspace = true +orm = { path = "../orm" } +serde_json.workspace = true diff --git a/benchmarks/continous/db/tool/cli/src/main.rs b/benchmarks/continous/db/tool/cli/src/main.rs new file mode 100644 index 00000000000..df57ac333b9 --- /dev/null +++ b/benchmarks/continous/db/tool/cli/src/main.rs @@ -0,0 +1,47 @@ +use std::fs; +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +use orm::{check_connection, establish_connection, insert_ft_transfer}; + +fn main() -> anyhow::Result<()> { + let args = Cli::parse(); + + match args.command { + Command::CheckConnection => { + let connection = &mut establish_connection()?; + check_connection(connection)?; + } + Command::InsertFtTransfer { in_path } => { + let file_content = fs::read_to_string(in_path)?; + let new_ft_transfer = serde_json::from_str(&file_content)?; + let connection = &mut establish_connection()?; + insert_ft_transfer(connection, &new_ft_transfer)?; + } + } + + Ok(()) +} + +#[derive(Debug, Parser)] +#[command( + about = "A CLI to interact with the db storing contiuous benchmark data. Commands that connect to the db require the env var DATABASE_URL_CLI to be set in a format compatible with the diesel crate. Consider sourcing the dbprofile file in the repository.", + long_about = None +)] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Connects to the db and runs a SELECT query to check if a connection can be established. + CheckConnection, + /// Insert data related to an ft transfer benchmark run. + #[command(arg_required_else_help = true)] + InsertFtTransfer { + /// Path to a file that contains the [`NewFtTransfer`](orm::models::NewFtTransfer) to insert serialized as JSON. + in_path: PathBuf, + }, +} diff --git a/benchmarks/continous/db/tool/dbprofile b/benchmarks/continous/db/tool/dbprofile new file mode 100644 index 00000000000..8de7cb44721 --- /dev/null +++ b/benchmarks/continous/db/tool/dbprofile @@ -0,0 +1,9 @@ +# Sets environment variables required for connecting to the db. +# `source ./dbprofile` + +# Used by diesel-cli. Connect as `postgres` since diesel-cli is used for db administration. +export DATABASE_URL=postgres://postgres@34.90.190.128/benchmarks + +# Used by the `/cli` crate which is executed on the nodes that run benchmarks. +export DATABASE_URL_CLI=postgres://benchmark_runner@34.90.190.128/benchmarks + diff --git a/benchmarks/continous/db/tool/orm/Cargo.toml b/benchmarks/continous/db/tool/orm/Cargo.toml new file mode 100644 index 00000000000..feb989b6a74 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "orm" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +chrono.workspace = true +diesel.workspace = true +serde.workspace = true diff --git a/benchmarks/continous/db/tool/orm/diesel.toml b/benchmarks/continous/db/tool/orm/diesel.toml new file mode 100644 index 00000000000..c028f4a6aa1 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId"] + +[migrations_directory] +dir = "migrations" diff --git a/benchmarks/continous/db/tool/orm/migrations/00000000000000_diesel_initial_setup/down.sql b/benchmarks/continous/db/tool/orm/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 00000000000..a9f52609119 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/benchmarks/continous/db/tool/orm/migrations/00000000000000_diesel_initial_setup/up.sql b/benchmarks/continous/db/tool/orm/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 00000000000..d68895b1a7b --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-03-142154_create_ft_transfers/down.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-03-142154_create_ft_transfers/down.sql new file mode 100644 index 00000000000..0ae4303f3a6 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-03-142154_create_ft_transfers/down.sql @@ -0,0 +1,4 @@ +revoke select on ft_transfers from grafana_reader; +revoke select on ft_transfers from benchmark_runner; +revoke insert on ft_transfers from benchmark_runner; +drop table ft_transfers; diff --git a/benchmarks/continous/db/ft_transfer_schema.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-03-142154_create_ft_transfers/up.sql similarity index 84% rename from benchmarks/continous/db/ft_transfer_schema.sql rename to benchmarks/continous/db/tool/orm/migrations/2024-06-03-142154_create_ft_transfers/up.sql index a5f8cf351c0..58cce5449ab 100644 --- a/benchmarks/continous/db/ft_transfer_schema.sql +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-03-142154_create_ft_transfers/up.sql @@ -1,4 +1,4 @@ -create table ft_transfer ( +create table ft_transfers ( id serial primary key, -- Metadata related to the execution of the benchmark. @@ -7,12 +7,12 @@ create table ft_transfer ( -- Enables Grafana time series queries. -- https://grafana.com/docs/grafana/latest/datasources/postgres -- /#time-series-queries - time timestamp not null, + time timestamp with time zone not null, -- Hash of the git commit off which the run's neard was compiled. git_commit_hash text not null, -- Time when the commit was made. Store it in the db to easily check how -- recent the commit is. - git_commit_time timestamp not null, + git_commit_time timestamp with time zone not null, -- Number of (physically separate) NEAR nodes participating in the run. num_nodes integer not null, -- Descriptors of NEAR nodes' hardware, e.g. GCP machine types. @@ -37,3 +37,7 @@ create table ft_transfer ( -- Total number of ft transfer transactions executed during the run. total_transactions integer not null ); + +grant select on ft_transfers to grafana_reader; +grant select on ft_transfers to benchmark_runner; +grant insert on ft_transfers to benchmark_runner; diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-12-094545_simplify_permissions/down.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-094545_simplify_permissions/down.sql new file mode 100644 index 00000000000..ec4a9f06b17 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-094545_simplify_permissions/down.sql @@ -0,0 +1,7 @@ +grant select on ft_transfers to grafana_reader; +grant select on ft_transfers to benchmark_runner; +grant insert on ft_transfers to benchmark_runner; + +revoke pg_read_all_data from grafana_reader; +revoke pg_read_all_data from benchmark_runner; +revoke pg_write_all_data from benchmark_runner; diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-12-094545_simplify_permissions/up.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-094545_simplify_permissions/up.sql new file mode 100644 index 00000000000..34d2b4932e9 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-094545_simplify_permissions/up.sql @@ -0,0 +1,13 @@ +-- Cleanup permissions granted previously. See comment below for motivation. +revoke select on ft_transfers from grafana_reader; +revoke select on ft_transfers from benchmark_runner; +revoke insert on ft_transfers from benchmark_runner; + +-- Granting individual permissions like done previously is tedious and error +-- prone. In addition it must be repeated for all new tables. +-- The benchmarks db contains no sensitive data, so we can use +-- `pg_read_all_data` and `pg_write_all_data` to simplify things. These +-- permissions automatically apply to new tables added in the future. +grant pg_read_all_data to grafana_reader; +grant pg_read_all_data to benchmark_runner; +grant pg_write_all_data to benchmark_runner; diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-12-120242_refine_ft_transfers/down.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-120242_refine_ft_transfers/down.sql new file mode 100644 index 00000000000..65fe44cdaf8 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-120242_refine_ft_transfers/down.sql @@ -0,0 +1,23 @@ +-- Assumes regularly values handled here cannot be negative, hence -1 flags out +-- of range values. +create function convert_bigint_to_int(x bigint) +returns integer as $$ +begin + if x between -2147483648 and 2147483647 then + return x; + else + return -1; + end if; +end; +$$ language plpgsql; + +alter table ft_transfers drop column time_end; +-- Ensure the bigint -> integer conversion will succeed. +update ft_transfers set + size_state_bytes = convert_bigint_to_int(size_state_bytes), + total_transactions = convert_bigint_to_int(total_transactions); +alter table ft_transfers +alter column size_state_bytes type integer, +alter column total_transactions type integer; + +drop function convert_bigint_to_int (integer); diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-12-120242_refine_ft_transfers/up.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-120242_refine_ft_transfers/up.sql new file mode 100644 index 00000000000..06b822d8667 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-12-120242_refine_ft_transfers/up.sql @@ -0,0 +1,7 @@ +alter table ft_transfers add column time_end timestamp with time zone; +-- Fill value for existing rows to allow setting `not null` in the next step. +update ft_transfers set time_end = time; +alter table ft_transfers alter column time_end set not null; +alter table ft_transfers +alter column size_state_bytes type bigint, +alter column total_transactions type bigint; diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-19-160030_ft_transfers_metadata/down.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-19-160030_ft_transfers_metadata/down.sql new file mode 100644 index 00000000000..ff30b530d1b --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-19-160030_ft_transfers_metadata/down.sql @@ -0,0 +1,3 @@ +alter table ft_transfers +drop column initiator, +drop column context; diff --git a/benchmarks/continous/db/tool/orm/migrations/2024-06-19-160030_ft_transfers_metadata/up.sql b/benchmarks/continous/db/tool/orm/migrations/2024-06-19-160030_ft_transfers_metadata/up.sql new file mode 100644 index 00000000000..e91bf893b76 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/migrations/2024-06-19-160030_ft_transfers_metadata/up.sql @@ -0,0 +1,10 @@ +-- Setting defaul values to enable `not null` by filling values for existing +-- rows. +alter table ft_transfers +add column initiator text not null default 'crt-benchmarks', +add column context text not null default 'scheduled benchmark run'; + +-- Drop defaults to enforce new data to explicitly set these columns. +alter table ft_transfers +alter column initiator drop default, +alter column context drop default; diff --git a/benchmarks/continous/db/tool/orm/src/lib.rs b/benchmarks/continous/db/tool/orm/src/lib.rs new file mode 100644 index 00000000000..88ddb78fb35 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/src/lib.rs @@ -0,0 +1,34 @@ +use std::env; + +use chrono::{DateTime, Utc}; +use diesel::pg::PgConnection; +use diesel::prelude::*; + +pub mod models; +pub mod schema; +use models::NewFtTransfer; + +pub fn establish_connection() -> anyhow::Result { + let database_url = env::var("DATABASE_URL_CLI") + .expect("DATABASE_URL_CLI must be set. Consider sourcing the dbprofile file."); + let connection = PgConnection::establish(&database_url)?; + Ok(connection) +} + +pub fn insert_ft_transfer( + connection: &mut PgConnection, + ft_transfer: &NewFtTransfer, +) -> anyhow::Result<()> { + use crate::schema::ft_transfers; + + let num_inserted = + diesel::insert_into(ft_transfers::table).values(ft_transfer).execute(connection)?; + anyhow::ensure!(num_inserted == 1, "failed to insert ft_transfer"); + Ok(()) +} + +pub fn check_connection(connection: &mut PgConnection) -> anyhow::Result<()> { + use crate::schema::ft_transfers::dsl::{ft_transfers, time}; + let _result = ft_transfers.select(time).limit(1).load::>(connection)?; + Ok(()) +} diff --git a/benchmarks/continous/db/tool/orm/src/models.rs b/benchmarks/continous/db/tool/orm/src/models.rs new file mode 100644 index 00000000000..fbc45b380be --- /dev/null +++ b/benchmarks/continous/db/tool/orm/src/models.rs @@ -0,0 +1,35 @@ +use chrono::{DateTime, Utc}; +use diesel::prelude::Insertable; +use serde::Deserialize; + +use crate::schema::ft_transfers; + +#[derive(Insertable, Deserialize)] +#[diesel(table_name = ft_transfers)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct NewFtTransfer { + /// String representation of UTC datetime when the benchmark run was started, e.g. + /// '2024-06-07T11:30:44Z'.The name `time` (as opposed to `time_start`) is chosen to enable + /// Grafana [time series queries]. + /// + /// [time series queries]: https://grafana.com/docs/grafana/latest/datasources/postgres/#time-series-queries + pub time: DateTime, + /// See `time` for formatting. + pub time_end: DateTime, + pub git_commit_hash: String, + /// See `time` for formatting. + pub git_commit_time: DateTime, + pub num_nodes: i32, + pub node_hardware: Vec, + pub num_traffic_gen_machines: i32, + pub disjoint_workloads: bool, + pub num_shards: i32, + pub num_unique_users: i32, + pub size_state_bytes: i64, + pub tps: i32, + pub total_transactions: i64, + /// Specifies who ran the benchmark. + pub initiator: String, + /// Describes the context, e.g. *scheduled continuous benchmark run*. + pub context: String, +} diff --git a/benchmarks/continous/db/tool/orm/src/schema.rs b/benchmarks/continous/db/tool/orm/src/schema.rs new file mode 100644 index 00000000000..de8f1328c77 --- /dev/null +++ b/benchmarks/continous/db/tool/orm/src/schema.rs @@ -0,0 +1,22 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + ft_transfers (id) { + id -> Int4, + time -> Timestamptz, + git_commit_hash -> Text, + git_commit_time -> Timestamptz, + num_nodes -> Int4, + node_hardware -> Array>, + num_traffic_gen_machines -> Int4, + disjoint_workloads -> Bool, + num_shards -> Int4, + num_unique_users -> Int4, + size_state_bytes -> Int8, + tps -> Int4, + total_transactions -> Int8, + time_end -> Timestamptz, + initiator -> Text, + context -> Text, + } +} diff --git a/chain/chain-primitives/Cargo.toml b/chain/chain-primitives/Cargo.toml index 14f92ce6309..d37b76b74de 100644 --- a/chain/chain-primitives/Cargo.toml +++ b/chain/chain-primitives/Cargo.toml @@ -16,7 +16,7 @@ thiserror.workspace = true time.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-primitives.workspace = true near-crypto.workspace = true diff --git a/chain/chain-primitives/src/error.rs b/chain/chain-primitives/src/error.rs index 7b6a2e3edc6..6458b1b1b58 100644 --- a/chain/chain-primitives/src/error.rs +++ b/chain/chain-primitives/src/error.rs @@ -1,10 +1,10 @@ -use near_async::time::Utc; use near_primitives::block::BlockValidityError; use near_primitives::challenge::{ChunkProofs, ChunkState}; use near_primitives::errors::{EpochError, StorageError}; use near_primitives::shard_layout::ShardLayoutError; use near_primitives::sharding::{ChunkHash, ShardChunkHeader}; use near_primitives::types::{BlockHeight, EpochId, ShardId}; +use near_time::Utc; use std::io; #[derive(thiserror::Error, Debug)] @@ -180,8 +180,8 @@ pub enum Error { #[error("Invalid Balance Burnt")] InvalidBalanceBurnt, /// Invalid Congestion Info - #[error("Invalid Congestion Info")] - InvalidCongestionInfo, + #[error("Invalid Congestion Info: {0}")] + InvalidCongestionInfo(String), /// Invalid shard id #[error("Shard id {0} does not exist")] InvalidShardId(ShardId), @@ -198,8 +198,8 @@ pub enum Error { #[error("Invalid Split Shard Ids when resharding. shard_id: {0}, parent_shard_id: {1}")] InvalidSplitShardsIds(u64, u64), /// Someone is not a validator. Usually happens in signature verification - #[error("Not A Validator")] - NotAValidator, + #[error("Not A Validator: {0}")] + NotAValidator(String), /// Someone is not a chunk validator. Happens if we're asked to validate a chunk we're not /// supposed to validate, or to verify a chunk approval signed by a validator that isn't /// supposed to validate the chunk. @@ -302,13 +302,13 @@ impl Error { | Error::InvalidGasPrice | Error::InvalidGasUsed | Error::InvalidBalanceBurnt - | Error::InvalidCongestionInfo + | Error::InvalidCongestionInfo(_) | Error::InvalidShardId(_) | Error::InvalidStateRequest(_) | Error::InvalidRandomnessBeaconOutput | Error::InvalidBlockMerkleRoot | Error::InvalidProtocolVersion - | Error::NotAValidator + | Error::NotAValidator(_) | Error::NotAChunkValidator | Error::InvalidChallengeRoot => true, } @@ -378,13 +378,13 @@ impl Error { Error::InvalidGasPrice => "invalid_gas_price", Error::InvalidGasUsed => "invalid_gas_used", Error::InvalidBalanceBurnt => "invalid_balance_burnt", - Error::InvalidCongestionInfo => "invalid_congestion_info", + Error::InvalidCongestionInfo(_) => "invalid_congestion_info", Error::InvalidShardId(_) => "invalid_shard_id", Error::InvalidStateRequest(_) => "invalid_state_request", Error::InvalidRandomnessBeaconOutput => "invalid_randomness_beacon_output", Error::InvalidBlockMerkleRoot => "invalid_block_merkele_root", Error::InvalidProtocolVersion => "invalid_protocol_version", - Error::NotAValidator => "not_a_validator", + Error::NotAValidator(_) => "not_a_validator", Error::NotAChunkValidator => "not_a_chunk_validator", Error::InvalidChallengeRoot => "invalid_challenge_root", } @@ -396,7 +396,9 @@ impl From for Error { match error { EpochError::EpochOutOfBounds(epoch_id) => Error::EpochOutOfBounds(epoch_id), EpochError::MissingBlock(h) => Error::DBNotFoundErr(format!("epoch block: {h}")), - EpochError::NotAValidator(_account_id, _epoch_id) => Error::NotAValidator, + EpochError::NotAValidator(account_id, epoch_id) => { + Error::NotAValidator(format!("account_id: {account_id}, epoch_id: {epoch_id:?}")) + } err => Error::ValidatorError(err.to_string()), } } diff --git a/chain/chain/Cargo.toml b/chain/chain/Cargo.toml index ad8c85a1249..bf921b08bbb 100644 --- a/chain/chain/Cargo.toml +++ b/chain/chain/Cargo.toml @@ -34,7 +34,10 @@ yansi.workspace = true near-async.workspace = true near-cache.workspace = true -near-chain-configs.workspace = true +near-chain-configs = { workspace = true, features = [ + "test_genesis", + "test_utils", +] } near-chain-primitives.workspace = true near-client-primitives.workspace = true near-crypto.workspace = true @@ -44,7 +47,7 @@ near-o11y.workspace = true near-performance-metrics.workspace = true near-performance-metrics-macros.workspace = true near-pool.workspace = true -near-primitives.workspace = true +near-primitives = { workspace = true, features = ["solomon", "rand"] } near-store.workspace = true node-runtime.workspace = true near-parameters.workspace = true @@ -52,6 +55,7 @@ near-vm-runner.workspace = true near-mainnet-res.workspace = true [dev-dependencies] +near-primitives = { workspace = true, features = ["clock"] } serde_json.workspace = true primitive-types.workspace = true insta.workspace = true @@ -64,6 +68,8 @@ expensive_tests = [] test_features = [ "near-vm-runner/test_features", "near-primitives/test_features", + "near-store/test_features", + "node-runtime/test_features", ] shadow_chunk_validation = [] no_cache = ["near-store/no_cache"] @@ -110,5 +116,9 @@ nightly_protocol = [ "near-vm-runner/nightly_protocol", "node-runtime/nightly_protocol", ] -statelessnet_protocol = ["near-primitives/statelessnet_protocol"] -sandbox = ["near-primitives/sandbox"] +statelessnet_protocol = [ + "near-store/statelessnet_protocol", + "near-primitives/statelessnet_protocol", +] +sandbox = ["near-o11y/sandbox", "near-primitives/sandbox"] +testloop = [] diff --git a/chain/chain/src/block_processing_utils.rs b/chain/chain/src/block_processing_utils.rs index 988486beac9..cc96b3d171b 100644 --- a/chain/chain/src/block_processing_utils.rs +++ b/chain/chain/src/block_processing_utils.rs @@ -8,9 +8,9 @@ use near_primitives::challenge::{ChallengeBody, ChallengesResult}; use near_primitives::hash::CryptoHash; use near_primitives::sharding::{ReceiptProof, ShardChunkHeader, StateSyncInfo}; use near_primitives::types::ShardId; -use once_cell::sync::OnceCell; use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; /// Max number of blocks that can be in the pool at once. /// This number will likely never be hit unless there are many forks in the chain. @@ -24,10 +24,8 @@ pub(crate) struct BlockPreprocessInfo { pub(crate) challenges_result: ChallengesResult, pub(crate) challenged_blocks: Vec, pub(crate) provenance: Provenance, - /// This field will be set when the apply_chunks has finished. - /// This is used to provide a way for caller to wait for the finishing of applying chunks of - /// a block - pub(crate) apply_chunks_done: Arc>, + /// Used to get notified when the applying chunks of a block finishes. + pub(crate) apply_chunks_done_tracker: ApplyChunksDoneTracker, /// This is used to calculate block processing time metric pub(crate) block_start_processing_time: Instant, } @@ -129,7 +127,7 @@ impl BlocksInProcessing { /// Returns true if new blocks are done applying chunks pub(crate) fn wait_for_all_blocks(&self) -> bool { for (_, (_, block_preprocess_info)) in self.preprocessed_blocks.iter() { - let _ = block_preprocess_info.apply_chunks_done.wait(); + let _ = block_preprocess_info.apply_chunks_done_tracker.wait_until_done(); } !self.preprocessed_blocks.is_empty() } @@ -144,8 +142,125 @@ impl BlocksInProcessing { .get(block_hash) .ok_or(BlockNotInPoolError)? .1 - .apply_chunks_done - .wait(); + .apply_chunks_done_tracker + .wait_until_done(); Ok(()) } } + +/// This is used to for the thread that applies chunks to notify other waiter threads. +/// The thread applying the chunks should call `set_done` to send the notification. +/// The waiter threads should call `wait_until_done` to wait (blocked) for the notification. +#[derive(Clone)] +pub struct ApplyChunksDoneTracker(Arc<(Mutex, Condvar)>); + +impl ApplyChunksDoneTracker { + pub fn new() -> Self { + Self(Arc::new((Mutex::new(false), Condvar::new()))) + } + + /// Notifies all threads waiting on `wait_until_done` that apply chunks is done. + /// This should be called only once. + /// Returns an error if it is called more than once or the mutex used internally is poisoned. + pub fn set_done(&mut self) -> Result<(), &'static str> { + let (lock, cvar) = &*self.0; + match lock.lock() { + Ok(mut guard) => { + if *guard { + Err("Apply chunks done marker is already set to true.") + } else { + *guard = true; + cvar.notify_all(); + Ok(()) + } + } + Err(_poisoned) => Err("Mutex is poisoned."), + } + } + + /// Blocks the current thread until the `set_done` is called after applying the chunks. + /// to indicate that apply chunks is done. + pub fn wait_until_done(&self) { + #[cfg(feature = "testloop")] + let mut testloop_total_wait_time = Duration::from_millis(0); + + let (lock, cvar) = &*self.0; + match lock.lock() { + Ok(mut guard) => loop { + let done = *guard; + if done { + break; + } + const WAIT_TIMEOUT: Duration = Duration::from_millis(100); + match cvar.wait_timeout(guard, WAIT_TIMEOUT) { + Ok(result) => { + guard = result.0; + + // Panics during testing (eg. due to assertion failures) cause the waiter + // threads to miss the notification (see issue #11447). Thus, for testing only, + // we limit the total wait time for waiting for the notification. + #[cfg(feature = "testloop")] + if result.1.timed_out() { + const TESTLOOP_MAX_WAIT_TIME: Duration = Duration::from_millis(5000); + testloop_total_wait_time += WAIT_TIMEOUT; + if testloop_total_wait_time >= TESTLOOP_MAX_WAIT_TIME { + break; + } + } + } + Err(_poisoned) => { + tracing::error!("Mutex is poisoned."); + break; + } + } + }, + Err(_poisoned) => { + tracing::error!("Mutex is poisoned."); + () + } + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::time::Duration; + + use super::ApplyChunksDoneTracker; + + #[test] + fn test_apply_chunks_with_multiple_waiters() { + let shared_value: Arc = Arc::new(AtomicBool::new(false)); + + let mut tracker = ApplyChunksDoneTracker::new(); + let waiter1 = tracker.clone(); + let waiter2 = tracker.clone(); + let waiter3 = tracker.clone(); + + let (results_sender, results_receiver) = std::sync::mpsc::channel(); + + // Spawn waiter tasks + for waiter in [waiter1, waiter2, waiter3] { + let current_sender = results_sender.clone(); + let current_shared_value = shared_value.clone(); + std::thread::spawn(move || { + waiter.wait_until_done(); + let read_value = current_shared_value.load(Ordering::Relaxed); + current_sender.send(read_value).unwrap(); + }); + } + + // Wait 300ms then set the shared_value to true, and notify the waiters. + std::thread::sleep(Duration::from_millis(300)); + shared_value.store(true, Ordering::Relaxed); + tracker.set_done().unwrap(); + + // Check values that waiters read + for _ in 0..3 { + let waiter_value = results_receiver.recv().unwrap(); + assert_eq!(waiter_value, true); + } + } +} diff --git a/chain/chain/src/chain.rs b/chain/chain/src/chain.rs index 72787e82b85..7d9351a1785 100644 --- a/chain/chain/src/chain.rs +++ b/chain/chain/src/chain.rs @@ -1,5 +1,5 @@ use crate::block_processing_utils::{ - BlockPreprocessInfo, BlockProcessingArtifact, BlocksInProcessing, + ApplyChunksDoneTracker, BlockPreprocessInfo, BlockProcessingArtifact, BlocksInProcessing, }; use crate::blocks_delay_tracker::BlocksDelayTracker; use crate::chain_update::ChainUpdate; @@ -39,7 +39,9 @@ use itertools::Itertools; use lru::LruCache; use near_async::futures::{AsyncComputationSpawner, AsyncComputationSpawnerExt}; use near_async::time::{Clock, Duration, Instant}; -use near_chain_configs::{MutableConfigValue, ReshardingConfig, ReshardingHandle}; +use near_chain_configs::{ + MutableConfigValue, MutableValidatorSigner, ReshardingConfig, ReshardingHandle, +}; #[cfg(feature = "new_epoch_sync")] use near_chain_primitives::error::epoch_sync::EpochSyncInfoError; use near_chain_primitives::error::{BlockKnownError, Error, LogTransientStorageError}; @@ -76,7 +78,7 @@ use near_primitives::state_sync::{ get_num_state_parts, BitArray, CachedParts, ReceiptProofResponse, RootProof, ShardStateSyncResponseHeader, ShardStateSyncResponseHeaderV2, StateHeaderKey, StatePartKey, }; -use near_primitives::stateless_validation::EncodedChunkStateWitness; +use near_primitives::stateless_validation::{ChunkStateWitness, ChunkStateWitnessSize}; use near_primitives::transaction::{ExecutionOutcomeWithIdAndProof, SignedTransaction}; use near_primitives::types::chunk_extra::ChunkExtra; use near_primitives::types::{ @@ -97,10 +99,11 @@ use near_store::config::StateSnapshotType; use near_store::flat::{store_helper, FlatStorageReadyStatus, FlatStorageStatus}; use near_store::get_genesis_state_roots; use near_store::DBCol; -use once_cell::sync::OnceCell; +use node_runtime::bootstrap_congestion_info; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::{Debug, Formatter}; +use std::num::NonZeroUsize; use std::sync::Arc; use time::ext::InstantExt as _; use tracing::{debug, debug_span, error, info, warn, Span}; @@ -318,8 +321,11 @@ impl Chain { ) -> Result { let state_roots = get_genesis_state_roots(runtime_adapter.store())? .expect("genesis should be initialized."); + let congestion_infos = + get_genesis_congestion_infos(epoch_manager, runtime_adapter, &state_roots)?; let genesis_chunks = genesis_chunks( state_roots, + congestion_infos, &epoch_manager.shard_ids(&EpochId::default())?, chain_genesis.gas_limit, chain_genesis.height, @@ -377,7 +383,7 @@ impl Chain { apply_chunks_receiver: rc, apply_chunks_spawner: Arc::new(RayonAsyncComputationSpawner), last_time_head_updated: clock.now(), - invalid_blocks: LruCache::new(INVALID_CHUNKS_POOL_SIZE), + invalid_blocks: LruCache::new(NonZeroUsize::new(INVALID_CHUNKS_POOL_SIZE).unwrap()), pending_state_patch: Default::default(), requested_state_parts: StateRequestTracker::new(), snapshot_callbacks: None, @@ -399,18 +405,19 @@ impl Chain { chain_config: ChainConfig, snapshot_callbacks: Option, apply_chunks_spawner: Arc, - validator_account_id: Option<&AccountId>, + validator: MutableValidatorSigner, ) -> Result { // Get runtime initial state and create genesis block out of it. let state_roots = get_genesis_state_roots(runtime_adapter.store())? .expect("genesis should be initialized."); - let mut chain_store = ChainStore::new( - runtime_adapter.store().clone(), - chain_genesis.height, - chain_config.save_trie_changes, - ); + let congestion_infos = get_genesis_congestion_infos( + epoch_manager.as_ref(), + runtime_adapter.as_ref(), + &state_roots, + )?; let genesis_chunks = genesis_chunks( state_roots.clone(), + congestion_infos, &epoch_manager.shard_ids(&EpochId::default())?, chain_genesis.gas_limit, chain_genesis.height, @@ -431,6 +438,12 @@ impl Chain { )?, ); + let mut chain_store = ChainStore::new( + runtime_adapter.store().clone(), + chain_genesis.height, + chain_config.save_trie_changes, + ); + // Check if we have a head in the store, otherwise pick genesis block. let mut store_update = chain_store.store_update(); let (block_head, header_head) = match store_update.head() { @@ -476,7 +489,7 @@ impl Chain { .enabled(chain_genesis.protocol_version) { genesis - .shards_congestion_info() + .block_congestion_info() .get(&chunk_header.shard_id()) .map(|info| info.congestion_info) } else { @@ -538,7 +551,7 @@ impl Chain { .iter() .filter(|shard_uid| { shard_tracker.care_about_shard( - validator_account_id, + validator.get().map(|v| v.validator_id().clone()).as_ref(), &tip.prev_block_hash, shard_uid.shard_id(), true, @@ -546,7 +559,7 @@ impl Chain { }) .cloned() .collect(); - runtime_adapter.get_tries().load_mem_tries_for_enabled_shards(&tracked_shards)?; + runtime_adapter.get_tries().load_mem_tries_for_enabled_shards(&tracked_shards, true)?; info!(target: "chain", "Init: header head @ #{} {}; block head @ #{} {}", header_head.height, header_head.last_block_hash, @@ -573,7 +586,7 @@ impl Chain { orphans: OrphanBlockPool::new(), blocks_with_missing_chunks: MissingChunksPool::new(), blocks_in_processing: BlocksInProcessing::new(), - invalid_blocks: LruCache::new(INVALID_CHUNKS_POOL_SIZE), + invalid_blocks: LruCache::new(NonZeroUsize::new(INVALID_CHUNKS_POOL_SIZE).unwrap()), genesis: genesis.clone(), transaction_validity_period: chain_genesis.transaction_validity_period, epoch_length: chain_genesis.epoch_length, @@ -986,8 +999,8 @@ impl Chain { if header.next_bp_hash() != &Chain::compute_bp_hash( self.epoch_manager.as_ref(), - header.next_epoch_id().clone(), - header.epoch_id().clone(), + *header.next_epoch_id(), + *header.epoch_id(), header.prev_hash(), )? { @@ -1782,7 +1795,7 @@ impl Chain { let block = block.into_inner(); let block_hash = *block.hash(); let block_height = block.header().height(); - let apply_chunks_done_marker = block_preprocess_info.apply_chunks_done.clone(); + let apply_chunks_done_tracker = block_preprocess_info.apply_chunks_done_tracker.clone(); self.blocks_in_processing.add(block, block_preprocess_info)?; // 3) schedule apply chunks, which will be executed in the rayon thread pool. @@ -1790,7 +1803,7 @@ impl Chain { block_hash, block_height, apply_chunk_work, - apply_chunks_done_marker, + apply_chunks_done_tracker, apply_chunks_done_sender, ); @@ -1798,14 +1811,14 @@ impl Chain { } /// Applying chunks async by starting the work at the rayon thread pool - /// `apply_chunks_done_marker`: a marker that will be set to true once applying chunks is finished + /// `apply_chunks_done_tracker`: notifies the threads that wait for applying chunks is finished /// `apply_chunks_done_sender`: a sender to send a ApplyChunksDoneMessage message once applying chunks is finished fn schedule_apply_chunks( &self, block_hash: CryptoHash, block_height: BlockHeight, work: Vec, - apply_chunks_done_marker: Arc>, + mut apply_chunks_done_tracker: ApplyChunksDoneTracker, apply_chunks_done_sender: Option>, ) { let sc = self.apply_chunks_sender.clone(); @@ -1815,7 +1828,7 @@ impl Chain { // If we encounter error here, that means the receiver is deallocated and the client // thread is already shut down. The node is already crashed, so we can unwrap here sc.send((block_hash, res)).unwrap(); - if let Err(_) = apply_chunks_done_marker.set(()) { + if let Err(_) = apply_chunks_done_tracker.set_done() { // This should never happen, if it does, it means there is a bug in our code. log_assert!(false, "apply chunks are called twice for block {block_hash:?}"); } @@ -1839,7 +1852,6 @@ impl Chain { self.should_produce_state_witness_for_this_or_next_epoch(me, block.header())?; let mut chain_update = self.chain_update(); let new_head = chain_update.postprocess_block( - me, &block, block_preprocess_info, apply_results, @@ -1899,49 +1911,55 @@ impl Chain { Ok(new_head) => new_head, }; - // Update flat storage head to be the last final block. Note that this update happens - // in a separate db transaction from the update from block processing. This is intentional - // because flat_storage need to be locked during the update of flat head, otherwise - // flat_storage is in an inconsistent state that could be accessed by the other - // apply chunks processes. This means, the flat head is not always the same as - // the last final block on chain, which is OK, because in the flat storage implementation - // we don't assume that. let epoch_id = block.header().epoch_id(); + let mut shards_cares_this_or_next_epoch = vec![]; for shard_id in self.epoch_manager.shard_ids(epoch_id)? { + let care_about_shard = self.shard_tracker.care_about_shard( + me.as_ref(), + block.header().prev_hash(), + shard_id, + true, + ); + let will_care_about_shard = self.shard_tracker.will_care_about_shard( + me.as_ref(), + block.header().prev_hash(), + shard_id, + true, + ); + let care_about_shard_this_or_next_epoch = care_about_shard || will_care_about_shard; + if care_about_shard_this_or_next_epoch { + let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, &epoch_id).unwrap(); + shards_cares_this_or_next_epoch.push(shard_uid); + } + + // Update flat storage head to be the last final block. Note that this update happens + // in a separate db transaction from the update from block processing. This is intentional + // because flat_storage need to be locked during the update of flat head, otherwise + // flat_storage is in an inconsistent state that could be accessed by the other + // apply chunks processes. This means, the flat head is not always the same as + // the last final block on chain, which is OK, because in the flat storage implementation + // we don't assume that. let need_flat_storage_update = if is_caught_up { // If we already caught up this epoch, then flat storage exists for both shards which we already track // and shards which will be tracked in next epoch, so we can update them. - self.shard_tracker.care_about_shard( - me.as_ref(), - block.header().prev_hash(), - shard_id, - true, - ) || self.shard_tracker.will_care_about_shard( - me.as_ref(), - block.header().prev_hash(), - shard_id, - true, - ) + care_about_shard_this_or_next_epoch } else { // If we didn't catch up, we can update only shards tracked right now. Remaining shards will be updated // during catchup of this block. - self.shard_tracker.care_about_shard( - me.as_ref(), - block.header().prev_hash(), - shard_id, - true, - ) + care_about_shard }; - tracing::debug!(target: "chain", shard_id,need_flat_storage_update, "Updating flat storage"); + tracing::debug!(target: "chain", shard_id, need_flat_storage_update, "Updating flat storage"); if need_flat_storage_update { - let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, epoch_id)?; - let flat_storage_manager = self.runtime_adapter.get_flat_storage_manager(); - flat_storage_manager.update_flat_storage_for_shard(shard_uid, &block)?; - self.garbage_collect_memtrie_roots(&block, shard_uid); + self.update_flat_storage_and_memtrie(&block, shard_id)?; } } + if self.epoch_manager.is_next_block_epoch_start(block.header().prev_hash())? { + // Keep in memory only these tries that we care about this or next epoch. + self.runtime_adapter.get_tries().retain_mem_tries(&shards_cares_this_or_next_epoch); + } + if let Err(err) = self.garbage_collect_state_transition_data(&block) { tracing::error!(target: "chain", ?err, "failed to garbage collect state transition data"); } @@ -1988,7 +2006,65 @@ impl Chain { Ok(AcceptedBlock { hash: *block.hash(), status: block_status, provenance }) } - fn garbage_collect_memtrie_roots(&self, block: &Block, shard_uid: ShardUId) { + /// Gets new flat storage head candidate for given `shard_id` and newly + /// processed `block`. + /// It will be `block.last_final_block().chunk(shard_id).prev_block_hash()` + /// if all necessary conditions are met. + /// This is required for `StateSnapshot` to be able to make snapshot of + /// flat storage at the epoch boundary. + fn get_new_flat_storage_head( + &self, + block: &Block, + shard_id: ShardId, + ) -> Result, Error> { + let epoch_id = block.header().epoch_id(); + let last_final_block_hash = *block.header().last_final_block(); + // If final block doesn't exist yet, skip getting candidate. + if last_final_block_hash == CryptoHash::default() { + return Ok(None); + } + + let last_final_block = self.get_block(&last_final_block_hash)?; + let last_final_block_epoch_id = last_final_block.header().epoch_id(); + // If shard layout was changed, the update is impossible so we skip + // getting candidate. + if self.epoch_manager.get_shard_layout(last_final_block_epoch_id) + != self.epoch_manager.get_shard_layout(epoch_id) + { + return Ok(None); + } + + let last_final_block_chunks = last_final_block.chunks(); + let chunk_header = last_final_block_chunks + .iter() + .find(|chunk| chunk.shard_id() == shard_id) + .ok_or_else(|| Error::InvalidShardId(shard_id))?; + let new_flat_head = *chunk_header.prev_block_hash(); + if new_flat_head == CryptoHash::default() { + return Ok(None); + } + Ok(Some(new_flat_head)) + } + + /// Update flat storage and memtrie for given `shard_id` and newly + /// processed `block`. + fn update_flat_storage_and_memtrie( + &self, + block: &Block, + shard_id: ShardId, + ) -> Result<(), Error> { + let epoch_id = block.header().epoch_id(); + let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, epoch_id)?; + + // Update flat storage. + let flat_storage_manager = self.runtime_adapter.get_flat_storage_manager(); + if flat_storage_manager.get_flat_storage_for_shard(shard_uid).is_some() { + if let Some(new_flat_head) = self.get_new_flat_storage_head(block, shard_id)? { + flat_storage_manager.update_flat_storage_for_shard(shard_uid, new_flat_head)?; + } + } + + // Garbage collect memtrie roots. let tries = self.runtime_adapter.get_tries(); let last_final_block = block.header().last_final_block(); if last_final_block != &CryptoHash::default() { @@ -1997,6 +2073,7 @@ impl Chain { tries.delete_memtrie_roots_up_to_height(shard_uid, prev_height); } } + Ok(()) } /// Preprocess a block before applying chunks, verify that we have the necessary information @@ -2022,7 +2099,7 @@ impl Chain { // Check that we know the epoch of the block before we try to get the header // (so that a block from unknown epoch doesn't get marked as an orphan) if !self.epoch_manager.epoch_exists(header.epoch_id()) { - return Err(Error::EpochOutOfBounds(header.epoch_id().clone())); + return Err(Error::EpochOutOfBounds(*header.epoch_id())); } if block.chunks().len() != self.epoch_manager.shard_ids(header.epoch_id())?.len() { @@ -2170,7 +2247,7 @@ impl Chain { challenges_result, challenged_blocks, provenance: provenance.clone(), - apply_chunks_done: Arc::new(OnceCell::new()), + apply_chunks_done_tracker: ApplyChunksDoneTracker::new(), block_start_processing_time: block_received_time, }, )) @@ -2799,7 +2876,7 @@ impl Chain { num_parts: u64, state_parts_task_scheduler: &near_async::messaging::Sender, ) -> Result<(), Error> { - let epoch_id = self.get_block_header(&sync_hash)?.epoch_id().clone(); + let epoch_id = *self.get_block_header(&sync_hash)?.epoch_id(); let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, &epoch_id)?; let shard_state_header = self.get_state_header(shard_id, sync_hash)?; @@ -2903,7 +2980,7 @@ impl Chain { let flat_storage_manager = self.runtime_adapter.get_flat_storage_manager(); if let Some(flat_storage) = flat_storage_manager.get_flat_storage_for_shard(shard_uid) { let header = self.get_block_header(&sync_hash)?; - flat_storage.update_flat_head(header.prev_hash(), true).unwrap(); + flat_storage.update_flat_head(header.prev_hash()).unwrap(); } Ok(()) @@ -3068,7 +3145,7 @@ impl Chain { } else { prev_block_header.next_gas_price() }; - let congestion_info = block.shards_congestion_info(); + let congestion_info = block.block_congestion_info(); Ok(ApplyChunkBlockContext::from_header(block_header, gas_price, congestion_info)) } @@ -3108,10 +3185,7 @@ impl Chain { shard_id, true, ) { - let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, epoch_id)?; - let flat_storage_manager = self.runtime_adapter.get_flat_storage_manager(); - flat_storage_manager.update_flat_storage_for_shard(shard_uid, &block)?; - self.garbage_collect_memtrie_roots(&block, shard_uid); + self.update_flat_storage_and_memtrie(&block, shard_id)?; } } @@ -3849,6 +3923,89 @@ impl Chain { } } +/// This method calculates the congestion info for the genesis chunks. It uses +/// the congestion info bootstrapping logic. This method is just a wrapper +/// around the [`get_genesis_congestion_infos_impl`]. It logs an error if one +/// happens. +pub fn get_genesis_congestion_infos( + epoch_manager: &dyn EpochManagerAdapter, + runtime: &dyn RuntimeAdapter, + state_roots: &Vec, +) -> Result>, Error> { + get_genesis_congestion_infos_impl(epoch_manager, runtime, state_roots).map_err(|err| { + tracing::error!(target: "chain", ?err, "Failed to get the genesis congestion infos."); + err + }) +} + +fn get_genesis_congestion_infos_impl( + epoch_manager: &dyn EpochManagerAdapter, + runtime: &dyn RuntimeAdapter, + state_roots: &Vec, +) -> Result>, Error> { + let genesis_prev_hash = CryptoHash::default(); + let genesis_epoch_id = epoch_manager.get_epoch_id_from_prev_block(&genesis_prev_hash)?; + let genesis_protocol_version = epoch_manager.get_epoch_protocol_version(&genesis_epoch_id)?; + // If congestion control is not enabled at the genesis block, we return None (congestion info) for each shard. + if !ProtocolFeature::CongestionControl.enabled(genesis_protocol_version) { + return Ok(std::iter::repeat(None).take(state_roots.len()).collect()); + } + + // Since the congestion info is already bootstrapped in statelessnet, skip another bootstrap. + // TODO: This is temporary mitigation for the failing genesis congestion info due to garbage + // collected genesis state roots. It can be removed after the statelessnet network is turned down. + if let Ok(protocol_config) = runtime.get_protocol_config(&genesis_epoch_id) { + if protocol_config.genesis_config.chain_id == near_primitives::chains::STATELESSNET { + return Ok(std::iter::repeat(None).take(state_roots.len()).collect()); + } + } + + // Check we had already computed the congestion infos from the genesis state roots. + if let Some(saved_infos) = near_store::get_genesis_congestion_infos(runtime.store())? { + tracing::debug!(target: "chain", "Reading genesis congestion infos from database."); + return Ok(saved_infos.into_iter().map(Option::Some).collect()); + } + + let mut new_infos = vec![]; + for (shard_id, &state_root) in state_roots.iter().enumerate() { + let shard_id = shard_id as ShardId; + let congestion_info = get_genesis_congestion_info( + runtime, + genesis_protocol_version, + &genesis_prev_hash, + shard_id, + state_root, + )?; + new_infos.push(congestion_info); + } + + // Store it in DB so that we can read it later, instead of recomputing from genesis state roots. + // Note that this is necessary because genesis state roots will be garbage-collected and will not + // be available, for example, when the node restarts later. + tracing::debug!(target: "chain", "Saving genesis congestion infos to database."); + let mut store_update = runtime.store().store_update(); + near_store::set_genesis_congestion_infos(&mut store_update, &new_infos); + store_update.commit()?; + + Ok(new_infos.into_iter().map(Option::Some).collect()) +} + +fn get_genesis_congestion_info( + runtime: &dyn RuntimeAdapter, + protocol_version: ProtocolVersion, + prev_hash: &CryptoHash, + shard_id: ShardId, + state_root: StateRoot, +) -> Result { + // Get the view trie because it's possible that the chain is ahead of + // genesis and doesn't have this block in flat state and memtrie. + let trie = runtime.get_view_trie_for_shard(shard_id, prev_hash, state_root)?; + let runtime_config = runtime.get_runtime_config(protocol_version)?; + let congestion_info = bootstrap_congestion_info(&trie, &runtime_config, shard_id)?; + tracing::debug!(target: "chain", ?shard_id, ?state_root, ?congestion_info, "Computed genesis congestion info."); + Ok(congestion_info) +} + fn shard_id_out_of_bounds(shard_id: ShardId) -> Error { Error::InvalidStateRequest(format!("shard_id {shard_id:?} out of bounds").into()) } @@ -3903,7 +4060,6 @@ impl Chain { ChainUpdate::new( &mut self.chain_store, self.epoch_manager.clone(), - self.shard_tracker.clone(), self.runtime_adapter.clone(), self.doomslug_threshold_mode, self.transaction_validity_period, @@ -4196,12 +4352,6 @@ impl Chain { self.chain_store.get_chunk_extra(block_hash, shard_uid) } - /// Get destination shard id for a given receipt id. - #[inline] - pub fn get_shard_id_for_receipt_id(&self, receipt_id: &CryptoHash) -> Result { - self.chain_store.get_shard_id_for_receipt_id(receipt_id) - } - /// Get next block hash for which there is a new chunk for the shard. /// If sharding changes before we can find a block with a new chunk for the shard, /// find the first block that contains a new chunk for any of the shards that split from the @@ -4212,13 +4362,13 @@ impl Chain { shard_id: ShardId, ) -> Result, Error> { let mut block_hash = *block_hash; - let mut epoch_id = self.get_block_header(&block_hash)?.epoch_id().clone(); + let mut epoch_id = *self.get_block_header(&block_hash)?.epoch_id(); let mut shard_layout = self.epoch_manager.get_shard_layout(&epoch_id)?; // this corrects all the shard where the original shard will split to if sharding changes let mut shard_ids = vec![shard_id]; while let Ok(next_block_hash) = self.chain_store.get_next_block_hash(&block_hash) { - let next_epoch_id = self.get_block_header(&next_block_hash)?.epoch_id().clone(); + let next_epoch_id = *self.get_block_header(&next_block_hash)?.epoch_id(); if next_epoch_id != epoch_id { let next_shard_layout = self.epoch_manager.get_shard_layout(&next_epoch_id)?; if next_shard_layout != shard_layout { @@ -4696,7 +4846,10 @@ pub struct BlockCatchUpResponse { #[derive(actix::Message, Debug, Clone, PartialEq, Eq)] #[rtype(result = "()")] -pub struct ChunkStateWitnessMessage(pub EncodedChunkStateWitness); +pub struct ChunkStateWitnessMessage { + pub witness: ChunkStateWitness, + pub raw_witness_size: ChunkStateWitnessSize, +} /// Helper to track blocks catch up /// Lifetime of a block_hash is as follows: diff --git a/chain/chain/src/chain_update.rs b/chain/chain/src/chain_update.rs index b14ace7bb10..c83cda618e9 100644 --- a/chain/chain/src/chain_update.rs +++ b/chain/chain/src/chain_update.rs @@ -11,7 +11,6 @@ use crate::update_shard::{NewChunkResult, OldChunkResult, ReshardingResult, Shar use crate::{metrics, DoomslugThresholdMode}; use crate::{Chain, Doomslug}; use near_chain_primitives::error::Error; -use near_epoch_manager::shard_tracker::ShardTracker; use near_epoch_manager::types::BlockHeaderInfo; use near_epoch_manager::EpochManagerAdapter; use near_primitives::apply::ApplyChunkReason; @@ -20,13 +19,11 @@ use near_primitives::block_header::BlockHeader; #[cfg(feature = "new_epoch_sync")] use near_primitives::epoch_manager::{block_info::BlockInfo, epoch_sync::EpochSyncInfo}; use near_primitives::hash::CryptoHash; -use near_primitives::shard_layout::{account_id_to_shard_id, account_id_to_shard_uid, ShardUId}; +use near_primitives::shard_layout::{account_id_to_shard_uid, ShardUId}; use near_primitives::sharding::ShardChunk; use near_primitives::state_sync::{ReceiptProofResponse, ShardStateSyncResponseHeader}; use near_primitives::types::chunk_extra::ChunkExtra; -use near_primitives::types::{ - AccountId, BlockExtra, BlockHeight, BlockHeightDelta, NumShards, ShardId, -}; +use near_primitives::types::{BlockExtra, BlockHeight, BlockHeightDelta, NumShards, ShardId}; use near_primitives::version::ProtocolFeature; use near_primitives::views::LightClientBlockView; use std::collections::HashMap; @@ -41,7 +38,6 @@ use tracing::{debug, info, warn}; /// Safe to stop process mid way (Ctrl+C or crash). pub struct ChainUpdate<'a> { epoch_manager: Arc, - shard_tracker: ShardTracker, runtime_adapter: Arc, chain_store_update: ChainStoreUpdate<'a>, doomslug_threshold_mode: DoomslugThresholdMode, @@ -53,7 +49,6 @@ impl<'a> ChainUpdate<'a> { pub fn new( chain_store: &'a mut ChainStore, epoch_manager: Arc, - shard_tracker: ShardTracker, runtime_adapter: Arc, doomslug_threshold_mode: DoomslugThresholdMode, transaction_validity_period: BlockHeightDelta, @@ -61,7 +56,6 @@ impl<'a> ChainUpdate<'a> { let chain_store_update: ChainStoreUpdate<'_> = chain_store.store_update(); Self::new_impl( epoch_manager, - shard_tracker, runtime_adapter, doomslug_threshold_mode, transaction_validity_period, @@ -71,7 +65,6 @@ impl<'a> ChainUpdate<'a> { fn new_impl( epoch_manager: Arc, - shard_tracker: ShardTracker, runtime_adapter: Arc, doomslug_threshold_mode: DoomslugThresholdMode, transaction_validity_period: BlockHeightDelta, @@ -79,7 +72,6 @@ impl<'a> ChainUpdate<'a> { ) -> Self { ChainUpdate { epoch_manager, - shard_tracker, runtime_adapter, chain_store_update, doomslug_threshold_mode, @@ -92,60 +84,6 @@ impl<'a> ChainUpdate<'a> { self.chain_store_update.commit() } - /// For all the outgoing receipts generated in block `hash` at the shards we - /// are tracking in this epoch, save a mapping from receipt ids to the - /// destination shard ids that the receipt will be sent to in the next - /// block. - /// - /// Note that this function should be called after `save_block` is called on - /// this block because it requires that the block info is available in - /// EpochManager, otherwise it will return an error. - fn save_receipt_id_to_shard_id_for_block( - &mut self, - account_id: Option<&AccountId>, - hash: &CryptoHash, - prev_hash: &CryptoHash, - shard_ids: &[ShardId], - ) -> Result<(), Error> { - let mut list = vec![]; - for &shard_id in shard_ids { - if self.shard_tracker.care_about_shard(account_id, prev_hash, shard_id, true) { - list.push(self.get_receipt_id_to_shard_id(hash, shard_id)?); - } - } - for map in list { - for (receipt_id, shard_id) in map { - self.chain_store_update.save_receipt_id_to_shard_id(receipt_id, shard_id); - } - } - Ok(()) - } - - /// Returns a mapping from the receipt id to the destination shard id. - fn get_receipt_id_to_shard_id( - &mut self, - hash: &CryptoHash, - shard_id: u64, - ) -> Result, Error> { - let outgoing_receipts = self.chain_store_update.get_outgoing_receipts(hash, shard_id); - let outgoing_receipts = if let Ok(outgoing_receipts) = outgoing_receipts { - outgoing_receipts - } else { - return Ok(HashMap::new()); - }; - let shard_layout = self.epoch_manager.get_shard_layout_from_prev_block(hash)?; - let outgoing_receipts = outgoing_receipts - .iter() - .map(|receipt| { - ( - *receipt.receipt_id(), - account_id_to_shard_id(receipt.receiver_id(), &shard_layout), - ) - }) - .collect(); - Ok(outgoing_receipts) - } - pub(crate) fn apply_chunk_postprocessing( &mut self, block: &Block, @@ -288,7 +226,17 @@ impl<'a> ChainUpdate<'a> { result.shard_uid, result.trie_changes.state_changes(), )?; - flat_storage_manager.update_flat_storage_for_shard(result.shard_uid, block)?; + let last_final_block_hash = *block.header().last_final_block(); + if last_final_block_hash != CryptoHash::default() { + // TODO(#11600): this seems good enough to create a snapshot + // for the new shard when resharding finishes, because there + // is no concept of missing chunk. However, testing is required. + flat_storage_manager.update_flat_storage_for_shard( + result.shard_uid, + last_final_block_hash, + )?; + } + self.chain_store_update.merge(store_update); self.chain_store_update.save_chunk_extra( @@ -446,13 +394,11 @@ impl<'a> ChainUpdate<'a> { )] pub(crate) fn postprocess_block( &mut self, - me: &Option, block: &Block, block_preprocess_info: BlockPreprocessInfo, apply_chunks_results: Vec<(ShardId, Result)>, should_save_state_transition_data: bool, ) -> Result, Error> { - let shard_ids = self.epoch_manager.shard_ids(block.header().epoch_id())?; let prev_hash = block.header().prev_hash(); let results = apply_chunks_results.into_iter().map(|(shard_id, x)| { if let Err(err) = &x { @@ -518,14 +464,6 @@ impl<'a> ChainUpdate<'a> { self.chain_store_update.save_block(block.clone()); self.chain_store_update.inc_block_refcount(prev_hash)?; - // Save receipt_id_to_shard_id for all outgoing receipts generated in this block - self.save_receipt_id_to_shard_id_for_block( - me.as_ref(), - block.hash(), - prev_hash, - &shard_ids, - )?; - // Update the chain head if it's the new tip let res = self.update_head(block.header())?; @@ -540,7 +478,7 @@ impl<'a> ChainUpdate<'a> { // is also just height, so the very first block to cross the epoch end is guaranteed // to be the head of the chain, and result in the light client block produced. let prev = self.chain_store_update.get_previous_header(block.header())?; - let prev_epoch_id = prev.epoch_id().clone(); + let prev_epoch_id = *prev.epoch_id(); if block.header().epoch_id() != &prev_epoch_id { if prev.last_final_block() != &CryptoHash::default() { let light_client_block = self.create_light_client_block(&prev)?; @@ -764,7 +702,7 @@ impl<'a> ChainUpdate<'a> { // TODO(nikurt): Determine the value correctly. let is_first_block_with_chunk_of_version = false; - let prev_block = self.chain_store_update.get_block(block_header.prev_hash())?; + let block = self.chain_store_update.get_block(block_header.hash())?; let apply_result = self.runtime_adapter.apply_chunk( RuntimeStorageConfig::new(chunk_header.prev_state_root(), true), @@ -784,7 +722,7 @@ impl<'a> ChainUpdate<'a> { gas_price, challenges_result: block_header.challenges_result().clone(), random_seed: *block_header.random_value(), - congestion_info: prev_block.shards_congestion_info(), + congestion_info: block.block_congestion_info(), }, &receipts, chunk.transactions(), @@ -869,8 +807,9 @@ impl<'a> ChainUpdate<'a> { // Don't continue return Ok(false); } + let block = self.chain_store_update.get_block(block_header.hash())?; + let prev_hash = block_header.prev_hash(); - let prev_block = self.chain_store_update.get_block(prev_hash)?; let prev_block_header = self.chain_store_update.get_block_header(prev_hash)?; let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, block_header.epoch_id())?; @@ -889,7 +828,7 @@ impl<'a> ChainUpdate<'a> { ApplyChunkBlockContext::from_header( &block_header, prev_block_header.next_gas_price(), - prev_block.shards_congestion_info(), + block.block_congestion_info(), ), &[], &[], diff --git a/chain/chain/src/crypto_hash_timer.rs b/chain/chain/src/crypto_hash_timer.rs index 97b5a074d73..eb75d2a5e3d 100644 --- a/chain/chain/src/crypto_hash_timer.rs +++ b/chain/chain/src/crypto_hash_timer.rs @@ -2,12 +2,13 @@ use lru::LruCache; use near_async::time::{Clock, Duration, Instant}; use near_primitives::hash::CryptoHash; use once_cell::sync::Lazy; +use std::num::NonZeroUsize; use std::sync::Mutex; // Cache with the mapping from CryptoHash (blocks, chunks) to the number milliseconds that it took to process them. // Used only for debugging purposes. static CRYPTO_HASH_TIMER_RESULTS: Lazy>> = - Lazy::new(|| Mutex::new(LruCache::new(10000))); + Lazy::new(|| Mutex::new(LruCache::new(NonZeroUsize::new(10000).unwrap()))); /// Struct to measure computation times related to different CryptoHashes (for example chunk or block computations). /// It stores the data in the global LRU cache, which allows it to be read afterwards. diff --git a/chain/chain/src/doomslug.rs b/chain/chain/src/doomslug.rs index d65a48578b1..cd922710e3f 100644 --- a/chain/chain/src/doomslug.rs +++ b/chain/chain/src/doomslug.rs @@ -138,7 +138,6 @@ pub struct Doomslug { endorsement_pending: bool, /// Information to track the timer (see `start_timer` routine in the paper) timer: DoomslugTimer, - signer: Option>, /// How many approvals to have before producing a block. In production should be always `HalfStake`, /// but for many tests we use `NoApprovals` to invoke more forkfulness threshold_mode: DoomslugThresholdMode, @@ -362,7 +361,6 @@ impl Doomslug { min_delay: Duration, delay_step: Duration, max_delay: Duration, - signer: Option>, threshold_mode: DoomslugThresholdMode, ) -> Self { Doomslug { @@ -392,7 +390,6 @@ impl Doomslug { delay_step, max_delay, }, - signer, threshold_mode, history: VecDeque::new(), } @@ -465,7 +462,7 @@ impl Doomslug { /// A vector of approvals that need to be sent to other block producers as a result of processing /// the timers #[must_use] - pub fn process_timer(&mut self) -> Vec { + pub fn process_timer(&mut self, signer: &Option>) -> Vec { let now = self.clock.now(); let mut ret = vec![]; for _ in 0..MAX_TIMER_ITERS { @@ -486,7 +483,7 @@ impl Doomslug { if tip_height >= self.largest_target_height.get() { self.largest_target_height.set(tip_height + 1); - if let Some(approval) = self.create_approval(tip_height + 1) { + if let Some(approval) = self.create_approval(tip_height + 1, signer) { ret.push(approval); } self.update_history(ApprovalHistoryEntry { @@ -514,7 +511,7 @@ impl Doomslug { self.largest_target_height .set(std::cmp::max(self.timer.height + 1, self.largest_target_height.get())); - if let Some(approval) = self.create_approval(self.timer.height + 1) { + if let Some(approval) = self.create_approval(self.timer.height + 1, signer) { ret.push(approval); } self.update_history(ApprovalHistoryEntry { @@ -541,9 +538,13 @@ impl Doomslug { ret } - fn create_approval(&self, target_height: BlockHeight) -> Option { - self.signer.as_ref().map(|signer| { - Approval::new(self.tip.block_hash, self.tip.height, target_height, &**signer) + fn create_approval( + &self, + target_height: BlockHeight, + signer: &Option>, + ) -> Option { + signer.as_ref().map(|signer| { + Approval::new(self.tip.block_hash, self.tip.height, target_height, &*signer) }) } @@ -787,6 +788,7 @@ mod tests { #[test] fn test_endorsements_and_skips_basic() { let clock = FakeClock::new(Utc::UNIX_EPOCH); + let signer = Some(Arc::new(create_test_signer("test").into())); let mut ds = Doomslug::new( clock.clock(), 0, @@ -794,29 +796,28 @@ mod tests { Duration::milliseconds(1000), Duration::milliseconds(100), Duration::milliseconds(3000), - Some(Arc::new(create_test_signer("test"))), DoomslugThresholdMode::TwoThirds, ); // Set a new tip, must produce an endorsement ds.set_tip(hash(&[1]), 1, 1); clock.advance(Duration::milliseconds(399)); - assert_eq!(ds.process_timer().len(), 0); + assert_eq!(ds.process_timer(&signer).len(), 0); clock.advance(Duration::milliseconds(1)); - let approval = ds.process_timer().into_iter().nth(0).unwrap(); + let approval = ds.process_timer(&signer).into_iter().nth(0).unwrap(); assert_eq!(approval.inner, ApprovalInner::Endorsement(hash(&[1]))); assert_eq!(approval.target_height, 2); // Same tip => no approval - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); // The block was `ds_final` and therefore started the timer. Try checking before one second expires clock.advance(Duration::milliseconds(599)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); // But one second should trigger the skip clock.advance(Duration::milliseconds(1)); - match ds.process_timer() { + match ds.process_timer(&signer) { approvals if approvals.is_empty() => assert!(false), approvals => { assert_eq!(approvals[0].inner, ApprovalInner::Skip(1)); @@ -827,7 +828,7 @@ mod tests { // Not processing a block at height 2 should not produce an appoval ds.set_tip(hash(&[2]), 2, 0); clock.advance(Duration::milliseconds(400)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); // Go forward more so we have 1 second clock.advance(Duration::milliseconds(600)); @@ -835,7 +836,7 @@ mod tests { // But at height 3 should (also neither block has finality set, keep last final at 0 for now) ds.set_tip(hash(&[3]), 3, 0); clock.advance(Duration::milliseconds(400)); - let approval = ds.process_timer().into_iter().nth(0).unwrap(); + let approval = ds.process_timer(&signer).into_iter().nth(0).unwrap(); assert_eq!(approval.inner, ApprovalInner::Endorsement(hash(&[3]))); assert_eq!(approval.target_height, 4); @@ -843,10 +844,10 @@ mod tests { clock.advance(Duration::milliseconds(600)); clock.advance(Duration::milliseconds(199)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); clock.advance(Duration::milliseconds(1)); - match ds.process_timer() { + match ds.process_timer(&signer) { approvals if approvals.is_empty() => assert!(false), approvals if approvals.len() == 1 => { assert_eq!(approvals[0].inner, ApprovalInner::Skip(3)); @@ -860,10 +861,10 @@ mod tests { // Now skip 5 (the extra delay is 200+300 = 500) clock.advance(Duration::milliseconds(499)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); clock.advance(Duration::milliseconds(1)); - match ds.process_timer() { + match ds.process_timer(&signer) { approvals if approvals.is_empty() => assert!(false), approvals => { assert_eq!(approvals[0].inner, ApprovalInner::Skip(3)); @@ -876,10 +877,10 @@ mod tests { // Skip 6 (the extra delay is 0+200+300+400 = 900) clock.advance(Duration::milliseconds(899)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); clock.advance(Duration::milliseconds(1)); - match ds.process_timer() { + match ds.process_timer(&signer) { approvals if approvals.is_empty() => assert!(false), approvals => { assert_eq!(approvals[0].inner, ApprovalInner::Skip(3)); @@ -893,11 +894,11 @@ mod tests { // Accept block at 5 with finality on the prev block, expect it to not produce an approval ds.set_tip(hash(&[5]), 5, 4); clock.advance(Duration::milliseconds(400)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); // Skip a whole bunch of heights by moving 100 seconds ahead clock.advance(Duration::seconds(100)); - assert!(ds.process_timer().len() > 10); + assert!(ds.process_timer(&signer).len() > 10); // Add some random small number of milliseconds to test that when the next block is added, the // timer is reset @@ -906,15 +907,15 @@ mod tests { // No approval, since we skipped 6 ds.set_tip(hash(&[6]), 6, 4); clock.advance(Duration::milliseconds(400)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); // The block height was less than the timer height, and thus the timer was reset. // The wait time for height 7 with last ds final block at 5 is 1100 clock.advance(Duration::milliseconds(699)); - assert_eq!(ds.process_timer(), vec![]); + assert_eq!(ds.process_timer(&signer), vec![]); clock.advance(Duration::milliseconds(1)); - match ds.process_timer() { + match ds.process_timer(&signer) { approvals if approvals.is_empty() => assert!(false), approvals => { assert_eq!(approvals[0].inner, ApprovalInner::Skip(6)); @@ -942,7 +943,6 @@ mod tests { .map(|(account_id, _, _)| create_test_signer(account_id)) .collect::>(); - let signer = Arc::new(create_test_signer("test")); let clock = FakeClock::new(Utc::UNIX_EPOCH); let mut ds = Doomslug::new( clock.clock(), @@ -951,7 +951,6 @@ mod tests { Duration::milliseconds(1000), Duration::milliseconds(100), Duration::milliseconds(3000), - Some(signer), DoomslugThresholdMode::TwoThirds, ); diff --git a/chain/chain/src/flat_storage_creator.rs b/chain/chain/src/flat_storage_creator.rs index 60fa21e6ed9..3cba5455dd4 100644 --- a/chain/chain/src/flat_storage_creator.rs +++ b/chain/chain/src/flat_storage_creator.rs @@ -28,7 +28,6 @@ use near_store::flat::{ use near_store::Store; use near_store::{Trie, TrieDBStorage, TrieTraversalItem}; use std::collections::HashMap; -use std::rc::Rc; use std::sync::atomic::AtomicU64; use std::sync::Arc; use tracing::{debug, info}; @@ -97,7 +96,7 @@ impl FlatStorageShardCreator { result_sender: Sender, ) { let trie_storage = TrieDBStorage::new(store.clone(), shard_uid); - let trie = Trie::new(Rc::new(trie_storage), state_root, None); + let trie = Trie::new(Arc::new(trie_storage), state_root, None); let path_begin = trie.find_state_part_boundary(part_id.idx, part_id.total).unwrap(); let path_end = trie.find_state_part_boundary(part_id.idx + 1, part_id.total).unwrap(); let hex_path_begin = Self::nibbles_to_hex(&path_begin); @@ -199,7 +198,7 @@ impl FlatStorageShardCreator { let trie_storage = TrieDBStorage::new(store, shard_uid); let state_root = *chain_store.get_chunk_extra(&block_hash, &shard_uid)?.state_root(); - let trie = Trie::new(Rc::new(trie_storage), state_root, None); + let trie = Trie::new(Arc::new(trie_storage), state_root, None); let root_node = trie.retrieve_root_node().unwrap(); let num_state_parts = root_node.memory_usage / STATE_PART_MEMORY_LIMIT.as_u64() + 1; diff --git a/chain/chain/src/garbage_collection.rs b/chain/chain/src/garbage_collection.rs index 0bf08500947..acefa3d7f52 100644 --- a/chain/chain/src/garbage_collection.rs +++ b/chain/chain/src/garbage_collection.rs @@ -146,7 +146,7 @@ impl ChainStore { if gc_stop_height > head.height { return Err(Error::GCError("gc_stop_height cannot be larger than head.height".into())); } - let prev_epoch_id = self.get_block_header(&head.prev_block_hash)?.epoch_id().clone(); + let prev_epoch_id = *self.get_block_header(&head.prev_block_hash)?.epoch_id(); let epoch_change = prev_epoch_id != head.epoch_id; let mut fork_tail = self.fork_tail()?; metrics::TAIL_HEIGHT.set(tail as i64); @@ -876,29 +876,6 @@ impl<'a> ChainStoreUpdate<'a> { fn gc_outgoing_receipts(&mut self, block_hash: &CryptoHash, shard_id: ShardId) { let mut store_update = self.store().store_update(); - match self.get_outgoing_receipts(block_hash, shard_id).map(|receipts| { - receipts.iter().map(|receipt| *receipt.receipt_id()).collect::>() - }) { - Ok(receipt_ids) => { - for receipt_id in receipt_ids { - let key: Vec = receipt_id.into(); - store_update.decrement_refcount(DBCol::ReceiptIdToShardId, &key); - self.chain_store().receipt_id_to_shard_id.pop(&key); - } - } - Err(error) => { - match error { - Error::DBNotFoundErr(_) => { - // Sometimes we don't save outgoing receipts. See the usages of save_outgoing_receipt. - // The invariant is that DBCol::OutgoingReceipts has same receipts as DBCol::ReceiptIdToShardId. - } - _ => { - tracing::error!(target: "chain", "Error getting outgoing receipts for block {}, shard {}: {:?}", block_hash, shard_id, error); - } - } - } - } - let key = get_block_shard_id(block_hash, shard_id); store_update.delete(DBCol::OutgoingReceipts, &key); self.chain_store().outgoing_receipts.pop(&key); @@ -930,7 +907,7 @@ impl<'a> ChainStoreUpdate<'a> { let mut store_update = self.store().store_update(); match col { DBCol::OutgoingReceipts => { - panic!("Must use gc_outgoing_receipts"); + panic!("Outgoing receipts must be garbage collected by calling gc_outgoing_receipts"); } DBCol::IncomingReceipts => { store_update.delete(col, key); @@ -973,9 +950,6 @@ impl<'a> ChainStoreUpdate<'a> { store_update.delete(col, key); self.chain_store().block_refcounts.pop(key); } - DBCol::ReceiptIdToShardId => { - panic!("Must use gc_outgoing_receipts"); - } DBCol::Transactions => { store_update.decrement_refcount(col, key); self.chain_store().transactions.pop(key); @@ -1072,6 +1046,7 @@ impl<'a> ChainStoreUpdate<'a> { | DBCol::FlatStateDeltaMetadata | DBCol::FlatStorageStatus | DBCol::Misc + | DBCol::_ReceiptIdToShardId => unreachable!(), #[cfg(feature = "new_epoch_sync")] DBCol::EpochSyncInfo => unreachable!(), diff --git a/chain/chain/src/lib.rs b/chain/chain/src/lib.rs index e84647baa25..e647d21fc29 100644 --- a/chain/chain/src/lib.rs +++ b/chain/chain/src/lib.rs @@ -19,7 +19,7 @@ mod doomslug; pub mod flat_storage_creator; mod garbage_collection; mod lightclient; -mod metrics; +pub mod metrics; pub mod migrations; pub mod missing_chunks; pub mod orphan; @@ -27,7 +27,7 @@ pub mod resharding; pub mod runtime; mod state_request_tracker; pub mod state_snapshot_actor; -mod stateless_validation; +pub mod stateless_validation; mod store; pub mod store_validator; pub mod test_utils; diff --git a/chain/chain/src/metrics.rs b/chain/chain/src/metrics.rs index 5eeb9d7ae78..f5922188fbe 100644 --- a/chain/chain/src/metrics.rs +++ b/chain/chain/src/metrics.rs @@ -238,36 +238,3 @@ pub(crate) static RESHARDING_STATUS: Lazy = Lazy::new(|| { ) .unwrap() }); - -pub static SAVE_LATEST_WITNESS_GENERATE_UPDATE_TIME: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_save_latest_witness_generate_update_time", - "Time taken to generate an update of latest witnesses", - &["shard_id"], - Some(exponential_buckets(0.001, 1.6, 20).unwrap()), - ) - .unwrap() -}); -pub static SAVE_LATEST_WITNESS_COMMIT_UPDATE_TIME: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_save_latest_witness_commit_update_time", - "Time taken to commit the update of latest witnesses", - &["shard_id"], - Some(exponential_buckets(0.001, 1.6, 20).unwrap()), - ) - .unwrap() -}); -pub static SAVED_LATEST_WITNESSES_COUNT: Lazy = Lazy::new(|| { - try_create_int_gauge( - "near_saved_latest_witnesses_count", - "Total number of saved latest witnesses", - ) - .unwrap() -}); -pub static SAVED_LATEST_WITNESSES_SIZE: Lazy = Lazy::new(|| { - try_create_int_gauge( - "near_saved_latest_witnesses_size", - "Total size of saved latest witnesses (in bytes)", - ) - .unwrap() -}); diff --git a/chain/chain/src/runtime/mod.rs b/chain/chain/src/runtime/mod.rs index 1d292538219..49eb8794c05 100644 --- a/chain/chain/src/runtime/mod.rs +++ b/chain/chain/src/runtime/mod.rs @@ -17,7 +17,9 @@ use near_pool::types::TransactionGroupIterator; use near_primitives::account::{AccessKey, Account}; use near_primitives::apply::ApplyChunkReason; use near_primitives::checked_feature; -use near_primitives::congestion_info::{CongestionControl, ExtendedCongestionInfo}; +use near_primitives::congestion_info::{ + CongestionControl, ExtendedCongestionInfo, RejectTransactionReason, ShardAcceptsTransactions, +}; use near_primitives::errors::{InvalidTxError, RuntimeError, StorageError}; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::receipt::{DelayedReceiptIndices, Receipt}; @@ -36,7 +38,7 @@ use near_primitives::types::{ use near_primitives::version::{ProtocolFeature, ProtocolVersion}; use near_primitives::views::{ AccessKeyInfoView, CallResult, ContractCodeView, QueryRequest, QueryResponse, - QueryResponseKind, ViewApplyState, ViewStateResult, + QueryResponseKind, ViewStateResult, }; use near_store::config::StateSnapshotType; use near_store::flat::FlatStorageManager; @@ -48,7 +50,7 @@ use near_store::{ use near_vm_runner::ContractCode; use near_vm_runner::{precompile_contract, ContractRuntimeCache, FilesystemContractRuntimeCache}; use node_runtime::adapter::ViewRuntimeAdapter; -use node_runtime::state_viewer::TrieViewer; +use node_runtime::state_viewer::{TrieViewer, ViewApplyState}; use node_runtime::{ validate_transaction, verify_and_charge_transaction, ApplyState, Runtime, ValidatorAccountsUpdate, @@ -167,6 +169,7 @@ impl NightshadeRuntime { compiled_contract_cache: Box, genesis_config: &GenesisConfig, epoch_manager: Arc, + runtime_config_store: Option, trie_config: TrieConfig, state_snapshot_type: StateSnapshotType, ) -> Arc { @@ -177,7 +180,7 @@ impl NightshadeRuntime { epoch_manager, None, None, - None, + runtime_config_store, DEFAULT_GC_NUM_EPOCHS_TO_KEEP, trie_config, StateSnapshotConfig { @@ -407,8 +410,8 @@ impl NightshadeRuntime { // TODO(#2152): process gracefully RuntimeError::BalanceMismatchError(e) => panic!("{}", e), // TODO(#2152): process gracefully - RuntimeError::UnexpectedIntegerOverflow => { - panic!("RuntimeError::UnexpectedIntegerOverflow") + RuntimeError::UnexpectedIntegerOverflow(reason) => { + panic!("RuntimeError::UnexpectedIntegerOverflow {reason}") } RuntimeError::StorageError(e) => Error::StorageError(e), // TODO(#2152): process gracefully @@ -494,7 +497,12 @@ impl NightshadeRuntime { let contract_cache = compiled_contract_cache.as_deref(); let slot_sender = slot_sender.clone(); scope.spawn(move |_| { - precompile_contract(&code, &runtime_config.wasm_config, contract_cache).ok(); + precompile_contract( + &code, + Arc::clone(&runtime_config.wasm_config), + contract_cache, + ) + .ok(); // If this fails, it just means there won't be any more attempts to recv the // slots let _ = slot_sender.send(()); @@ -643,9 +651,36 @@ impl RuntimeAdapter for NightshadeRuntime { verify_signature: bool, epoch_id: &EpochId, current_protocol_version: ProtocolVersion, + receiver_congestion_info: Option, ) -> Result, Error> { let runtime_config = self.runtime_config_store.get_config(current_protocol_version); + if let Some(congestion_info) = receiver_congestion_info { + let congestion_control = CongestionControl::new( + runtime_config.congestion_control_config, + congestion_info.congestion_info, + congestion_info.missed_chunks_count, + ); + if let ShardAcceptsTransactions::No(reason) = + congestion_control.shard_accepts_transactions() + { + let receiver_shard = + self.account_id_to_shard_uid(transaction.transaction.receiver_id(), epoch_id)?; + let shard_id = receiver_shard.shard_id; + let err = match reason { + RejectTransactionReason::IncomingCongestion { congestion_level } + | RejectTransactionReason::OutgoingCongestion { congestion_level } + | RejectTransactionReason::MemoryCongestion { congestion_level } => { + InvalidTxError::ShardCongested { shard_id, congestion_level } + } + RejectTransactionReason::MissedChunks { missed_chunks } => { + InvalidTxError::ShardStuck { shard_id, missed_chunks } + } + }; + return Ok(Some(err)); + } + } + if let Some(state_root) = state_root { let shard_uid = self.account_id_to_shard_uid(transaction.transaction.signer_id(), epoch_id)?; @@ -663,12 +698,7 @@ impl RuntimeAdapter for NightshadeRuntime { current_protocol_version, ) { Ok(_) => Ok(None), - Err(RuntimeError::InvalidTxError(err)) => { - debug!(target: "runtime", "Tx {:?} validation failed: {:?}", transaction, err); - Ok(Some(err)) - } - Err(RuntimeError::StorageError(err)) => Err(Error::StorageError(err)), - Err(err) => unreachable!("Unexpected RuntimeError error {:?}", err), + Err(e) => Ok(Some(e)), } } else { // Doing basic validation without a state root @@ -680,12 +710,7 @@ impl RuntimeAdapter for NightshadeRuntime { current_protocol_version, ) { Ok(_) => Ok(None), - Err(RuntimeError::InvalidTxError(err)) => { - debug!(target: "runtime", "Tx {:?} validation failed: {:?}", transaction, err); - Ok(Some(err)) - } - Err(RuntimeError::StorageError(err)) => Err(Error::StorageError(err)), - Err(err) => unreachable!("Unexpected RuntimeError error {:?}", err), + Err(e) => Ok(Some(e)), } } } @@ -762,20 +787,8 @@ impl RuntimeAdapter for NightshadeRuntime { let delayed_receipts_indices: DelayedReceiptIndices = near_store::get(&state_update, &TrieKey::DelayedReceiptIndices)?.unwrap_or_default(); let min_fee = runtime_config.fees.fee(ActionCosts::new_action_receipt).exec_fee(); - let new_receipt_count_limit = if min_fee > 0 { - // Round up to include at least one receipt. - let max_processed_receipts_in_chunk = (gas_limit + min_fee - 1) / min_fee; - // Allow at most 2 chunks worth of delayed receipts. This way under congestion, - // after processing a single chunk, we will still have at least 1 chunk worth of - // delayed receipts, ensuring the high throughput even if the next chunk producer - // does not include any receipts. - // This buffer size is a trade-off between the max queue size and system efficiency - // under congestion. - let delayed_receipt_count_limit = max_processed_receipts_in_chunk * 2; - delayed_receipt_count_limit.saturating_sub(delayed_receipts_indices.len()) as usize - } else { - usize::MAX - }; + let new_receipt_count_limit = + get_new_receipt_count_limit(min_fee, gas_limit, delayed_receipts_indices); let size_limit: u64 = calculate_transactions_size_limit( protocol_version, @@ -799,10 +812,11 @@ impl RuntimeAdapter for NightshadeRuntime { break; } if !ProtocolFeature::CongestionControl.enabled(protocol_version) { + // Local Congestion Control. // Keep this for the upgrade phase, afterwards it can be // removed. It does not need to be kept because it does not // affect replayability. - // TODO: remove at release CongestionControl + 1 or later + // TODO(congestion_control): remove at release CongestionControl + 1 or later if result.transactions.len() >= new_receipt_count_limit { result.limited_by = Some(PrepareTransactionsLimit::ReceiptCount); break; @@ -836,31 +850,26 @@ impl RuntimeAdapter for NightshadeRuntime { break 'add_txs_loop; } - // Take the transaction out of the pool + // Take the transaction out of the pool. Please take note that + // the transaction may still be rejected in which case it will + // not be returned to the pool. Most notably this may happen + // under congestion. let tx = transaction_group_iter .next() .expect("peek_next() returned Some, so next() should return Some as well"); num_checked_transactions += 1; - if ProtocolFeature::CongestionControl.enabled(protocol_version) { - let receiving_shard = EpochManagerAdapter::account_id_to_shard_id( - self.epoch_manager.as_ref(), - tx.transaction.receiver_id(), - &epoch_id, - )?; - if let Some(congestion_info) = prev_block.congestion_info.get(&receiving_shard) - { - let congestion_control = CongestionControl::new( - runtime_config.congestion_control_config, - congestion_info.congestion_info, - congestion_info.missed_chunks_count, - ); - if !congestion_control.shard_accepts_transactions() { - tracing::trace!(target: "runtime", tx=?tx.get_hash(), "discarding transaction due to congestion"); - rejected_due_to_congestion += 1; - continue; - } - } + if !congestion_control_accepts_transaction( + self.epoch_manager.as_ref(), + protocol_version, + &runtime_config, + &epoch_id, + &prev_block, + &tx, + )? { + tracing::trace!(target: "runtime", tx=?tx.get_hash(), "discarding transaction due to congestion"); + rejected_due_to_congestion += 1; + continue; } // Verifying the transaction is on the same chain and hasn't expired yet. @@ -889,16 +898,11 @@ impl RuntimeAdapter for NightshadeRuntime { // Take one transaction from this group, no more. break; } - Err(RuntimeError::InvalidTxError(err)) => { + Err(err) => { tracing::trace!(target: "runtime", tx=?tx.get_hash(), ?err, "discarding transaction that is invalid"); rejected_invalid_tx += 1; state_update.rollback(); } - Err(RuntimeError::StorageError(err)) => { - tracing::trace!(target: "runtime", tx=?tx.get_hash(), ?err, "discarding transaction due to storage error"); - return Err(Error::StorageError(err)); - } - Err(err) => unreachable!("Unexpected RuntimeError error {:?}", err), } } } @@ -1296,6 +1300,14 @@ impl RuntimeAdapter for NightshadeRuntime { } } + fn get_runtime_config( + &self, + protocol_version: ProtocolVersion, + ) -> Result { + let runtime_config = self.runtime_config_store.get_config(protocol_version); + Ok(runtime_config.as_ref().clone()) + } + fn get_protocol_config(&self, epoch_id: &EpochId) -> Result { let protocol_version = self.epoch_manager.get_epoch_protocol_version(epoch_id)?; let mut genesis_config = self.genesis_config.clone(); @@ -1312,6 +1324,10 @@ impl RuntimeAdapter for NightshadeRuntime { epoch_config.block_producer_kickout_threshold; genesis_config.chunk_producer_kickout_threshold = epoch_config.chunk_producer_kickout_threshold; + genesis_config.chunk_validator_only_kickout_threshold = + epoch_config.chunk_validator_only_kickout_threshold; + genesis_config.target_validator_mandates_per_shard = + epoch_config.target_validator_mandates_per_shard; genesis_config.max_kickout_stake_perc = epoch_config.validator_max_kickout_stake_perc; genesis_config.online_min_threshold = epoch_config.online_min_threshold; genesis_config.online_max_threshold = epoch_config.online_max_threshold; @@ -1340,6 +1356,27 @@ impl RuntimeAdapter for NightshadeRuntime { } } +/// Get the limit on the number of new receipts imposed by the local congestion control. +fn get_new_receipt_count_limit( + min_fee: u64, + gas_limit: u64, + delayed_receipts_indices: DelayedReceiptIndices, +) -> usize { + if min_fee == 0 { + return usize::MAX; + } + // Round up to include at least one receipt. + let max_processed_receipts_in_chunk = (gas_limit + min_fee - 1) / min_fee; + // Allow at most 2 chunks worth of delayed receipts. This way under congestion, + // after processing a single chunk, we will still have at least 1 chunk worth of + // delayed receipts, ensuring the high throughput even if the next chunk producer + // does not include any receipts. + // This buffer size is a trade-off between the max queue size and system efficiency + // under congestion. + let delayed_receipt_count_limit = max_processed_receipts_in_chunk * 2; + delayed_receipt_count_limit.saturating_sub(delayed_receipts_indices.len()) as usize +} + /// How much gas of the next chunk we want to spend on converting new /// transactions to receipts. fn chunk_tx_gas_limit( @@ -1349,28 +1386,22 @@ fn chunk_tx_gas_limit( shard_id: u64, gas_limit: u64, ) -> u64 { - if ProtocolFeature::CongestionControl.enabled(protocol_version) { - if let Some(own_congestion) = prev_block.congestion_info.get(&shard_id) { - let congestion_control = CongestionControl::new( - runtime_config.congestion_control_config, - own_congestion.congestion_info, - own_congestion.missed_chunks_count, - ); - congestion_control.process_tx_limit() - } else { - // When a new shard is created, or when the feature is just being enabled. - // Using the default (no congestion) is a reasonable choice in this case. - let own_congestion = ExtendedCongestionInfo::default(); - let congestion_control = CongestionControl::new( - runtime_config.congestion_control_config, - own_congestion.congestion_info, - own_congestion.missed_chunks_count, - ); - congestion_control.process_tx_limit() - } - } else { - gas_limit / 2 + if !ProtocolFeature::CongestionControl.enabled(protocol_version) { + return gas_limit / 2; } + + // The own congestion may be None when a new shard is created, or when the + // feature is just being enabled. Using the default (no congestion) is a + // reasonable choice in this case. + let own_congestion = prev_block.congestion_info.get(&shard_id).cloned(); + let own_congestion = own_congestion.unwrap_or_default(); + + let congestion_control = CongestionControl::new( + runtime_config.congestion_control_config, + own_congestion.congestion_info, + own_congestion.missed_chunks_count, + ); + congestion_control.process_tx_limit() } fn calculate_transactions_size_limit( @@ -1396,13 +1427,45 @@ fn calculate_transactions_size_limit( // cost of roundtripping a byte of data through disk. For today's value // of parameters, this corresponds to about 13megs worth of // transactions. - let roundtripping_cost = - runtime_config.wasm_config.ext_costs.gas_cost(ExtCosts::storage_write_value_byte) - + runtime_config.wasm_config.ext_costs.gas_cost(ExtCosts::storage_read_value_byte); + let ext_costs_config = &runtime_config.wasm_config.ext_costs; + let write_cost = ext_costs_config.gas_cost(ExtCosts::storage_write_value_byte); + let read_cost = ext_costs_config.gas_cost(ExtCosts::storage_read_value_byte); + let roundtripping_cost = write_cost + read_cost; transactions_gas_limit / roundtripping_cost } } +/// Returns true if the transaction passes the congestion control checks. The +/// transaction will be accepted if the receiving shard is not congested or its +/// congestion level is below the threshold. +fn congestion_control_accepts_transaction( + epoch_manager: &dyn EpochManagerAdapter, + protocol_version: ProtocolVersion, + runtime_config: &RuntimeConfig, + epoch_id: &EpochId, + prev_block: &PrepareTransactionsBlockContext, + tx: &SignedTransaction, +) -> Result { + if !ProtocolFeature::CongestionControl.enabled(protocol_version) { + return Ok(true); + } + let receiver_id = tx.transaction.receiver_id(); + let receiving_shard = epoch_manager.account_id_to_shard_id(receiver_id, &epoch_id)?; + + let congestion_info = prev_block.congestion_info.get(&receiving_shard); + let Some(congestion_info) = congestion_info else { + return Ok(true); + }; + + let congestion_control = CongestionControl::new( + runtime_config.congestion_control_config, + congestion_info.congestion_info, + congestion_info.missed_chunks_count, + ); + let shard_accepts_transactions = congestion_control.shard_accepts_transactions(); + Ok(shard_accepts_transactions.is_yes()) +} + impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { fn view_account( &self, @@ -1447,11 +1510,11 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { block_height: height, prev_block_hash: *prev_block_hash, block_hash: *block_hash, - epoch_id: epoch_id.clone(), + epoch_id: *epoch_id, epoch_height, block_timestamp, current_protocol_version, - cache: Some(Box::new(self.compiled_contract_cache.handle())), + cache: Some(self.compiled_contract_cache.handle()), }; self.trie_viewer.call_function( state_update, diff --git a/chain/chain/src/runtime/tests.rs b/chain/chain/src/runtime/tests.rs index 6fe397e41d2..776ef292840 100644 --- a/chain/chain/src/runtime/tests.rs +++ b/chain/chain/src/runtime/tests.rs @@ -9,9 +9,11 @@ use near_epoch_manager::{EpochManager, RngSeed}; use near_pool::{ InsertTransactionResult, PoolIteratorWrapper, TransactionGroupIteratorWrapper, TransactionPool, }; +use near_primitives::action::FunctionCallAction; use near_primitives::apply::ApplyChunkReason; use near_primitives::checked_feature; -use near_primitives::congestion_info::ExtendedCongestionInfo; +use near_primitives::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; +use near_primitives::receipt::{ActionReceipt, ReceiptV1}; use near_primitives::test_utils::create_test_signer; use near_primitives::types::validator_stake::{ValidatorStake, ValidatorStakeIter}; use near_primitives::version::PROTOCOL_VERSION; @@ -21,8 +23,8 @@ use num_rational::Ratio; use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng}; use near_chain_configs::{ - default_produce_chunk_add_transactions_time_limit, Genesis, DEFAULT_GC_NUM_EPOCHS_TO_KEEP, - NEAR_BASE, + default_produce_chunk_add_transactions_time_limit, Genesis, MutableConfigValue, + DEFAULT_GC_NUM_EPOCHS_TO_KEEP, NEAR_BASE, }; use near_crypto::{InMemorySigner, KeyType, Signer}; use near_o11y::testonly::init_test_logger; @@ -46,110 +48,6 @@ use near_async::time::Clock; use near_primitives::trie_key::TrieKey; use primitive_types::U256; -fn stake( - nonce: Nonce, - signer: &dyn Signer, - sender: &dyn ValidatorSigner, - stake: Balance, -) -> SignedTransaction { - SignedTransaction::from_actions( - nonce, - sender.validator_id().clone(), - sender.validator_id().clone(), - &*signer, - vec![Action::Stake(Box::new(StakeAction { stake, public_key: sender.public_key() }))], - // runtime does not validate block history - CryptoHash::default(), - 0, - ) -} - -impl NightshadeRuntime { - fn update( - &self, - state_root: &StateRoot, - shard_id: ShardId, - height: BlockHeight, - block_timestamp: u64, - prev_block_hash: &CryptoHash, - block_hash: &CryptoHash, - receipts: &[Receipt], - transactions: &[SignedTransaction], - last_validator_proposals: ValidatorStakeIter, - gas_price: Balance, - gas_limit: Gas, - challenges_result: &ChallengesResult, - ) -> (StateRoot, Vec, Vec) { - // TODO(congestion_control): pass down prev block info and read congestion info from there - // For now, just use default. - let epoch_id = - self.epoch_manager.get_epoch_id_from_prev_block(block_hash).unwrap_or_default(); - let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap(); - let congestion_info_map: HashMap = - if !ProtocolFeature::CongestionControl.enabled(protocol_version) { - HashMap::new() - } else { - let shard_ids = self.epoch_manager.shard_ids(&epoch_id).unwrap(); - shard_ids - .into_iter() - .map(|shard_id| (shard_id, ExtendedCongestionInfo::default())) - .collect() - }; - let mut result = self - .apply_chunk( - RuntimeStorageConfig::new(*state_root, true), - ApplyChunkReason::UpdateTrackedShard, - ApplyChunkShardContext { - shard_id, - last_validator_proposals, - gas_limit, - is_new_chunk: true, - is_first_block_with_chunk_of_version: false, - }, - ApplyChunkBlockContext { - height, - block_hash: *block_hash, - prev_block_hash: *prev_block_hash, - block_timestamp, - gas_price, - challenges_result: challenges_result.clone(), - random_seed: CryptoHash::default(), - congestion_info: congestion_info_map, - }, - receipts, - transactions, - ) - .unwrap(); - let mut store_update = self.store.store_update(); - let flat_state_changes = - FlatStateChanges::from_state_changes(&result.trie_changes.state_changes()); - result.trie_changes.insertions_into(&mut store_update); - result.trie_changes.state_changes_into(&mut store_update); - - let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, &EpochId::default()).unwrap(); - if let Some(flat_storage) = - self.get_flat_storage_manager().get_flat_storage_for_shard(shard_uid) - { - let delta = FlatStateDelta { - changes: flat_state_changes, - metadata: FlatStateDeltaMetadata { - block: near_store::flat::BlockInfo { - hash: *block_hash, - height, - prev_hash: *prev_block_hash, - }, - prev_block_with_changes: None, - }, - }; - let new_store_update = flat_storage.add_delta(delta).unwrap(); - store_update.merge(new_store_update); - } - store_update.commit().unwrap(); - - (result.new_root, result.validator_proposals, result.outgoing_receipts) - } -} - struct TestEnvConfig { epoch_length: BlockHeightDelta, has_reward: bool, @@ -206,6 +104,8 @@ impl TestEnv { genesis.config.epoch_length = config.epoch_length; genesis.config.chunk_producer_kickout_threshold = genesis.config.block_producer_kickout_threshold; + genesis.config.chunk_validator_only_kickout_threshold = + genesis.config.block_producer_kickout_threshold; if !config.has_reward { genesis.config.max_inflation_rate = Ratio::from_integer(0); } @@ -302,6 +202,111 @@ impl TestEnv { } } + pub fn apply_new_chunk( + &self, + shard_id: ShardId, + new_block_hash: CryptoHash, + transactions: &[SignedTransaction], + receipts: &[Receipt], + challenges_result: ChallengesResult, + ) -> ApplyChunkResult { + // TODO(congestion_control): pass down prev block info and read congestion info from there + // For now, just use default. + let prev_block_hash = self.head.last_block_hash; + let state_root = self.state_roots[shard_id as usize]; + let gas_limit = u64::MAX; + let height = self.head.height + 1; + let block_timestamp = 0; + let epoch_id = + self.epoch_manager.get_epoch_id_from_prev_block(&prev_block_hash).unwrap_or_default(); + let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap(); + let gas_price = self.runtime.genesis_config.min_gas_price; + let congestion_info = if !ProtocolFeature::CongestionControl.enabled(protocol_version) { + BlockCongestionInfo::default() + } else { + let shard_ids = self.epoch_manager.shard_ids(&epoch_id).unwrap(); + let shards_congestion_info = shard_ids + .into_iter() + .map(|shard_id| (shard_id, ExtendedCongestionInfo::default())) + .collect(); + BlockCongestionInfo::new(shards_congestion_info) + }; + self.runtime + .apply_chunk( + RuntimeStorageConfig::new(state_root, true), + ApplyChunkReason::UpdateTrackedShard, + ApplyChunkShardContext { + shard_id, + last_validator_proposals: ValidatorStakeIter::new( + self.last_shard_proposals.get(&shard_id).unwrap_or(&vec![]), + ), + gas_limit, + is_new_chunk: true, + is_first_block_with_chunk_of_version: false, + }, + ApplyChunkBlockContext { + height, + block_hash: new_block_hash, + prev_block_hash, + block_timestamp, + gas_price, + challenges_result, + random_seed: CryptoHash::default(), + congestion_info, + }, + receipts, + transactions, + ) + .unwrap() + } + + fn update_runtime( + &self, + shard_id: ShardId, + new_block_hash: CryptoHash, + transactions: &[SignedTransaction], + receipts: &[Receipt], + challenges_result: ChallengesResult, + ) -> (CryptoHash, Vec, Vec) { + let mut apply_result = self.apply_new_chunk( + shard_id, + new_block_hash, + transactions, + receipts, + challenges_result, + ); + let mut store_update = self.runtime.store().store_update(); + let flat_state_changes = + FlatStateChanges::from_state_changes(&apply_result.trie_changes.state_changes()); + apply_result.trie_changes.insertions_into(&mut store_update); + apply_result.trie_changes.state_changes_into(&mut store_update); + + let prev_block_hash = self.head.last_block_hash; + let epoch_id = + self.epoch_manager.get_epoch_id_from_prev_block(&prev_block_hash).unwrap_or_default(); + let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, &epoch_id).unwrap(); + if let Some(flat_storage) = + self.runtime.get_flat_storage_manager().get_flat_storage_for_shard(shard_uid) + { + let delta = FlatStateDelta { + changes: flat_state_changes, + metadata: FlatStateDeltaMetadata { + block: near_store::flat::BlockInfo { + hash: new_block_hash, + height: self.head.height + 1, + prev_hash: prev_block_hash, + }, + prev_block_with_changes: None, + }, + }; + let new_store_update = flat_storage.add_delta(delta).unwrap(); + store_update.merge(new_store_update); + } + store_update.commit().unwrap(); + + (apply_result.new_root, apply_result.validator_proposals, apply_result.outgoing_receipts) + } + pub fn step( &mut self, transactions: Vec>, @@ -315,21 +320,12 @@ impl TestEnv { let mut all_proposals = vec![]; let mut all_receipts = vec![]; for shard_id in shard_ids { - let (state_root, proposals, receipts) = self.runtime.update( - &self.state_roots[shard_id as usize], + let (state_root, proposals, receipts) = self.update_runtime( shard_id, - self.head.height + 1, - 0, - &self.head.last_block_hash, - &new_hash, - self.last_receipts.get(&shard_id).map_or(&[], |v| v.as_slice()), + new_hash, &transactions[shard_id as usize], - ValidatorStakeIter::new( - self.last_shard_proposals.get(&shard_id).unwrap_or(&vec![]), - ), - self.runtime.genesis_config.min_gas_price, - u64::max_value(), - &challenges_result, + self.last_receipts.get(&shard_id).map_or(&[], |v| v.as_slice()), + challenges_result.clone(), ); self.state_roots[shard_id as usize] = state_root; all_receipts.extend(receipts); @@ -436,13 +432,15 @@ fn test_validator_rotation() { let block_producers: Vec<_> = validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signer = - InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()); + InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()) + .into(); // test1 doubles stake and the new account stakes the same, so test2 will be kicked out.` let staking_transaction = stake(1, &signer, &block_producers[0], TESTING_INIT_STAKE * 2); let new_account = AccountId::try_from(format!("test{}", num_nodes + 1)).unwrap(); let new_validator = create_test_signer(new_account.as_str()); - let new_signer = - InMemorySigner::from_seed(new_account.clone(), KeyType::ED25519, new_account.as_ref()); + let new_signer: Signer = + InMemorySigner::from_seed(new_account.clone(), KeyType::ED25519, new_account.as_ref()) + .into(); let create_account_transaction = SignedTransaction::create_account( 2, block_producers[0].validator_id().clone(), @@ -460,7 +458,8 @@ fn test_validator_rotation() { validators[1].clone(), KeyType::ED25519, validators[1].as_ref(), - ); + ) + .into(); vec![ staking_transaction, create_account_transaction, @@ -522,7 +521,8 @@ fn test_validator_stake_change() { let block_producers: Vec<_> = validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signer = - InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()); + InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()) + .into(); let desired_stake = 2 * TESTING_INIT_STAKE / 3; let staking_transaction = stake(1, &signer, &block_producers[0], desired_stake); @@ -559,7 +559,7 @@ fn test_validator_stake_change_multiple_times() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signers: Vec<_> = validators .iter() - .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) + .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref()).into()) .collect(); let staking_transaction = stake(1, &signers[0], &block_producers[0], TESTING_INIT_STAKE - 1); @@ -654,7 +654,7 @@ fn test_stake_in_last_block_of_an_epoch() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signers: Vec<_> = validators .iter() - .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) + .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref()).into()) .collect(); let staking_transaction = stake(1, &signers[0], &block_producers[0], TESTING_INIT_STAKE + TESTING_INIT_STAKE / 6); @@ -715,7 +715,8 @@ fn test_state_sync() { let block_producers: Vec<_> = validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signer = - InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()); + InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()) + .into(); let staking_transaction = stake(1, &signer, &block_producers[0], TESTING_INIT_STAKE + 1); env.step_default(vec![staking_transaction]); env.step_default(vec![]); @@ -804,7 +805,8 @@ fn test_get_validator_info() { let block_producers: Vec<_> = validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signer = - InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()); + InMemorySigner::from_seed(validators[0].clone(), KeyType::ED25519, validators[0].as_ref()) + .into(); let staking_transaction = stake(1, &signer, &block_producers[0], 0); let mut expected_blocks = [0, 0]; let mut expected_chunks = [0, 0]; @@ -814,7 +816,7 @@ fn test_get_validator_info() { expected_blocks: &mut [u64; 2], expected_chunks: &mut [u64; 2], expected_endorsements: &mut [u64; 2]| { - let epoch_id = env.head.epoch_id.clone(); + let epoch_id = env.head.epoch_id; let height = env.head.height; let em = env.runtime.epoch_manager.read(); let bp = em.get_block_producer_info(&epoch_id, height).unwrap(); @@ -852,7 +854,7 @@ fn test_get_validator_info() { ); assert!(env .epoch_manager - .get_validator_info(ValidatorInfoIdentifier::EpochId(env.head.epoch_id.clone())) + .get_validator_info(ValidatorInfoIdentifier::EpochId(env.head.epoch_id)) .is_err()); env.step_default(vec![]); update_validator_stats( @@ -1045,7 +1047,8 @@ fn test_double_sign_challenge_not_all_slashed() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signer = - InMemorySigner::from_seed(validators[2].clone(), KeyType::ED25519, validators[2].as_ref()); + InMemorySigner::from_seed(validators[2].clone(), KeyType::ED25519, validators[2].as_ref()) + .into(); let staking_transaction = stake(1, &signer, &block_producers[2], TESTING_INIT_STAKE / 3); env.step( vec![vec![staking_transaction]], @@ -1221,7 +1224,7 @@ fn test_fishermen_stake() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signers: Vec<_> = validators .iter() - .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) + .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref()).into()) .collect(); let fishermen_stake = 3300 * NEAR_BASE + 1; @@ -1288,7 +1291,7 @@ fn test_fishermen_unstake() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signers: Vec<_> = validators .iter() - .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) + .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref()).into()) .collect(); let fishermen_stake = 3300 * NEAR_BASE + 1; @@ -1365,7 +1368,7 @@ fn test_delete_account_after_unstake() { .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) .collect(); - let staking_transaction1 = stake(1, &signers[1], &block_producers[1], 0); + let staking_transaction1 = stake(1, &signers[1].clone().into(), &block_producers[1], 0); env.step_default(vec![staking_transaction1]); let account = env.view_account(block_producers[1].validator_id()); assert_eq!(account.amount, TESTING_INIT_BALANCE - TESTING_INIT_STAKE); @@ -1373,7 +1376,7 @@ fn test_delete_account_after_unstake() { for _ in 2..=5 { env.step_default(vec![]); } - let staking_transaction2 = stake(2, &signers[1], &block_producers[1], 1); + let staking_transaction2 = stake(2, &signers[1].clone().into(), &block_producers[1], 1); env.step_default(vec![staking_transaction2]); for _ in 7..=13 { env.step_default(vec![]); @@ -1385,7 +1388,7 @@ fn test_delete_account_after_unstake() { 4, signers[1].account_id.clone(), signers[1].account_id.clone(), - &signers[1] as &dyn Signer, + &signers[1].clone().into(), vec![Action::DeleteAccount(DeleteAccountAction { beneficiary_id: signers[0].account_id.clone(), })], @@ -1410,7 +1413,7 @@ fn test_proposal_deduped() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signers: Vec<_> = validators .iter() - .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) + .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref()).into()) .collect(); let staking_transaction1 = stake(1, &signers[1], &block_producers[1], TESTING_INIT_STAKE - 100); @@ -1431,7 +1434,7 @@ fn test_insufficient_stake() { validators.iter().map(|id| create_test_signer(id.as_str())).collect(); let signers: Vec<_> = validators .iter() - .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref())) + .map(|id| InMemorySigner::from_seed(id.clone(), KeyType::ED25519, id.as_ref()).into()) .collect(); let staking_transaction1 = stake(1, &signers[1], &block_producers[1], 100); @@ -1479,7 +1482,7 @@ fn test_trie_and_flat_state_equality() { 4, signers[0].account_id.clone(), validators[1].clone(), - &signers[0] as &dyn Signer, + &signers[0].clone().into(), vec![Action::Transfer(TransferAction { deposit: 10 })], // runtime does not validate block history CryptoHash::default(), @@ -1567,7 +1570,7 @@ fn generate_transaction_pool( round.try_into().unwrap(), signers[i].account_id.clone(), signers[(i + round) % signer_count].account_id.clone(), - &signers[i] as &dyn Signer, + &signers[i].clone().into(), round.try_into().unwrap(), block_hash, ); @@ -1610,7 +1613,7 @@ fn get_test_env_with_chain_and_pool() -> (TestEnv, Chain, TransactionPool) { ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); @@ -1636,7 +1639,7 @@ fn prepare_transactions( ) -> Result { let shard_id = 0; let block = chain.get_block(&env.head.prev_block_hash).unwrap(); - let congestion_info = block.shards_congestion_info(); + let congestion_info = block.block_congestion_info(); env.runtime.prepare_transactions( storage_config, @@ -1762,3 +1765,60 @@ fn test_prepare_transactions_empty_storage_proof() { assert!(validation_result.is_err()); } + +#[test] +#[cfg_attr(not(feature = "test_features"), ignore)] +fn test_storage_proof_garbage() { + if !checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { + return; + } + let shard_id = 0; + let signer = create_test_signer("test1"); + let env = TestEnv::new(vec![vec![signer.validator_id().clone()]], 100, false); + let garbage_size_mb = 50usize; + let receipt = Receipt::V1(ReceiptV1 { + predecessor_id: signer.validator_id().clone(), + receiver_id: signer.validator_id().clone(), + receipt_id: CryptoHash::hash_bytes(&[42]), + receipt: near_primitives::receipt::ReceiptEnum::Action(ActionReceipt { + signer_id: signer.validator_id().clone(), + signer_public_key: signer.public_key(), + gas_price: env.runtime.genesis_config.min_gas_price, + output_data_receivers: vec![], + input_data_ids: vec![], + actions: vec![Action::FunctionCall( + FunctionCallAction { + method_name: format!("internal_record_storage_garbage_{garbage_size_mb}"), + args: vec![], + gas: 300000000000000, + deposit: 300000000000000, + } + .into(), + )], + }), + priority: 0, + }); + let apply_result = + env.apply_new_chunk(shard_id, hash(&[42]), &[], &[receipt], ChallengesResult::default()); + let PartialState::TrieValues(storage_proof) = apply_result.proof.unwrap().nodes; + let total_size: usize = storage_proof.iter().map(|v| v.len()).sum(); + assert_eq!(total_size / 1000_000, garbage_size_mb); +} + +fn stake( + nonce: Nonce, + signer: &Signer, + sender: &ValidatorSigner, + stake: Balance, +) -> SignedTransaction { + SignedTransaction::from_actions( + nonce, + sender.validator_id().clone(), + sender.validator_id().clone(), + &*signer, + vec![Action::Stake(Box::new(StakeAction { stake, public_key: sender.public_key() }))], + // runtime does not validate block history + CryptoHash::default(), + 0, + ) +} diff --git a/chain/chain/src/state_request_tracker.rs b/chain/chain/src/state_request_tracker.rs index d5de7112541..64d7fef2fad 100644 --- a/chain/chain/src/state_request_tracker.rs +++ b/chain/chain/src/state_request_tracker.rs @@ -7,6 +7,7 @@ use near_primitives::{ views::{PartElapsedTimeView, RequestedStatePartsView}, }; use std::collections::HashMap; +use std::num::NonZeroUsize; const REQUESTED_STATE_PARTS_CACHE_SIZE: usize = 4; @@ -22,7 +23,9 @@ pub(crate) struct StateRequestTracker { impl StateRequestTracker { pub(crate) fn new() -> Self { StateRequestTracker { - requested_state_parts: LruCache::new(REQUESTED_STATE_PARTS_CACHE_SIZE), + requested_state_parts: LruCache::new( + NonZeroUsize::new(REQUESTED_STATE_PARTS_CACHE_SIZE).unwrap(), + ), } } diff --git a/chain/chain/src/state_snapshot_actor.rs b/chain/chain/src/state_snapshot_actor.rs index de558ca3fba..b1b2d8a36d8 100644 --- a/chain/chain/src/state_snapshot_actor.rs +++ b/chain/chain/src/state_snapshot_actor.rs @@ -1,5 +1,5 @@ use near_async::messaging::{Actor, CanSend, Handler, Sender}; -use near_async::{MultiSend, MultiSendMessage, MultiSenderFrom}; +use near_async::{MultiSend, MultiSenderFrom}; use near_network::types::{NetworkRequests, PeerManagerAdapter, PeerManagerMessageRequest}; use near_performance_metrics_macros::perf; use near_primitives::block::Block; @@ -84,18 +84,17 @@ impl StateSnapshotActor { } match res { Ok(res_shard_uids) => { - if let Some(res_shard_uids) = res_shard_uids { - self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( - NetworkRequests::SnapshotHostInfo { - sync_hash: prev_block_hash, - epoch_height, - shards: res_shard_uids - .iter() - .map(|uid| uid.shard_id as ShardId) - .collect(), - }, - )); - } + let Some(res_shard_uids) = res_shard_uids else { + return; + }; + + self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( + NetworkRequests::SnapshotHostInfo { + sync_hash: prev_block_hash, + epoch_height, + shards: res_shard_uids.iter().map(|uid| uid.shard_id as ShardId).collect(), + }, + )); } Err(err) => { tracing::error!(target: "state_snapshot", ?err, "State snapshot creation failed.\ @@ -120,14 +119,12 @@ impl Handler for StateSnapshotActor { } } -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct StateSnapshotSenderForStateSnapshot { create_snapshot: Sender, } -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct StateSnapshotSenderForClient(Sender); type MakeSnapshotCallback = diff --git a/chain/chain/src/stateless_validation/chunk_validation.rs b/chain/chain/src/stateless_validation/chunk_validation.rs new file mode 100644 index 00000000000..08b5e2e0bf5 --- /dev/null +++ b/chain/chain/src/stateless_validation/chunk_validation.rs @@ -0,0 +1,607 @@ +use crate::chain::{ + apply_new_chunk, apply_old_chunk, NewChunkData, NewChunkResult, OldChunkData, OldChunkResult, + ShardContext, StorageContext, +}; +use crate::rayon_spawner::RayonAsyncComputationSpawner; +use crate::sharding::shuffle_receipt_proofs; +use crate::stateless_validation::processing_tracker::ProcessingDoneTracker; +use crate::types::{ + ApplyChunkBlockContext, ApplyChunkResult, PreparedTransactions, RuntimeAdapter, + RuntimeStorageConfig, StorageDataSource, +}; +use crate::validate::validate_chunk_with_chunk_extra_and_receipts_root; +use crate::{Chain, ChainStoreAccess}; +use lru::LruCache; +use near_async::futures::AsyncComputationSpawnerExt; +use near_chain_primitives::Error; +use near_epoch_manager::EpochManagerAdapter; +use near_pool::TransactionGroupIteratorWrapper; +use near_primitives::apply::ApplyChunkReason; +use near_primitives::block::Block; +use near_primitives::hash::{hash, CryptoHash}; +use near_primitives::merkle::merklize; +use near_primitives::receipt::Receipt; +use near_primitives::shard_layout::ShardUId; +use near_primitives::sharding::{ChunkHash, ReceiptProof, ShardChunkHeader}; +use near_primitives::stateless_validation::{ChunkStateWitness, EncodedChunkStateWitness}; +use near_primitives::transaction::SignedTransaction; +use near_primitives::types::chunk_extra::ChunkExtra; +use near_primitives::types::{ProtocolVersion, ShardId}; +use near_store::PartialStorage; +use std::collections::HashMap; +use std::num::NonZeroUsize; +use std::sync::{Arc, Mutex}; +use std::time::Instant; + +#[allow(clippy::large_enum_variant)] +pub enum MainTransition { + Genesis { chunk_extra: ChunkExtra, block_hash: CryptoHash, shard_id: ShardId }, + NewChunk(NewChunkData), +} + +impl MainTransition { + pub fn block_hash(&self) -> CryptoHash { + match self { + Self::Genesis { block_hash, .. } => *block_hash, + Self::NewChunk(data) => data.block.block_hash, + } + } + + pub fn shard_id(&self) -> ShardId { + match self { + Self::Genesis { shard_id, .. } => *shard_id, + Self::NewChunk(data) => data.chunk_header.shard_id(), + } + } +} + +pub struct PreValidationOutput { + pub main_transition_params: MainTransition, + pub implicit_transition_params: Vec, +} + +#[derive(Clone)] +pub struct ChunkStateWitnessValidationResult { + pub chunk_extra: ChunkExtra, + pub outgoing_receipts: Vec, +} + +pub type MainStateTransitionCache = + Arc>>>; + +/// The number of state witness validation results to cache per shard. +/// This number needs to be small because result contains outgoing receipts, which can be large. +const NUM_WITNESS_RESULT_CACHE_ENTRIES: usize = 20; + +/// Checks that proposed `transactions` are valid for a chunk with `chunk_header`. +/// Uses `storage_config` to possibly record reads or use recorded storage. +pub fn validate_prepared_transactions( + chain: &Chain, + runtime_adapter: &dyn RuntimeAdapter, + chunk_header: &ShardChunkHeader, + storage_config: RuntimeStorageConfig, + transactions: &[SignedTransaction], + last_chunk_transactions: &[SignedTransaction], +) -> Result { + let parent_block = chain.chain_store().get_block(chunk_header.prev_block_hash())?; + let last_chunk_transactions_size = borsh::to_vec(last_chunk_transactions)?.len(); + runtime_adapter.prepare_transactions( + storage_config, + crate::types::PrepareTransactionsChunkContext { + shard_id: chunk_header.shard_id(), + gas_limit: chunk_header.gas_limit(), + last_chunk_transactions_size, + }, + (&parent_block).into(), + &mut TransactionGroupIteratorWrapper::new(transactions), + &mut chain.transaction_validity_check(parent_block.header().clone()), + None, + ) +} + +/// Pre-validates the chunk's receipts and transactions against the chain. +/// We do this before handing off the computationally intensive part to a +/// validation thread. +pub fn pre_validate_chunk_state_witness( + state_witness: &ChunkStateWitness, + chain: &Chain, + epoch_manager: &dyn EpochManagerAdapter, + runtime_adapter: &dyn RuntimeAdapter, +) -> Result { + let store = chain.chain_store(); + let shard_id = state_witness.chunk_header.shard_id(); + + // First, go back through the blockchain history to locate the last new chunk + // and last last new chunk for the shard. + + // Blocks from the last new chunk (exclusive) to the parent block (inclusive). + let mut blocks_after_last_chunk = Vec::new(); + // Blocks from the last last new chunk (exclusive) to the last new chunk (inclusive). + let mut blocks_after_last_last_chunk = Vec::new(); + + { + let mut block_hash = *state_witness.chunk_header.prev_block_hash(); + let mut prev_chunks_seen = 0; + loop { + let block = store.get_block(&block_hash)?; + let chunks = block.chunks(); + let Some(chunk) = chunks.get(shard_id as usize) else { + return Err(Error::InvalidChunkStateWitness(format!( + "Shard {} does not exist in block {:?}", + shard_id, block_hash + ))); + }; + let is_new_chunk = chunk.is_new_chunk(block.header().height()); + let is_genesis = block.header().is_genesis(); + block_hash = *block.header().prev_hash(); + if is_new_chunk { + prev_chunks_seen += 1; + } + if prev_chunks_seen == 0 { + blocks_after_last_chunk.push(block); + } else if prev_chunks_seen == 1 { + blocks_after_last_last_chunk.push(block); + } + if prev_chunks_seen == 2 || is_genesis { + break; + } + } + } + + let receipts_to_apply = validate_source_receipt_proofs( + &state_witness.source_receipt_proofs, + &blocks_after_last_last_chunk, + shard_id, + )?; + let applied_receipts_hash = hash(&borsh::to_vec(receipts_to_apply.as_slice()).unwrap()); + if applied_receipts_hash != state_witness.applied_receipts_hash { + return Err(Error::InvalidChunkStateWitness(format!( + "Receipts hash {:?} does not match expected receipts hash {:?}", + applied_receipts_hash, state_witness.applied_receipts_hash + ))); + } + let (tx_root_from_state_witness, _) = merklize(&state_witness.transactions); + let last_chunk_block = blocks_after_last_last_chunk.first().ok_or_else(|| { + Error::Other("blocks_after_last_last_chunk is empty, this should be impossible!".into()) + })?; + let last_new_chunk_tx_root = + last_chunk_block.chunks().get(shard_id as usize).unwrap().tx_root(); + if last_new_chunk_tx_root != tx_root_from_state_witness { + return Err(Error::InvalidChunkStateWitness(format!( + "Transaction root {:?} does not match expected transaction root {:?}", + tx_root_from_state_witness, last_new_chunk_tx_root + ))); + } + + // Verify that all proposed transactions are valid. + let new_transactions = &state_witness.new_transactions; + if !new_transactions.is_empty() { + let transactions_validation_storage_config = RuntimeStorageConfig { + state_root: state_witness.chunk_header.prev_state_root(), + use_flat_storage: true, + source: StorageDataSource::Recorded(PartialStorage { + nodes: state_witness.new_transactions_validation_state.clone(), + }), + state_patch: Default::default(), + }; + + match validate_prepared_transactions( + chain, + runtime_adapter, + &state_witness.chunk_header, + transactions_validation_storage_config, + &new_transactions, + &state_witness.transactions, + ) { + Ok(result) => { + if result.transactions.len() != new_transactions.len() { + return Err(Error::InvalidChunkStateWitness(format!( + "New transactions validation failed. {} transactions out of {} proposed transactions were valid.", + result.transactions.len(), + new_transactions.len(), + ))); + } + } + Err(error) => { + return Err(Error::InvalidChunkStateWitness(format!( + "New transactions validation failed: {}", + error, + ))); + } + }; + } + + let main_transition_params = if last_chunk_block.header().is_genesis() { + let epoch_id = last_chunk_block.header().epoch_id(); + let congestion_info = last_chunk_block + .block_congestion_info() + .get(&shard_id) + .map(|info| info.congestion_info); + let genesis_protocol_version = epoch_manager.get_epoch_protocol_version(&epoch_id)?; + let chunk_extra = + chain.genesis_chunk_extra(shard_id, genesis_protocol_version, congestion_info)?; + MainTransition::Genesis { chunk_extra, block_hash: *last_chunk_block.hash(), shard_id } + } else { + MainTransition::NewChunk(NewChunkData { + chunk_header: last_chunk_block.chunks().get(shard_id as usize).unwrap().clone(), + transactions: state_witness.transactions.clone(), + receipts: receipts_to_apply, + resharding_state_roots: None, + block: Chain::get_apply_chunk_block_context( + epoch_manager, + last_chunk_block, + &store.get_block_header(last_chunk_block.header().prev_hash())?, + true, + )?, + is_first_block_with_chunk_of_version: false, + storage_context: StorageContext { + storage_data_source: StorageDataSource::Recorded(PartialStorage { + nodes: state_witness.main_state_transition.base_state.clone(), + }), + state_patch: Default::default(), + }, + }) + }; + + Ok(PreValidationOutput { + main_transition_params, + implicit_transition_params: blocks_after_last_chunk + .into_iter() + .rev() + .map(|block| -> Result<_, Error> { + Ok(Chain::get_apply_chunk_block_context( + epoch_manager, + &block, + &store.get_block_header(block.header().prev_hash())?, + false, + )?) + }) + .collect::>()?, + }) +} + +/// Validate that receipt proofs contain the receipts that should be applied during the +/// transition proven by ChunkStateWitness. The receipts are extracted from the proofs +/// and arranged in the order in which they should be applied during the transition. +/// TODO(resharding): Handle resharding properly. If the receipts were sent from before +/// a resharding boundary, we should first validate the proof using the pre-resharding +/// target_shard_id and then extract the receipts that are targeted at this half of a split shard. +fn validate_source_receipt_proofs( + source_receipt_proofs: &HashMap, + receipt_source_blocks: &[Block], + target_chunk_shard_id: ShardId, +) -> Result, Error> { + if receipt_source_blocks.iter().any(|block| block.header().is_genesis()) { + if receipt_source_blocks.len() != 1 { + return Err(Error::Other( + "Invalid chain state: receipt_source_blocks should not have any blocks alongside genesis".to_owned() + )); + } + if !source_receipt_proofs.is_empty() { + return Err(Error::InvalidChunkStateWitness(format!( + "genesis source_receipt_proofs should be empty, actual len is {}", + source_receipt_proofs.len() + ))); + } + return Ok(vec![]); + } + + let mut receipts_to_apply = Vec::new(); + let mut expected_proofs_len = 0; + + // Iterate over blocks between last_chunk_block (inclusive) and last_last_chunk_block (exclusive), + // from the newest blocks to the oldest. + for block in receipt_source_blocks { + // Collect all receipts coming from this block. + let mut block_receipt_proofs = Vec::new(); + + for chunk in block.chunks().iter() { + if !chunk.is_new_chunk(block.header().height()) { + continue; + } + + // Collect receipts coming from this chunk and validate that they are correct. + let Some(receipt_proof) = source_receipt_proofs.get(&chunk.chunk_hash()) else { + return Err(Error::InvalidChunkStateWitness(format!( + "Missing source receipt proof for chunk {:?}", + chunk.chunk_hash() + ))); + }; + validate_receipt_proof(receipt_proof, chunk, target_chunk_shard_id)?; + + expected_proofs_len += 1; + block_receipt_proofs.push(receipt_proof); + } + + // Arrange the receipts in the order in which they should be applied. + shuffle_receipt_proofs(&mut block_receipt_proofs, block.hash()); + for proof in block_receipt_proofs { + receipts_to_apply.extend(proof.0.iter().cloned()); + } + } + + // Check that there are no extraneous proofs in source_receipt_proofs. + if source_receipt_proofs.len() != expected_proofs_len { + return Err(Error::InvalidChunkStateWitness(format!( + "source_receipt_proofs contains too many proofs. Expected {} proofs, found {}", + expected_proofs_len, + source_receipt_proofs.len() + ))); + } + Ok(receipts_to_apply) +} + +fn validate_receipt_proof( + receipt_proof: &ReceiptProof, + from_chunk: &ShardChunkHeader, + target_chunk_shard_id: ShardId, +) -> Result<(), Error> { + // Validate that from_shard_id is correct. The receipts must match the outgoing receipt root + // for this shard, so it's impossible to fake it. + if receipt_proof.1.from_shard_id != from_chunk.shard_id() { + return Err(Error::InvalidChunkStateWitness(format!( + "Receipt proof for chunk {:?} is from shard {}, expected shard {}", + from_chunk.chunk_hash(), + receipt_proof.1.from_shard_id, + from_chunk.shard_id(), + ))); + } + // Validate that to_shard_id is correct. to_shard_id is also encoded in the merkle tree, + // so it's impossible to fake it. + if receipt_proof.1.to_shard_id != target_chunk_shard_id { + return Err(Error::InvalidChunkStateWitness(format!( + "Receipt proof for chunk {:?} is for shard {}, expected shard {}", + from_chunk.chunk_hash(), + receipt_proof.1.to_shard_id, + target_chunk_shard_id + ))); + } + // Verify that (receipts, to_shard_id) belongs to the merkle tree of outgoing receipts in from_chunk. + if !receipt_proof.verify_against_receipt_root(from_chunk.prev_outgoing_receipts_root()) { + return Err(Error::InvalidChunkStateWitness(format!( + "Receipt proof for chunk {:?} has invalid merkle path, doesn't match outgoing receipts root", + from_chunk.chunk_hash() + ))); + } + Ok(()) +} + +pub fn validate_chunk_state_witness( + state_witness: ChunkStateWitness, + pre_validation_output: PreValidationOutput, + epoch_manager: &dyn EpochManagerAdapter, + runtime_adapter: &dyn RuntimeAdapter, + main_state_transition_cache: &MainStateTransitionCache, +) -> Result<(), Error> { + let _timer = crate::stateless_validation::metrics::CHUNK_STATE_WITNESS_VALIDATION_TIME + .with_label_values(&[&state_witness.chunk_header.shard_id().to_string()]) + .start_timer(); + let span = tracing::debug_span!(target: "client", "validate_chunk_state_witness").entered(); + let block_hash = pre_validation_output.main_transition_params.block_hash(); + let epoch_id = epoch_manager.get_epoch_id(&block_hash)?; + let shard_uid = epoch_manager + .shard_id_to_uid(pre_validation_output.main_transition_params.shard_id(), &epoch_id)?; + let protocol_version = epoch_manager.get_epoch_protocol_version(&epoch_id)?; + let cache_result = { + let mut shard_cache = main_state_transition_cache.lock().unwrap(); + shard_cache.get_mut(&shard_uid).and_then(|cache| cache.get(&block_hash).cloned()) + }; + let (mut chunk_extra, outgoing_receipts) = + match (pre_validation_output.main_transition_params, cache_result) { + (MainTransition::Genesis { chunk_extra, .. }, _) => (chunk_extra, vec![]), + (MainTransition::NewChunk(new_chunk_data), None) => { + let chunk_header = new_chunk_data.chunk_header.clone(); + let NewChunkResult { apply_result: mut main_apply_result, .. } = apply_new_chunk( + ApplyChunkReason::ValidateChunkStateWitness, + &span, + new_chunk_data, + ShardContext { + shard_uid, + cares_about_shard_this_epoch: true, + will_shard_layout_change: false, + should_apply_chunk: true, + need_to_reshard: false, + }, + runtime_adapter, + epoch_manager, + )?; + let outgoing_receipts = std::mem::take(&mut main_apply_result.outgoing_receipts); + let chunk_extra = + apply_result_to_chunk_extra(protocol_version, main_apply_result, &chunk_header); + + (chunk_extra, outgoing_receipts) + } + (_, Some(result)) => (result.chunk_extra, result.outgoing_receipts), + }; + if chunk_extra.state_root() != &state_witness.main_state_transition.post_state_root { + // This is an early check, it's not for correctness, only for better + // error reporting in case of an invalid state witness due to a bug. + // Only the final state root check against the chunk header is required. + return Err(Error::InvalidChunkStateWitness(format!( + "Post state root {:?} for main transition does not match expected post state root {:?}", + chunk_extra.state_root(), + state_witness.main_state_transition.post_state_root, + ))); + } + + // Compute receipt hashes here to avoid copying receipts + let outgoing_receipts_hashes = { + let shard_layout = epoch_manager + .get_shard_layout_from_prev_block(state_witness.chunk_header.prev_block_hash())?; + Chain::build_receipts_hashes(&outgoing_receipts, &shard_layout) + }; + // Save main state transition result to cache. + { + let mut shard_cache = main_state_transition_cache.lock().unwrap(); + let cache = shard_cache.entry(shard_uid).or_insert_with(|| { + LruCache::new(NonZeroUsize::new(NUM_WITNESS_RESULT_CACHE_ENTRIES).unwrap()) + }); + cache.put( + block_hash, + ChunkStateWitnessValidationResult { + chunk_extra: chunk_extra.clone(), + outgoing_receipts: outgoing_receipts, + }, + ); + } + + for (block, transition) in pre_validation_output + .implicit_transition_params + .into_iter() + .zip(state_witness.implicit_transitions.into_iter()) + { + let block_hash = block.block_hash; + let old_chunk_data = OldChunkData { + prev_chunk_extra: chunk_extra.clone(), + resharding_state_roots: None, + block, + storage_context: StorageContext { + storage_data_source: StorageDataSource::Recorded(PartialStorage { + nodes: transition.base_state, + }), + state_patch: Default::default(), + }, + }; + let OldChunkResult { apply_result, .. } = apply_old_chunk( + ApplyChunkReason::ValidateChunkStateWitness, + &span, + old_chunk_data, + ShardContext { + // Consider other shard uid in case of resharding. + shard_uid, + cares_about_shard_this_epoch: true, + will_shard_layout_change: false, + should_apply_chunk: false, + need_to_reshard: false, + }, + runtime_adapter, + epoch_manager, + )?; + *chunk_extra.state_root_mut() = apply_result.new_root; + if chunk_extra.state_root() != &transition.post_state_root { + // This is an early check, it's not for correctness, only for better + // error reporting in case of an invalid state witness due to a bug. + // Only the final state root check against the chunk header is required. + return Err(Error::InvalidChunkStateWitness(format!( + "Post state root {:?} for implicit transition at block {:?}, does not match expected state root {:?}", + chunk_extra.state_root(), block_hash, transition.post_state_root + ))); + } + } + + // Finally, verify that the newly proposed chunk matches everything we have computed. + let (outgoing_receipts_root, _) = merklize(&outgoing_receipts_hashes); + validate_chunk_with_chunk_extra_and_receipts_root( + &chunk_extra, + &state_witness.chunk_header, + &outgoing_receipts_root, + )?; + + Ok(()) +} + +pub fn apply_result_to_chunk_extra( + protocol_version: ProtocolVersion, + apply_result: ApplyChunkResult, + chunk: &ShardChunkHeader, +) -> ChunkExtra { + let (outcome_root, _) = ApplyChunkResult::compute_outcomes_proof(&apply_result.outcomes); + ChunkExtra::new( + protocol_version, + &apply_result.new_root, + outcome_root, + apply_result.validator_proposals, + apply_result.total_gas_burnt, + chunk.gas_limit(), + apply_result.total_balance_burnt, + apply_result.congestion_info, + ) +} + +impl Chain { + pub fn shadow_validate_state_witness( + &self, + witness: ChunkStateWitness, + epoch_manager: &dyn EpochManagerAdapter, + runtime_adapter: &dyn RuntimeAdapter, + processing_done_tracker: Option, + ) -> Result<(), Error> { + let shard_id = witness.chunk_header.shard_id(); + let height_created = witness.chunk_header.height_created(); + let chunk_hash = witness.chunk_header.chunk_hash(); + let parent_span = tracing::debug_span!( + target: "chain", "shadow_validate", shard_id, height_created); + let (encoded_witness, raw_witness_size) = { + let shard_id_label = shard_id.to_string(); + let encode_timer = + crate::stateless_validation::metrics::CHUNK_STATE_WITNESS_ENCODE_TIME + .with_label_values(&[shard_id_label.as_str()]) + .start_timer(); + let (encoded_witness, raw_witness_size) = EncodedChunkStateWitness::encode(&witness)?; + encode_timer.observe_duration(); + crate::stateless_validation::metrics::record_witness_size_metrics( + raw_witness_size, + encoded_witness.size_bytes(), + &witness, + ); + let decode_timer = + crate::stateless_validation::metrics::CHUNK_STATE_WITNESS_DECODE_TIME + .with_label_values(&[shard_id_label.as_str()]) + .start_timer(); + encoded_witness.decode()?; + decode_timer.observe_duration(); + (encoded_witness, raw_witness_size) + }; + let pre_validation_start = Instant::now(); + let pre_validation_result = + pre_validate_chunk_state_witness(&witness, &self, epoch_manager, runtime_adapter)?; + tracing::debug!( + parent: &parent_span, + shard_id, + ?chunk_hash, + witness_size = encoded_witness.size_bytes(), + raw_witness_size, + pre_validation_elapsed = ?pre_validation_start.elapsed(), + "completed shadow chunk pre-validation" + ); + let epoch_manager = self.epoch_manager.clone(); + let runtime_adapter = self.runtime_adapter.clone(); + Arc::new(RayonAsyncComputationSpawner).spawn("shadow_validate", move || { + // processing_done_tracker must survive until the processing is finished. + let _processing_done_tracker_capture: Option = + processing_done_tracker; + + let validation_start = Instant::now(); + + match validate_chunk_state_witness( + witness, + pre_validation_result, + epoch_manager.as_ref(), + runtime_adapter.as_ref(), + &MainStateTransitionCache::default(), + ) { + Ok(()) => { + tracing::debug!( + parent: &parent_span, + shard_id, + ?chunk_hash, + validation_elapsed = ?validation_start.elapsed(), + "completed shadow chunk validation" + ); + } + Err(err) => { + crate::stateless_validation::metrics::SHADOW_CHUNK_VALIDATION_FAILED_TOTAL + .inc(); + tracing::error!( + parent: &parent_span, + ?err, + shard_id, + ?chunk_hash, + "shadow chunk validation failed" + ); + } + } + }); + Ok(()) + } +} diff --git a/chain/chain/src/stateless_validation/metrics.rs b/chain/chain/src/stateless_validation/metrics.rs new file mode 100644 index 00000000000..8537ebd8e05 --- /dev/null +++ b/chain/chain/src/stateless_validation/metrics.rs @@ -0,0 +1,208 @@ +use near_o11y::metrics::{ + exponential_buckets, linear_buckets, try_create_histogram_vec, try_create_int_counter, + try_create_int_gauge, HistogramVec, IntCounter, IntGauge, +}; +use near_primitives::stateless_validation::ChunkStateWitness; +use once_cell::sync::Lazy; + +pub static SAVE_LATEST_WITNESS_GENERATE_UPDATE_TIME: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_save_latest_witness_generate_update_time", + "Time taken to generate an update of latest witnesses", + &["shard_id"], + Some(exponential_buckets(0.001, 1.6, 20).unwrap()), + ) + .unwrap() +}); +pub static SAVE_LATEST_WITNESS_COMMIT_UPDATE_TIME: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_save_latest_witness_commit_update_time", + "Time taken to commit the update of latest witnesses", + &["shard_id"], + Some(exponential_buckets(0.001, 1.6, 20).unwrap()), + ) + .unwrap() +}); +pub static SAVED_LATEST_WITNESSES_COUNT: Lazy = Lazy::new(|| { + try_create_int_gauge( + "near_saved_latest_witnesses_count", + "Total number of saved latest witnesses", + ) + .unwrap() +}); +pub static SAVED_LATEST_WITNESSES_SIZE: Lazy = Lazy::new(|| { + try_create_int_gauge( + "near_saved_latest_witnesses_size", + "Total size of saved latest witnesses (in bytes)", + ) + .unwrap() +}); + +pub static CHUNK_STATE_WITNESS_ENCODE_TIME: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_encode_time", + "State witness encoding (serialization + compression) latency in seconds", + &["shard_id"], + Some(linear_buckets(0.025, 0.025, 20).unwrap()), + ) + .unwrap() +}); + +pub static SHADOW_CHUNK_VALIDATION_FAILED_TOTAL: Lazy = Lazy::new(|| { + try_create_int_counter( + "near_shadow_chunk_validation_failed_total", + "Shadow chunk validation failures count", + ) + .unwrap() +}); + +pub(crate) static CHUNK_STATE_WITNESS_VALIDATION_TIME: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_validation_time", + "State witness validation latency in seconds", + &["shard_id"], + Some(exponential_buckets(0.01, 2.0, 12).unwrap()), + ) + .unwrap() +}); + +pub(crate) static CHUNK_STATE_WITNESS_TOTAL_SIZE: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_total_size", + "Stateless validation compressed state witness size in bytes", + &["shard_id"], + Some(exponential_buckets(100_000.0, 1.2, 32).unwrap()), + ) + .unwrap() +}); + +pub(crate) static CHUNK_STATE_WITNESS_RAW_SIZE: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_raw_size", + "Stateless validation uncompressed (raw) state witness size in bytes", + &["shard_id"], + Some(exponential_buckets(100_000.0, 1.2, 32).unwrap()), + ) + .unwrap() +}); + +pub static CHUNK_STATE_WITNESS_DECODE_TIME: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_decode_time", + "State witness decoding (decompression + deserialization) latency in seconds", + &["shard_id"], + Some(linear_buckets(0.025, 0.025, 20).unwrap()), + ) + .unwrap() +}); + +pub(crate) static CHUNK_STATE_WITNESS_MAIN_STATE_TRANSISTION_SIZE: Lazy = Lazy::new( + || { + try_create_histogram_vec( + "near_chunk_state_witness_main_state_transition_size", + "Size of ChunkStateWitness::main_state_transition (storage proof needed to execute receipts)", + &["shard_id"], + Some(buckets_for_witness_field_size()), + ) + .unwrap() + }, +); + +pub(crate) static CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_SIZE: Lazy = Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_new_transactions_size", + "Size of ChunkStateWitness::new_transactions (new proposed transactions)", + &["shard_id"], + Some(buckets_for_witness_field_size()), + ) + .unwrap() +}); + +pub(crate) static CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_STATE_SIZE: Lazy = Lazy::new( + || { + try_create_histogram_vec( + "near_chunk_state_witness_new_transactions_state_size", + "Size of ChunkStateWitness::new_transactions_validation_state (storage proof to validate new proposed transactions)", + &["shard_id"], + Some(buckets_for_witness_field_size()), + ) + .unwrap() + }, +); + +pub(crate) static CHUNK_STATE_WITNESS_SOURCE_RECEIPT_PROOFS_SIZE: Lazy = + Lazy::new(|| { + try_create_histogram_vec( + "near_chunk_state_witness_source_receipt_proofs_size", + "Size of ChunkStateWitness::source_receipt_proofs (incoming receipts proofs)", + &["shard_id"], + Some(buckets_for_witness_field_size()), + ) + .unwrap() + }); + +pub fn record_witness_size_metrics( + decoded_size: usize, + encoded_size: usize, + witness: &ChunkStateWitness, +) { + if let Err(err) = record_witness_size_metrics_fallible(decoded_size, encoded_size, witness) { + tracing::warn!(target:"client", "Failed to record witness size metrics!, error: {}", err); + } +} + +fn record_witness_size_metrics_fallible( + decoded_size: usize, + encoded_size: usize, + witness: &ChunkStateWitness, +) -> Result<(), std::io::Error> { + let shard_id = witness.chunk_header.shard_id().to_string(); + CHUNK_STATE_WITNESS_RAW_SIZE + .with_label_values(&[shard_id.as_str()]) + .observe(decoded_size as f64); + CHUNK_STATE_WITNESS_TOTAL_SIZE + .with_label_values(&[&shard_id.as_str()]) + .observe(encoded_size as f64); + CHUNK_STATE_WITNESS_MAIN_STATE_TRANSISTION_SIZE + .with_label_values(&[shard_id.as_str()]) + .observe(borsh::to_vec(&witness.main_state_transition)?.len() as f64); + CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_SIZE + .with_label_values(&[&shard_id.as_str()]) + .observe(borsh::to_vec(&witness.new_transactions)?.len() as f64); + CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_STATE_SIZE + .with_label_values(&[&shard_id.as_str()]) + .observe(borsh::to_vec(&witness.new_transactions_validation_state)?.len() as f64); + CHUNK_STATE_WITNESS_SOURCE_RECEIPT_PROOFS_SIZE + .with_label_values(&[&shard_id.as_str()]) + .observe(borsh::to_vec(&witness.source_receipt_proofs)?.len() as f64); + Ok(()) +} + +/// Buckets from 0 to 10MB +/// Meant for measuring size of a single field inside ChunkSizeWitness. +fn buckets_for_witness_field_size() -> Vec { + vec![ + 10_000., + 20_000., + 50_000., + 100_000., + 200_000., + 300_000., + 500_000., + 750_000., + 1000_000., + 1500_000., + 2000_000., + 2500_000., + 3000_000., + 3500_000., + 4000_000., + 4500_000., + 5000_000., + 6000_000., + 7000_000., + 8000_000., + 9000_000., + 10_000_000., + ] +} diff --git a/chain/chain/src/stateless_validation/mod.rs b/chain/chain/src/stateless_validation/mod.rs index 04304b75620..d70739ecb76 100644 --- a/chain/chain/src/stateless_validation/mod.rs +++ b/chain/chain/src/stateless_validation/mod.rs @@ -1,2 +1,5 @@ pub(crate) mod chunk_endorsement; +pub mod chunk_validation; +pub mod metrics; +pub mod processing_tracker; pub(crate) mod state_transition_data; diff --git a/chain/client/src/stateless_validation/processing_tracker.rs b/chain/chain/src/stateless_validation/processing_tracker.rs similarity index 100% rename from chain/client/src/stateless_validation/processing_tracker.rs rename to chain/chain/src/stateless_validation/processing_tracker.rs diff --git a/chain/chain/src/store/latest_witnesses.rs b/chain/chain/src/store/latest_witnesses.rs index 4ecec8bdd69..66570e4140a 100644 --- a/chain/chain/src/store/latest_witnesses.rs +++ b/chain/chain/src/store/latest_witnesses.rs @@ -12,7 +12,7 @@ use near_primitives::stateless_validation::ChunkStateWitness; use near_primitives::types::EpochId; use near_store::DBCol; -use crate::metrics; +use crate::stateless_validation; use crate::ChainStoreAccess; use super::ChainStore; @@ -171,7 +171,7 @@ impl ChainStore { let key = LatestWitnessesKey { height: witness.chunk_header.height_created(), shard_id: witness.chunk_header.shard_id(), - epoch_id: witness.epoch_id.clone(), + epoch_id: witness.epoch_id, witness_size: serialized_witness_size, random_uuid, }; @@ -193,14 +193,14 @@ impl ChainStore { let store_commit_time = start_time.elapsed().saturating_sub(store_update_time); let shard_id_str = witness.chunk_header.shard_id().to_string(); - metrics::SAVE_LATEST_WITNESS_GENERATE_UPDATE_TIME + stateless_validation::metrics::SAVE_LATEST_WITNESS_GENERATE_UPDATE_TIME .with_label_values(&[shard_id_str.as_str()]) .observe(store_update_time.as_secs_f64()); - metrics::SAVE_LATEST_WITNESS_COMMIT_UPDATE_TIME + stateless_validation::metrics::SAVE_LATEST_WITNESS_COMMIT_UPDATE_TIME .with_label_values(&[shard_id_str.as_str()]) .observe(store_commit_time.as_secs_f64()); - metrics::SAVED_LATEST_WITNESSES_COUNT.set(info.count as i64); - metrics::SAVED_LATEST_WITNESSES_SIZE.set(info.total_size as i64); + stateless_validation::metrics::SAVED_LATEST_WITNESSES_COUNT.set(info.count as i64); + stateless_validation::metrics::SAVED_LATEST_WITNESSES_SIZE.set(info.total_size as i64); tracing::debug!( ?store_update_time, diff --git a/chain/chain/src/store/mod.rs b/chain/chain/src/store/mod.rs index 15cd3614290..2c7d3768304 100644 --- a/chain/chain/src/store/mod.rs +++ b/chain/chain/src/store/mod.rs @@ -303,9 +303,6 @@ pub trait ChainStoreAccess { chunk_hash: &ChunkHash, ) -> Result>, Error>; - /// Get destination shard id for receipt id. - fn get_shard_id_for_receipt_id(&self, receipt_id: &CryptoHash) -> Result; - fn get_transaction( &self, tx_hash: &CryptoHash, @@ -361,7 +358,7 @@ pub trait ChainStoreAccess { .get(shard_id as usize) .ok_or_else(|| Error::InvalidShardId(shard_id as ShardId))? { - break Ok(block_header.epoch_id().clone()); + break Ok(*block_header.epoch_id()); } candidate_hash = *block_header.prev_hash(); shard_id = epoch_manager.get_prev_shard_ids(&candidate_hash, vec![shard_id])?[0]; @@ -437,8 +434,6 @@ pub struct ChainStore { pub(crate) incoming_receipts: CellLruCache, Arc>>, /// Invalid chunks. pub(crate) invalid_chunks: CellLruCache, Arc>, - /// Mapping from receipt id to destination shard id - pub(crate) receipt_id_to_shard_id: CellLruCache, ShardId>, /// Transactions pub(crate) transactions: CellLruCache, Arc>, /// Receipts @@ -491,7 +486,6 @@ impl ChainStore { outgoing_receipts: CellLruCache::new(CACHE_SIZE), incoming_receipts: CellLruCache::new(CACHE_SIZE), invalid_chunks: CellLruCache::new(CACHE_SIZE), - receipt_id_to_shard_id: CellLruCache::new(CHUNK_CACHE_SIZE), transactions: CellLruCache::new(CHUNK_CACHE_SIZE), receipts: CellLruCache::new(CHUNK_CACHE_SIZE), block_merkle_tree: CellLruCache::new(CACHE_SIZE), @@ -1340,17 +1334,6 @@ impl ChainStoreAccess for ChainStore { .map_err(|err| err.into()) } - fn get_shard_id_for_receipt_id(&self, receipt_id: &CryptoHash) -> Result { - option_to_not_found( - self.read_with_cache( - DBCol::ReceiptIdToShardId, - &self.receipt_id_to_shard_id, - receipt_id.as_ref(), - ), - format_args!("RECEIPT ID: {}", receipt_id), - ) - } - fn get_transaction( &self, tx_hash: &CryptoHash, @@ -1422,7 +1405,6 @@ pub(crate) struct ChainStoreCacheUpdate { outcomes: HashMap<(CryptoHash, CryptoHash), ExecutionOutcomeWithProof>, outcome_ids: HashMap<(CryptoHash, ShardId), Vec>, invalid_chunks: HashMap>, - receipt_id_to_shard_id: HashMap, transactions: HashMap>, receipts: HashMap>, block_refcounts: HashMap, @@ -1745,15 +1727,6 @@ impl<'a> ChainStoreAccess for ChainStoreUpdate<'a> { } } - fn get_shard_id_for_receipt_id(&self, receipt_id: &CryptoHash) -> Result { - if let Some(shard_id) = self.chain_store_cache_update.receipt_id_to_shard_id.get(receipt_id) - { - Ok(*shard_id) - } else { - self.chain_store.get_shard_id_for_receipt_id(receipt_id) - } - } - fn get_transaction( &self, tx_hash: &CryptoHash, @@ -2050,10 +2023,6 @@ impl<'a> ChainStoreUpdate<'a> { .insert((*hash, shard_id), Arc::new(outgoing_receipts)); } - pub fn save_receipt_id_to_shard_id(&mut self, receipt_id: CryptoHash, shard_id: ShardId) { - self.chain_store_cache_update.receipt_id_to_shard_id.insert(receipt_id, shard_id); - } - pub fn save_incoming_receipt( &mut self, hash: &CryptoHash, @@ -2237,8 +2206,8 @@ impl<'a> ChainStoreUpdate<'a> { height, last_block_hash: *block_hash, prev_block_hash: *header.prev_hash(), - epoch_id: header.epoch_id().clone(), - next_epoch_id: header.next_epoch_id().clone(), + epoch_id: *header.epoch_id(), + next_epoch_id: *header.next_epoch_id(), }; chain_store_update.head = Some(tip.clone()); chain_store_update.tail = Some(height); @@ -2361,7 +2330,7 @@ impl<'a> ChainStoreUpdate<'a> { .get_all_block_hashes_by_height(block.header().height())? .as_ref(), ); - map.entry(block.header().epoch_id().clone()) + map.entry(*block.header().epoch_id()) .or_insert_with(|| HashSet::new()) .insert(*hash); store_update.set_ser( @@ -2543,10 +2512,6 @@ impl<'a> ChainStoreUpdate<'a> { } } - for (receipt_id, shard_id) in self.chain_store_cache_update.receipt_id_to_shard_id.iter() { - let data = borsh::to_vec(&shard_id)?; - store_update.increment_refcount(DBCol::ReceiptIdToShardId, receipt_id.as_ref(), &data); - } for (block_hash, refcount) in self.chain_store_cache_update.block_refcounts.iter() { store_update.set_ser(DBCol::BlockRefCount, block_hash.as_ref(), refcount)?; } @@ -2717,7 +2682,6 @@ impl<'a> ChainStoreUpdate<'a> { outgoing_receipts, incoming_receipts, invalid_chunks, - receipt_id_to_shard_id, transactions, receipts, block_refcounts, @@ -2777,9 +2741,6 @@ impl<'a> ChainStoreUpdate<'a> { for (hash, invalid_chunk) in invalid_chunks { self.chain_store.invalid_chunks.put(hash.into(), invalid_chunk); } - for (receipt_id, shard_id) in receipt_id_to_shard_id { - self.chain_store.receipt_id_to_shard_id.put(receipt_id.into(), shard_id); - } for (hash, transaction) in transactions { self.chain_store.transactions.put(hash.into(), transaction); } diff --git a/chain/chain/src/store_validator.rs b/chain/chain/src/store_validator.rs index e83f5b5a9bd..075f7171c09 100644 --- a/chain/chain/src/store_validator.rs +++ b/chain/chain/src/store_validator.rs @@ -382,7 +382,7 @@ impl StoreValidator { #[cfg(test)] mod tests { use near_async::time::Clock; - use near_chain_configs::Genesis; + use near_chain_configs::{Genesis, MutableConfigValue}; use near_epoch_manager::EpochManager; use near_store::genesis::initialize_genesis_state; use near_store::test_utils::create_test_store; @@ -418,7 +418,7 @@ mod tests { ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); ( diff --git a/chain/chain/src/test_utils.rs b/chain/chain/src/test_utils.rs index 0fe9ffc1387..e9124bcfee9 100644 --- a/chain/chain/src/test_utils.rs +++ b/chain/chain/src/test_utils.rs @@ -1,5 +1,4 @@ mod kv_runtime; -pub mod test_loop; mod validator_schedule; use std::cmp::Ordering; @@ -14,7 +13,7 @@ use crate::types::{AcceptedBlock, ChainConfig, ChainGenesis}; use crate::DoomslugThresholdMode; use crate::{BlockProcessingArtifact, Provenance}; use near_async::time::Clock; -use near_chain_configs::Genesis; +use near_chain_configs::{Genesis, MutableConfigValue}; use near_chain_primitives::Error; use near_epoch_manager::shard_tracker::ShardTracker; use near_epoch_manager::{EpochManager, EpochManagerHandle}; @@ -23,7 +22,7 @@ use near_primitives::hash::CryptoHash; use near_primitives::test_utils::create_test_signer; use near_primitives::types::{AccountId, NumBlocks, NumShards}; use near_primitives::utils::MaybeValidated; -use near_primitives::validator_signer::InMemoryValidatorSigner; +use near_primitives::validator_signer::ValidatorSigner; use near_primitives::version::PROTOCOL_VERSION; use near_store::genesis::initialize_genesis_state; use near_store::test_utils::create_test_store; @@ -76,7 +75,7 @@ pub fn get_chain_with_epoch_length_and_num_shards( ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap() } @@ -120,7 +119,7 @@ pub fn process_block_sync( // TODO(#8190) Improve this testing API. pub fn setup( clock: Clock, -) -> (Chain, Arc, Arc, Arc) { +) -> (Chain, Arc, Arc, Arc) { setup_with_tx_validity_period(clock, 100, 1000) } @@ -128,7 +127,7 @@ pub fn setup_with_tx_validity_period( clock: Clock, tx_validity_period: NumBlocks, epoch_length: u64, -) -> (Chain, Arc, Arc, Arc) { +) -> (Chain, Arc, Arc, Arc) { let store = create_test_store(); let mut genesis = Genesis::test_sharded( clock.clone(), @@ -160,7 +159,7 @@ pub fn setup_with_tx_validity_period( ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); diff --git a/chain/chain/src/test_utils/kv_runtime.rs b/chain/chain/src/test_utils/kv_runtime.rs index 6b267550e81..81a1c6f9030 100644 --- a/chain/chain/src/test_utils/kv_runtime.rs +++ b/chain/chain/src/test_utils/kv_runtime.rs @@ -13,12 +13,13 @@ use near_chain_primitives::Error; use near_crypto::{KeyType, PublicKey, SecretKey, Signature}; use near_epoch_manager::types::BlockHeaderInfo; use near_epoch_manager::{EpochManagerAdapter, RngSeed}; +use near_parameters::RuntimeConfig; use near_pool::types::TransactionGroupIterator; use near_primitives::account::{AccessKey, Account}; use near_primitives::apply::ApplyChunkReason; use near_primitives::block::Tip; use near_primitives::block_header::{Approval, ApprovalInner}; -use near_primitives::congestion_info::CongestionInfo; +use near_primitives::congestion_info::{CongestionInfo, ExtendedCongestionInfo}; use near_primitives::epoch_manager::block_info::BlockInfo; use near_primitives::epoch_manager::epoch_info::EpochInfo; use near_primitives::epoch_manager::EpochConfig; @@ -268,26 +269,16 @@ impl MockEpochManager { None => 0, Some(prev_valset) => prev_valset + 1, }; - ( - prev_next_epoch.clone(), - EpochId(prev_hash), - new_valset, - prev_block_header.height() + 1, - ) + (*prev_next_epoch, EpochId(prev_hash), new_valset, prev_block_header.height() + 1) } else { - ( - prev_epoch.unwrap().clone(), - prev_next_epoch.clone(), - prev_valset.unwrap(), - prev_epoch_start, - ) + (*prev_epoch.unwrap(), *prev_next_epoch, prev_valset.unwrap(), prev_epoch_start) }; - hash_to_next_epoch.insert(prev_hash, next_epoch.clone()); - hash_to_epoch.insert(prev_hash, epoch.clone()); + hash_to_next_epoch.insert(prev_hash, next_epoch); + hash_to_epoch.insert(prev_hash, epoch); hash_to_next_epoch_approvals_req.insert(prev_hash, needs_next_epoch_approvals); - hash_to_valset.insert(epoch.clone(), valset); - hash_to_valset.insert(next_epoch.clone(), valset + 1); + hash_to_valset.insert(epoch, valset); + hash_to_valset.insert(next_epoch, valset + 1); epoch_start_map.insert(prev_hash, epoch_start); Ok((epoch, valset as usize % self.validators_by_valset.len(), next_epoch)) @@ -309,7 +300,7 @@ impl MockEpochManager { .read() .unwrap() .get(epoch_id) - .ok_or_else(|| EpochError::EpochOutOfBounds(epoch_id.clone()))? as usize + .ok_or_else(|| EpochError::EpochOutOfBounds(*epoch_id))? as usize % self.validators_by_valset.len()) } @@ -486,6 +477,8 @@ impl EpochManagerAdapter for MockEpochManager { avg_hidden_validator_seats_per_shard: vec![1, 1], block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, + target_validator_mandates_per_shard: 1, validator_max_kickout_stake_perc: 0, online_min_threshold: Ratio::new(1i32, 4i32), online_max_threshold: Ratio::new(3i32, 4i32), @@ -629,7 +622,7 @@ impl EpochManagerAdapter for MockEpochManager { } match (self.get_valset_for_epoch(epoch_id), self.get_valset_for_epoch(other_epoch_id)) { (Ok(index1), Ok(index2)) => Ok(index1.cmp(&index2)), - _ => Err(EpochError::EpochOutOfBounds(epoch_id.clone())), + _ => Err(EpochError::EpochOutOfBounds(*epoch_id)), } } @@ -771,7 +764,7 @@ impl EpochManagerAdapter for MockEpochManager { return Ok((validator_stake.clone(), false)); } } - Err(EpochError::NotAValidator(account_id.clone(), epoch_id.clone())) + Err(EpochError::NotAValidator(account_id.clone(), *epoch_id)) } fn get_fisherman_by_account_id( @@ -780,7 +773,7 @@ impl EpochManagerAdapter for MockEpochManager { _last_known_block_hash: &CryptoHash, account_id: &AccountId, ) -> Result<(ValidatorStake, bool), EpochError> { - Err(EpochError::NotAValidator(account_id.clone(), epoch_id.clone())) + Err(EpochError::NotAValidator(account_id.clone(), *epoch_id)) } fn get_validator_info( @@ -964,6 +957,25 @@ impl EpochManagerAdapter for MockEpochManager { Ok(true) } + fn cares_about_shard_in_epoch( + &self, + epoch_id: EpochId, + account_id: &AccountId, + shard_id: ShardId, + ) -> Result { + // This `unwrap` here tests that in all code paths we check that the epoch exists before + // we check if we care about a shard. Please do not remove the unwrap, fix the logic of + // the calling function. + let epoch_valset = self.get_valset_for_epoch(&epoch_id).unwrap(); + let chunk_producers = self.get_chunk_producers(epoch_valset, shard_id); + for validator in chunk_producers { + if validator.account_id() == account_id { + return Ok(true); + } + } + Ok(false) + } + fn cares_about_shard_from_prev_block( &self, parent_hash: &CryptoHash, @@ -1042,6 +1054,13 @@ impl EpochManagerAdapter for MockEpochManager { #[cfg(feature = "new_epoch_sync")] fn force_update_aggregator(&self, _epoch_id: &EpochId, _hash: &CryptoHash) {} + + fn get_epoch_all_validators( + &self, + _epoch_id: &EpochId, + ) -> Result, EpochError> { + Ok(self.validators.iter().map(|(_, v)| v.clone()).collect()) + } } impl RuntimeAdapter for KeyValueRuntime { @@ -1089,6 +1108,7 @@ impl RuntimeAdapter for KeyValueRuntime { _verify_signature: bool, _epoch_id: &EpochId, _current_protocol_version: ProtocolVersion, + _receiver_congestion_info: Option, ) -> Result, Error> { Ok(None) } @@ -1460,7 +1480,14 @@ impl RuntimeAdapter for KeyValueRuntime { } fn get_protocol_config(&self, _epoch_id: &EpochId) -> Result { - unreachable!("get_protocol_config should not be called in KeyValueRuntime"); + Err(Error::Other("get_protocol_config should not be used in KeyValueRuntime".into())) + } + + fn get_runtime_config( + &self, + _protocol_version: ProtocolVersion, + ) -> Result { + Ok(RuntimeConfig::test()) } fn will_shard_layout_change_next_epoch( diff --git a/chain/chain/src/test_utils/test_loop.rs b/chain/chain/src/test_utils/test_loop.rs deleted file mode 100644 index 67ab89a355c..00000000000 --- a/chain/chain/src/test_utils/test_loop.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::state_snapshot_actor::{ - StateSnapshotActor, StateSnapshotSenderForClientMessage, - StateSnapshotSenderForStateSnapshotMessage, -}; -use near_async::messaging::Handler; -use near_async::test_loop::event_handler::LoopEventHandler; - -pub fn forward_state_snapshot_messages_from_state_snapshot( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, actor: &mut StateSnapshotActor| match msg { - StateSnapshotSenderForStateSnapshotMessage::_create_snapshot(msg) => actor.handle(msg), - }) -} - -pub fn forward_state_snapshot_messages_from_client( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, actor: &mut StateSnapshotActor| match msg { - StateSnapshotSenderForClientMessage::_0(msg) => actor.handle(msg), - }) -} diff --git a/chain/chain/src/test_utils/validator_schedule.rs b/chain/chain/src/test_utils/validator_schedule.rs index cefbd0eeb6d..383117db799 100644 --- a/chain/chain/src/test_utils/validator_schedule.rs +++ b/chain/chain/src/test_utils/validator_schedule.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; /// the KeyValue runtime. /// /// In the real runtime, we use complex algorithm based on randomness and stake -/// to select the validators. For for tests though, we just want to select them +/// to select the validators. For tests though, we just want to select them /// by fiat. /// /// Conventional short name for `ValidatorSchedule` is `vs`. diff --git a/chain/chain/src/tests/doomslug.rs b/chain/chain/src/tests/doomslug.rs index 931029c5bd7..1757c08e10c 100644 --- a/chain/chain/src/tests/doomslug.rs +++ b/chain/chain/src/tests/doomslug.rs @@ -47,24 +47,22 @@ fn one_iter( .collect::>(); let signers = account_ids .iter() - .map(|account_id| Arc::new(create_test_signer(account_id))) + .map(|account_id| Some(Arc::new(create_test_signer(account_id)))) .collect::>(); let clock = FakeClock::new(Utc::UNIX_EPOCH); - let mut doomslugs = signers - .iter() - .map(|signer| { - Doomslug::new( - clock.clock(), - 0, - Duration::milliseconds(200), - Duration::milliseconds(1000), - Duration::milliseconds(100), - delta * 20, // some arbitrary number larger than delta * 6 - Some(signer.clone()), - DoomslugThresholdMode::TwoThirds, - ) - }) - .collect::>(); + let mut doomslugs: Vec<_> = std::iter::repeat_with(|| { + Doomslug::new( + clock.clock(), + 0, + Duration::milliseconds(200), + Duration::milliseconds(1000), + Duration::milliseconds(100), + delta * 20, // some arbitrary number larger than delta * 6 + DoomslugThresholdMode::TwoThirds, + ) + }) + .take(signers.len()) + .collect(); let started = clock.now(); let gst = clock.now() + time_to_gst; @@ -149,8 +147,8 @@ fn one_iter( block_queue = new_block_queue; // 3. Process timers - for ds in doomslugs.iter_mut() { - for approval in ds.process_timer() { + for (i, ds) in doomslugs.iter_mut().enumerate() { + for approval in ds.process_timer(&signers[i]) { approval_queue.push((approval, get_msg_delivery_time(clock.now(), gst, delta))); } } diff --git a/chain/chain/src/tests/garbage_collection.rs b/chain/chain/src/tests/garbage_collection.rs index 956ace33a44..b7a2c25cb4c 100644 --- a/chain/chain/src/tests/garbage_collection.rs +++ b/chain/chain/src/tests/garbage_collection.rs @@ -20,7 +20,7 @@ use near_primitives::merkle::PartialMerkleTree; use near_primitives::shard_layout::ShardUId; use near_primitives::test_utils::{create_test_signer, TestBlockBuilder}; use near_primitives::types::{BlockHeight, NumBlocks, StateRoot}; -use near_primitives::validator_signer::InMemoryValidatorSigner; +use near_primitives::validator_signer::ValidatorSigner; use near_store::test_utils::gen_changes; use near_store::{DBCol, ShardTries, Trie, WrappedTrieChanges}; @@ -57,7 +57,7 @@ fn do_fork( TestBlockBuilder::new(Clock::real(), &prev_block, signer.clone()).build() } else { let prev_hash = prev_block.hash(); - let epoch_id = prev_block.header().next_epoch_id().clone(); + let epoch_id = *prev_block.header().next_epoch_id(); if verbose { println!( "Creating block with new epoch id {:?} @{}", @@ -67,8 +67,8 @@ fn do_fork( } let next_bp_hash = Chain::compute_bp_hash( chain.epoch_manager.as_ref(), - next_epoch_id.clone(), - epoch_id.clone(), + next_epoch_id, + epoch_id, &prev_hash, ) .unwrap(); @@ -730,7 +730,7 @@ fn add_block( epoch_manager: &dyn EpochManagerAdapter, prev_block: &mut Block, blocks: &mut Vec, - signer: Arc, + signer: Arc, height: u64, ) { let next_epoch_id = epoch_manager @@ -742,14 +742,9 @@ fn add_block( TestBlockBuilder::new(Clock::real(), &prev_block, signer).height(height).build() } else { let prev_hash = prev_block.hash(); - let epoch_id = prev_block.header().next_epoch_id().clone(); - let next_bp_hash = Chain::compute_bp_hash( - epoch_manager, - next_epoch_id.clone(), - epoch_id.clone(), - &prev_hash, - ) - .unwrap(); + let epoch_id = *prev_block.header().next_epoch_id(); + let next_bp_hash = + Chain::compute_bp_hash(epoch_manager, next_epoch_id, epoch_id, &prev_hash).unwrap(); TestBlockBuilder::new(Clock::real(), &prev_block, signer) .height(height) .epoch_id(epoch_id) diff --git a/chain/chain/src/tests/simple_chain.rs b/chain/chain/src/tests/simple_chain.rs index 3243f9c98a4..8fa5ffab107 100644 --- a/chain/chain/src/tests/simple_chain.rs +++ b/chain/chain/src/tests/simple_chain.rs @@ -34,7 +34,7 @@ fn build_chain() { if cfg!(feature = "nightly") { insta::assert_snapshot!(hash, @"C3zeKRZubVungxfrSdq379TSCYnuz2YzjEkcJTdm3pU4"); } else { - insta::assert_snapshot!(hash, @"2WHohfYksQnwKwSEoTKpkseu2RWthbGf9kmGetgHgfQQ"); + insta::assert_snapshot!(hash, @"EKBbsbiindwuPwbiARE9LevUffurNhprbSaUjgPKCwEq"); } for i in 1..5 { @@ -52,17 +52,18 @@ fn build_chain() { if cfg!(feature = "nightly") { insta::assert_snapshot!(hash, @"EjLaoHRiAdRp2NcDqwbMcAYYxGfcv5R7GuYUNfRpaJvB"); } else { - insta::assert_snapshot!(hash, @"HJuuENeSwwikoR9BZA7cSonxAPZgY5mKQWL2pSXwjAwZ"); + insta::assert_snapshot!(hash, @"9Ag5sa6bF9knuJKe9XECTKZi7HwtDhCSxCZ8P9AdSvWH"); } } #[test] fn build_chain_with_orphans() { init_test_logger(); - let (mut chain, _, _, signer) = setup(Clock::real()); + let clock = Clock::real(); + let (mut chain, _, _, signer) = setup(clock.clone()); let mut blocks = vec![chain.get_block(&chain.genesis().hash().clone()).unwrap()]; for i in 1..4 { - let block = TestBlockBuilder::new(Clock::real(), &blocks[i - 1], signer.clone()).build(); + let block = TestBlockBuilder::new(clock.clone(), &blocks[i - 1], signer.clone()).build(); blocks.push(block); } let last_block = &blocks[blocks.len() - 1]; @@ -74,8 +75,8 @@ fn build_chain_with_orphans() { last_block.header().block_ordinal() + 1, last_block.chunks().iter().cloned().collect(), vec![vec![]; last_block.chunks().len()], - last_block.header().epoch_id().clone(), - last_block.header().next_epoch_id().clone(), + *last_block.header().epoch_id(), + *last_block.header().next_epoch_id(), None, vec![], Ratio::from_integer(0), @@ -87,7 +88,8 @@ fn build_chain_with_orphans() { &*signer, *last_block.header().next_bp_hash(), CryptoHash::default(), - Utc::UNIX_EPOCH, + clock, + None, ); assert_matches!(chain.process_block_test(&None, block).unwrap_err(), Error::Orphan); assert_matches!( diff --git a/chain/chain/src/types.rs b/chain/chain/src/types.rs index 736b3279e33..42789ded60f 100644 --- a/chain/chain/src/types.rs +++ b/chain/chain/src/types.rs @@ -6,11 +6,13 @@ use near_chain_configs::ProtocolConfig; use near_chain_configs::ReshardingConfig; use near_chain_primitives::Error; pub use near_epoch_manager::EpochManagerAdapter; +use near_parameters::RuntimeConfig; use near_pool::types::TransactionGroupIterator; use near_primitives::apply::ApplyChunkReason; pub use near_primitives::block::{Block, BlockHeader, Tip}; use near_primitives::challenge::{ChallengesResult, PartialState}; use near_primitives::checked_feature; +use near_primitives::congestion_info::BlockCongestionInfo; use near_primitives::congestion_info::CongestionInfo; use near_primitives::congestion_info::ExtendedCongestionInfo; use near_primitives::errors::InvalidTxError; @@ -291,14 +293,14 @@ pub struct ApplyChunkBlockContext { pub gas_price: Balance, pub challenges_result: ChallengesResult, pub random_seed: CryptoHash, - pub congestion_info: HashMap, + pub congestion_info: BlockCongestionInfo, } impl ApplyChunkBlockContext { pub fn from_header( header: &BlockHeader, gas_price: Balance, - congestion_info: HashMap, + congestion_info: BlockCongestionInfo, ) -> Self { Self { height: header.height(), @@ -349,7 +351,7 @@ pub struct PrepareTransactionsBlockContext { pub next_gas_price: Balance, pub height: BlockHeight, pub block_hash: CryptoHash, - pub congestion_info: HashMap, + pub congestion_info: BlockCongestionInfo, } impl From<&Block> for PrepareTransactionsBlockContext { @@ -359,7 +361,7 @@ impl From<&Block> for PrepareTransactionsBlockContext { next_gas_price: header.next_gas_price(), height: header.height(), block_hash: *header.hash(), - congestion_info: block.shards_congestion_info(), + congestion_info: block.block_congestion_info(), } } } @@ -417,6 +419,7 @@ pub trait RuntimeAdapter: Send + Sync { verify_signature: bool, epoch_id: &EpochId, current_protocol_version: ProtocolVersion, + receiver_congestion_info: Option, ) -> Result, Error>; /// Returns an ordered list of valid transactions from the pool up the given limits. @@ -522,6 +525,9 @@ pub trait RuntimeAdapter: Send + Sync { ) -> bool; fn get_protocol_config(&self, epoch_id: &EpochId) -> Result; + + fn get_runtime_config(&self, protocol_version: ProtocolVersion) + -> Result; } /// The last known / checked height and time when we have processed it. @@ -548,8 +554,14 @@ mod tests { #[test] fn test_block_produce() { let shard_ids: Vec<_> = (0..32).collect(); - let genesis_chunks = - genesis_chunks(vec![Trie::EMPTY_ROOT], &shard_ids, 1_000_000, 0, PROTOCOL_VERSION); + let genesis_chunks = genesis_chunks( + vec![Trie::EMPTY_ROOT], + vec![Default::default(); shard_ids.len()], + &shard_ids, + 1_000_000, + 0, + PROTOCOL_VERSION, + ); let genesis_bps: Vec = Vec::new(); let genesis = Block::genesis( PROTOCOL_VERSION, diff --git a/chain/chain/src/validate.rs b/chain/chain/src/validate.rs index 304a506145d..0c0de032186 100644 --- a/chain/chain/src/validate.rs +++ b/chain/chain/src/validate.rs @@ -15,7 +15,6 @@ use near_primitives::sharding::{ShardChunk, ShardChunkHeader}; use near_primitives::transaction::SignedTransaction; use near_primitives::types::chunk_extra::ChunkExtra; use near_primitives::types::{AccountId, BlockHeight, EpochId, Nonce}; -use near_primitives::version::{ProtocolFeature, ProtocolVersion}; use crate::types::RuntimeAdapter; use crate::{byzantine_assert, Chain}; @@ -126,14 +125,10 @@ pub fn validate_chunk_with_chunk_extra( }; let (outgoing_receipts_root, _) = merklize(&outgoing_receipts_hashes); - let header_epoch_id = epoch_manager.get_epoch_id_from_prev_block(prev_block_hash)?; - let header_protocol_version = epoch_manager.get_epoch_protocol_version(&header_epoch_id)?; - validate_chunk_with_chunk_extra_and_receipts_root( prev_chunk_extra, chunk_header, &outgoing_receipts_root, - header_protocol_version, ) } @@ -142,7 +137,6 @@ pub fn validate_chunk_with_chunk_extra_and_receipts_root( prev_chunk_extra: &ChunkExtra, chunk_header: &ShardChunkHeader, outgoing_receipts_root: &CryptoHash, - header_protocol_version: ProtocolVersion, ) -> Result<(), Error> { if *prev_chunk_extra.state_root() != chunk_header.prev_state_root() { return Err(Error::InvalidStateRoot); @@ -183,11 +177,7 @@ pub fn validate_chunk_with_chunk_extra_and_receipts_root( return Err(Error::InvalidGasLimit); } - validate_congestion_info( - &prev_chunk_extra.congestion_info(), - &chunk_header.congestion_info(), - header_protocol_version, - )?; + validate_congestion_info(&prev_chunk_extra.congestion_info(), &chunk_header.congestion_info())?; Ok(()) } @@ -199,40 +189,25 @@ pub fn validate_chunk_with_chunk_extra_and_receipts_root( fn validate_congestion_info( extra_congestion_info: &Option, header_congestion_info: &Option, - header_protocol_version: ProtocolVersion, ) -> Result<(), Error> { - // The congestion info should be Some iff the congestion control features is enabled. - let enabled = ProtocolFeature::CongestionControl.enabled(header_protocol_version); - if header_congestion_info.is_some() != enabled { - return Err(Error::InvalidCongestionInfo); - } - match (extra_congestion_info, header_congestion_info) { // If both are none then there is no congestion info to validate. (None, None) => Ok(()), - // If the congestion control is enabled in the previous chunk then it should - // also be enabled in the current chunk. - (Some(_), None) => Err(Error::InvalidCongestionInfo), - // At the epoch boundary where congestion control was enabled the chunk - // extra does not have the congestion control enabled and the header does - // have it enabled. The chunk extra of the previous chunk does not have - // congestion info so the congestion info in the current chunk header should - // be set to the default one. - (None, Some(_)) => { - if header_congestion_info == &Some(CongestionInfo::default()) { - Ok(()) - } else { - Err(Error::InvalidCongestionInfo) - } - } + // It is invalid to have one None and one Some. The congestion info in + // header should always be derived from the congestion info in extra. + (None, Some(_)) | (Some(_), None) => Err(Error::InvalidCongestionInfo(format!( + "Congestion Information mismatch. extra: {:?}, header: {:?}", + extra_congestion_info, header_congestion_info + ))), // Congestion Info is present in both the extra and the header. Validate it. - (Some(extra), Some(header)) => { - if !CongestionInfo::validate_extra_and_header(extra, header) { - Err(Error::InvalidCongestionInfo) - } else { - Ok(()) - } - } + (Some(extra), Some(header)) => CongestionInfo::validate_extra_and_header(extra, header) + .then_some(()) + .ok_or_else(|| { + Error::InvalidCongestionInfo(format!( + "Congestion Information validate error. extra: {:?}, header: {:?}", + extra, header + )) + }), } } @@ -481,7 +456,7 @@ mod tests { nonce, account_id, "bob".parse().unwrap(), - &signer, + &signer.into(), 10, CryptoHash::default(), ) diff --git a/chain/chunks/src/chunk_cache.rs b/chain/chunks/src/chunk_cache.rs index d910cd62631..2ac1f0b0e70 100644 --- a/chain/chunks/src/chunk_cache.rs +++ b/chain/chunks/src/chunk_cache.rs @@ -296,7 +296,7 @@ mod tests { CryptoHash::default(), CryptoHash::default(), vec![], - &signer, + &signer.into(), )) } diff --git a/chain/chunks/src/client.rs b/chain/chunks/src/client.rs index 30fdecbfe0b..6e0e2052d2f 100644 --- a/chain/chunks/src/client.rs +++ b/chain/chunks/src/client.rs @@ -229,7 +229,7 @@ mod tests { nonce, signer_id.clone(), receiver_id.clone(), - &signer, + &signer.into(), deposit, CryptoHash::default(), ); diff --git a/chain/chunks/src/lib.rs b/chain/chunks/src/lib.rs index 9409eabeaa4..9c946082a24 100644 --- a/chain/chunks/src/lib.rs +++ b/chain/chunks/src/lib.rs @@ -4,5 +4,4 @@ pub mod client; pub mod logic; pub mod metrics; pub mod shards_manager_actor; -pub mod test_loop; pub mod test_utils; diff --git a/chain/chunks/src/shards_manager_actor.rs b/chain/chunks/src/shards_manager_actor.rs index b0f6288ac76..92f2b8c3d6b 100644 --- a/chain/chunks/src/shards_manager_actor.rs +++ b/chain/chunks/src/shards_manager_actor.rs @@ -98,6 +98,7 @@ use near_chain::byzantine_assert; use near_chain::chunks_store::ReadOnlyChunksStore; use near_chain::near_chain_primitives::error::Error::DBNotFoundErr; use near_chain::types::EpochManagerAdapter; +use near_chain_configs::MutableValidatorSigner; pub use near_chunks_primitives::Error; use near_epoch_manager::shard_tracker::ShardTracker; use near_network::shards_manager::ShardsManagerRequestFromNetwork; @@ -116,8 +117,8 @@ use near_primitives::receipt::Receipt; use near_primitives::reed_solomon::{reed_solomon_decode, reed_solomon_encode}; use near_primitives::sharding::{ ChunkHash, EncodedShardChunk, EncodedShardChunkBody, PartialEncodedChunk, - PartialEncodedChunkPart, PartialEncodedChunkV2, ReceiptProof, ShardChunk, ShardChunkHeader, - ShardProof, TransactionReceipt, + PartialEncodedChunkPart, PartialEncodedChunkV2, ShardChunk, ShardChunkHeader, + TransactionReceipt, }; use near_primitives::transaction::SignedTransaction; use near_primitives::types::validator_stake::ValidatorStake; @@ -133,6 +134,7 @@ use rand::seq::IteratorRandom; use rand::Rng; use reed_solomon_erasure::galois_8::ReedSolomon; use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; use std::sync::Arc; use tracing::{debug, debug_span, error, warn}; @@ -242,7 +244,10 @@ impl RequestPool { pub struct ShardsManagerActor { clock: time::Clock, - me: Option, + /// Contains validator info about this node. This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + validator_signer: MutableValidatorSigner, store: ReadOnlyChunksStore, epoch_manager: Arc, @@ -292,7 +297,7 @@ pub fn start_shards_manager( shard_tracker: ShardTracker, network_adapter: Sender, client_adapter_for_shards_manager: Sender, - me: Option, + validator_signer: MutableValidatorSigner, store: Store, chunk_request_retry_period: Duration, ) -> (actix::Addr>, actix::ArbiterHandle) { @@ -309,7 +314,7 @@ pub fn start_shards_manager( let chunks_store = ReadOnlyChunksStore::new(store); let shards_manager = ShardsManagerActor::new( Clock::real(), - me, + validator_signer, epoch_manager, shard_tracker, network_adapter, @@ -330,7 +335,7 @@ pub fn start_shards_manager( impl ShardsManagerActor { pub fn new( clock: time::Clock, - me: Option, + validator_signer: MutableValidatorSigner, epoch_manager: Arc, shard_tracker: ShardTracker, network_adapter: Sender, @@ -342,7 +347,7 @@ impl ShardsManagerActor { ) -> Self { Self { clock, - me, + validator_signer, store, epoch_manager: epoch_manager.clone(), shard_tracker, @@ -360,7 +365,9 @@ impl ShardsManagerActor { CHUNK_REQUEST_SWITCH_TO_FULL_FETCH, CHUNK_REQUEST_RETRY_MAX, ), - chunk_forwards_cache: lru::LruCache::new(CHUNK_FORWARD_CACHE_SIZE), + chunk_forwards_cache: lru::LruCache::new( + NonZeroUsize::new(CHUNK_FORWARD_CACHE_SIZE).unwrap(), + ), chain_head: initial_chain_head, chain_header_head: initial_chain_header_head, chunk_request_retry_period, @@ -381,7 +388,7 @@ impl ShardsManagerActor { ) } - pub fn update_chain_heads(&mut self, head: Tip, header_head: Tip) { + fn update_chain_heads(&mut self, head: Tip, header_head: Tip) { self.encoded_chunks.update_largest_seen_height( head.height, &self.requested_partial_encoded_chunks.requests, @@ -399,6 +406,7 @@ impl ShardsManagerActor { force_request_full: bool, request_own_parts_from_others: bool, request_from_archival: bool, + me: Option<&AccountId>, ) -> Result<(), near_chain::Error> { let _span = tracing::debug_span!( target: "chunks", @@ -414,7 +422,7 @@ impl ShardsManagerActor { let request_full = force_request_full || cares_about_shard_this_or_next_epoch( - self.me.as_ref(), + me, ancestor_hash, shard_id, true, @@ -442,7 +450,6 @@ impl ShardsManagerActor { // from the target account or any eligible peer of the node (See comments in // AccountIdOrPeerTrackingShard for when target account is used or peer is used) - let me = self.me.as_ref(); // A account that is either the original chunk producer or a random block producer tracking // the shard let shard_representative_target = if !request_own_parts_from_others @@ -451,7 +458,7 @@ impl ShardsManagerActor { { Some(chunk_producer_account_id) } else { - self.get_random_target_tracking_shard(ancestor_hash, shard_id)? + self.get_random_target_tracking_shard(ancestor_hash, shard_id, me)? }; let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(ancestor_hash)?; @@ -488,7 +495,7 @@ impl ShardsManagerActor { let shards_to_fetch_receipts = // TODO: only keep shards for which we don't have receipts yet - if request_full { HashSet::new() } else { self.get_tracking_shards(ancestor_hash) }; + if request_full { HashSet::new() } else { self.get_tracking_shards(ancestor_hash, me) }; // The loop below will be sending PartialEncodedChunkRequestMsg to various block producers. // We need to send such a message to the original chunk producer if we do not have the receipts @@ -552,6 +559,7 @@ impl ShardsManagerActor { &self, parent_hash: &CryptoHash, shard_id: ShardId, + me: Option<&AccountId>, ) -> Result, near_chain::Error> { let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(parent_hash).unwrap(); let block_producers = self @@ -568,7 +576,7 @@ impl ShardsManagerActor { false, &self.shard_tracker, ) - && self.me.as_ref() != Some(&account_id) + && me != Some(&account_id) { Some(account_id) } else { @@ -579,7 +587,11 @@ impl ShardsManagerActor { Ok(block_producers.choose(&mut rand::thread_rng())) } - fn get_tracking_shards(&self, parent_hash: &CryptoHash) -> HashSet { + fn get_tracking_shards( + &self, + parent_hash: &CryptoHash, + me: Option<&AccountId>, + ) -> HashSet { let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(parent_hash).unwrap(); self.epoch_manager .shard_ids(&epoch_id) @@ -587,7 +599,7 @@ impl ShardsManagerActor { .into_iter() .filter(|chunk_shard_id| { cares_about_shard_this_or_next_epoch( - self.me.as_ref(), + me, parent_hash, *chunk_shard_id, true, @@ -607,9 +619,10 @@ impl ShardsManagerActor { prev_hash: &CryptoHash, shard_id: ShardId, next_chunk_height: BlockHeight, + me: Option<&AccountId>, ) -> Result { // chunks will not be forwarded to non-validators - let me = match self.me.as_ref() { + let me = match me { None => return Ok(false), Some(it) => it, }; @@ -631,8 +644,12 @@ impl ShardsManagerActor { /// Only marks this chunk as being requested /// Note no requests are actually sent at this point. - fn request_chunk_single_mark_only(&mut self, chunk_header: &ShardChunkHeader) { - self.request_chunk_single(chunk_header, *chunk_header.prev_block_hash(), true) + fn request_chunk_single_mark_only( + &mut self, + chunk_header: &ShardChunkHeader, + me: Option<&AccountId>, + ) { + self.request_chunk_single(chunk_header, *chunk_header.prev_block_hash(), true, me) } /// send partial chunk requests for one chunk @@ -648,6 +665,7 @@ impl ShardsManagerActor { chunk_header: &ShardChunkHeader, ancestor_hash: CryptoHash, mark_only: bool, + me: Option<&AccountId>, ) { let height = chunk_header.height_created(); let shard_id = chunk_header.shard_id(); @@ -707,7 +725,7 @@ impl ShardsManagerActor { && self.chain_header_head.prev_block_hash != prev_block_hash; let should_wait_for_chunk_forwarding = - self.should_wait_for_chunk_forwarding(&ancestor_hash, chunk_header.shard_id(), chunk_header.height_created()+1).unwrap_or_else(|_| { + self.should_wait_for_chunk_forwarding(&ancestor_hash, chunk_header.shard_id(), chunk_header.height_created()+1, me).unwrap_or_else(|_| { // ancestor_hash must be accepted because we don't request missing chunks through this // this function for orphans debug_assert!(false, "{:?} must be accepted", ancestor_hash); @@ -730,6 +748,7 @@ impl ShardsManagerActor { false, old_block, fetch_from_archival, + me, ); if let Err(err) = request_result { error!(target: "chunks", "Error during requesting partial encoded chunk: {}", err); @@ -743,10 +762,11 @@ impl ShardsManagerActor { /// `chunks_to_request`: chunks to request /// `prev_hash`: hash of prev block of the block we are requesting missing chunks for /// The function assumes the prev block is accepted - pub fn request_chunks( + fn request_chunks( &mut self, chunks_to_request: Vec, prev_hash: CryptoHash, + me: Option<&AccountId>, ) { let _span = debug_span!( target: "chunks", @@ -755,7 +775,7 @@ impl ShardsManagerActor { num_chunks_to_request = chunks_to_request.len()) .entered(); for chunk_header in chunks_to_request { - self.request_chunk_single(&chunk_header, prev_hash, false); + self.request_chunk_single(&chunk_header, prev_hash, false, me); } } @@ -767,11 +787,12 @@ impl ShardsManagerActor { /// 1) it is from the same epoch than `epoch_id` /// 2) it is processed /// If the above conditions are not met, the request will be dropped - pub fn request_chunks_for_orphan( + fn request_chunks_for_orphan( &mut self, chunks_to_request: Vec, epoch_id: &EpochId, ancestor_hash: CryptoHash, + me: Option<&AccountId>, ) { let _span = debug_span!( target: "chunks", @@ -787,7 +808,7 @@ impl ShardsManagerActor { } for chunk_header in chunks_to_request { - self.request_chunk_single(&chunk_header, ancestor_hash, false) + self.request_chunk_single(&chunk_header, ancestor_hash, false, me) } } @@ -799,6 +820,7 @@ impl ShardsManagerActor { header_head_height = self.chain_header_head.height, pool_size = self.requested_partial_encoded_chunks.len()) .entered(); + let me = self.validator_signer.get().map(|signer| signer.validator_id().clone()); // Process chunk one part requests. let requests = self.requested_partial_encoded_chunks.fetch(self.clock.now().into()); for (chunk_hash, chunk_request) in requests { @@ -823,6 +845,7 @@ impl ShardsManagerActor { || self.clock.now() - chunk_request.added >= self.requested_partial_encoded_chunks.switch_to_others_duration, fetch_from_archival, + me.as_ref(), ) { Ok(()) => {} Err(err) => { @@ -833,34 +856,11 @@ impl ShardsManagerActor { } } - pub fn receipts_recipient_filter( - from_shard_id: ShardId, - tracking_shards: T, - receipts_by_shard: &HashMap>, - proofs: &[MerklePath], - ) -> Vec - where - T: IntoIterator, - { - tracking_shards - .into_iter() - .map(|to_shard_id| { - let receipts = - receipts_by_shard.get(&to_shard_id).cloned().unwrap_or_else(Vec::new); - let shard_proof = ShardProof { - from_shard_id, - to_shard_id, - proof: proofs[to_shard_id as usize].clone(), - }; - ReceiptProof(receipts, shard_proof) - }) - .collect() - } - - pub fn process_partial_encoded_chunk_request( + fn process_partial_encoded_chunk_request( &self, request: PartialEncodedChunkRequestMsg, route_back: CryptoHash, + me: Option<&AccountId>, ) { let _span = tracing::debug_span!( target: "chunks", @@ -871,7 +871,7 @@ impl ShardsManagerActor { chunk_hash = %request.chunk_hash.0, part_ords = ?request.part_ords, shards = ?request.tracking_shards, - account = ?self.me.as_ref()); + account = ?me); let started = self.clock.now(); let (source, response) = self.prepare_partial_encoded_chunk_response(request); @@ -995,7 +995,7 @@ impl ShardsManagerActor { /// Looks up the given part_ords and tracking_shards from the partial chunks /// storage, appending any we have found into the response, and deleting those we /// have found from part_ords and tracking_shards. - pub fn lookup_partial_encoded_chunk_from_partial_chunk_storage( + fn lookup_partial_encoded_chunk_from_partial_chunk_storage( part_ords: HashSet, tracking_shards: HashSet, response: &mut PartialEncodedChunkResponseMsg, @@ -1149,6 +1149,7 @@ impl ShardsManagerActor { fn decode_encoded_chunk_if_complete( &mut self, mut encoded_chunk: EncodedShardChunk, + me: Option<&AccountId>, ) -> Result, Error> { match self.check_chunk_complete(&mut encoded_chunk) { ChunkStatus::Complete(merkle_paths) => { @@ -1156,7 +1157,7 @@ impl ShardsManagerActor { match decode_encoded_chunk( &encoded_chunk, merkle_paths, - self.me.as_ref(), + me, self.epoch_manager.as_ref(), &self.shard_tracker, ) { @@ -1263,9 +1264,10 @@ impl ShardsManagerActor { } } - pub fn process_partial_encoded_chunk_forward( + fn process_partial_encoded_chunk_forward( &mut self, forward: PartialEncodedChunkForwardMsg, + me: Option<&AccountId>, ) -> Result<(), Error> { let maybe_header = self .validate_partial_encoded_chunk_forward(&forward) @@ -1302,7 +1304,7 @@ impl ShardsManagerActor { parts: forward.parts, prev_outgoing_receipts: Vec::new(), }); - self.process_partial_encoded_chunk(MaybeValidated::from_validated(partial_chunk))?; + self.process_partial_encoded_chunk(MaybeValidated::from_validated(partial_chunk), me)?; Ok(()) } @@ -1446,9 +1448,10 @@ impl ShardsManagerActor { /// are needed for processing the full chunk /// ProcessPartialEncodedChunkResult::HaveAllPartsAndReceipts: if all parts and /// receipts in the chunk are received and the chunk has been processed. - pub fn process_partial_encoded_chunk( + fn process_partial_encoded_chunk( &mut self, partial_encoded_chunk: MaybeValidated, + me: Option<&AccountId>, ) -> Result { let partial_encoded_chunk = partial_encoded_chunk.map(|chunk| PartialEncodedChunkV2::from(chunk)); @@ -1555,6 +1558,7 @@ impl ShardsManagerActor { new_part_ords, &epoch_id, &partial_encoded_chunk.header.prev_block_hash(), + me, )?; } else { let epoch_id = self @@ -1565,6 +1569,7 @@ impl ShardsManagerActor { new_part_ords, &epoch_id, &self.chain_head.last_block_hash.clone(), + me, )?; }; @@ -1572,39 +1577,41 @@ impl ShardsManagerActor { self.insert_header_if_not_exists_and_process_cached_chunk_forwards(header); // 5. Check if the chunk is complete; requesting more if not. - let result = self.try_process_chunk_parts_and_receipts(header)?; + let result = self.try_process_chunk_parts_and_receipts(header, me)?; match result { ProcessPartialEncodedChunkResult::NeedMorePartsOrReceipts => { // This may be the first time we see this chunk, so mark it in the request pool. // If it's not already requested for, next time we resend requests we would // request the chunk. - self.request_chunk_single_mark_only(header); + self.request_chunk_single_mark_only(header, me); } _ => {} } Ok(result) } - pub fn process_partial_encoded_chunk_response( + fn process_partial_encoded_chunk_response( &mut self, response: PartialEncodedChunkResponseMsg, + me: Option<&AccountId>, ) -> Result<(), Error> { let header = self.get_partial_encoded_chunk_header(&response.chunk_hash)?; let partial_chunk = PartialEncodedChunk::new(header, response.parts, response.receipts); // We already know the header signature is valid because we read it from the // shard manager. - self.process_partial_encoded_chunk(MaybeValidated::from_validated(partial_chunk))?; + self.process_partial_encoded_chunk(MaybeValidated::from_validated(partial_chunk), me)?; Ok(()) } /// Let the ShardsManager know about the chunk header, when encountering that chunk header /// from the block and the chunk is possibly not yet known to the ShardsManager. - pub fn process_chunk_header_from_block( + fn process_chunk_header_from_block( &mut self, header: &ShardChunkHeader, + me: Option<&AccountId>, ) -> Result<(), Error> { if self.insert_header_if_not_exists_and_process_cached_chunk_forwards(header) { - self.try_process_chunk_parts_and_receipts(header)?; + self.try_process_chunk_parts_and_receipts(header, me)?; } Ok(()) } @@ -1616,6 +1623,7 @@ impl ShardsManagerActor { fn try_process_chunk_parts_and_receipts( &mut self, header: &ShardChunkHeader, + me: Option<&AccountId>, ) -> Result { let chunk_hash = header.chunk_hash(); let _span = debug_span!( @@ -1673,8 +1681,8 @@ impl ShardsManagerActor { // chunk. See comments in has_all_parts and has_all_receipts to see the conditions. // we can safely unwrap here because we already checked that chunk_hash exist in encoded_chunks let entry = self.encoded_chunks.get(&chunk_hash).unwrap(); - let have_all_parts = self.has_all_parts(&prev_block_hash, entry)?; - let have_all_receipts = self.has_all_receipts(&prev_block_hash, entry)?; + let have_all_parts = self.has_all_parts(&prev_block_hash, entry, me)?; + let have_all_receipts = self.has_all_receipts(&prev_block_hash, entry, me)?; let can_reconstruct = entry.parts.len() >= self.epoch_manager.num_data_parts(); let chunk_producer = self.epoch_manager.get_chunk_producer( @@ -1695,7 +1703,7 @@ impl ShardsManagerActor { let entry = self.encoded_chunks.get(&chunk_hash).unwrap(); let cares_about_shard = cares_about_shard_this_or_next_epoch( - self.me.as_ref(), + me, &prev_block_hash, header.shard_id(), true, @@ -1710,7 +1718,7 @@ impl ShardsManagerActor { header, entry.parts.values(), entry.receipts.values(), - self.me.as_ref(), + me, self.epoch_manager.as_ref(), &self.shard_tracker, ); @@ -1735,7 +1743,7 @@ impl ShardsManagerActor { } let (shard_chunk, partial_chunk) = self - .decode_encoded_chunk_if_complete(encoded_chunk)? + .decode_encoded_chunk_if_complete(encoded_chunk, me)? .expect("decoding shouldn't fail"); // For consistency, only persist shard_chunk if we actually care about the shard. @@ -1772,7 +1780,7 @@ impl ShardsManagerActor { /// This function is needed because chunks in chunk cache will only be marked as complete after /// the previous block is accepted. So we need to check if there are any chunks can be marked as /// complete when a new block is accepted. - pub fn check_incomplete_chunks(&mut self, prev_block_hash: &CryptoHash) { + fn check_incomplete_chunks(&mut self, prev_block_hash: &CryptoHash, me: Option<&AccountId>) { let _span = debug_span!(target: "chunks", "check_incomplete_chunks", ?prev_block_hash).entered(); let mut chunks_to_process = vec![]; @@ -1789,13 +1797,13 @@ impl ShardsManagerActor { chunk_hash = ?header.chunk_hash(), ?prev_block_hash, "try to process incomplete chunk"); - if let Err(err) = self.try_process_chunk_parts_and_receipts(&header) { + if let Err(err) = self.try_process_chunk_parts_and_receipts(&header, me) { error!(target:"chunks", "unexpected error processing orphan chunk {:?}", err) } } } - /// Send the parts of the partial_encoded_chunk that are owned by `self.me` to the + /// Send the parts of the partial_encoded_chunk that are owned by `self.me()` to the /// other validators that are tracking the shard. fn send_partial_encoded_chunk_to_chunk_trackers( &mut self, @@ -1803,8 +1811,9 @@ impl ShardsManagerActor { part_ords: HashSet, epoch_id: &EpochId, latest_block_hash: &CryptoHash, + me: Option<&AccountId>, ) -> Result<(), Error> { - let me = match self.me.as_ref() { + let me = match me { Some(me) => me, None => return Ok(()), }; @@ -1926,11 +1935,12 @@ impl ShardsManagerActor { &self, prev_block_hash: &CryptoHash, chunk_entry: &EncodedChunksCacheEntry, + me: Option<&AccountId>, ) -> Result { let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(prev_block_hash)?; for shard_id in self.epoch_manager.shard_ids(&epoch_id)? { if !chunk_entry.receipts.contains_key(&shard_id) { - if need_receipt(prev_block_hash, shard_id, self.me.as_ref(), &self.shard_tracker) { + if need_receipt(prev_block_hash, shard_id, me, &self.shard_tracker) { return Ok(false); } } @@ -1945,16 +1955,12 @@ impl ShardsManagerActor { &self, prev_block_hash: &CryptoHash, chunk_entry: &EncodedChunksCacheEntry, + me: Option<&AccountId>, ) -> Result { for part_ord in 0..self.epoch_manager.num_total_parts() { let part_ord = part_ord as u64; if !chunk_entry.parts.contains_key(&part_ord) { - if need_part( - prev_block_hash, - part_ord, - self.me.as_ref(), - self.epoch_manager.as_ref(), - )? { + if need_part(prev_block_hash, part_ord, me, self.epoch_manager.as_ref())? { return Ok(false); } } @@ -1976,8 +1982,8 @@ impl ShardsManagerActor { prev_outgoing_receipts: &[Receipt], prev_outgoing_receipts_root: CryptoHash, tx_root: CryptoHash, - congestion_info: CongestionInfo, - signer: &dyn ValidatorSigner, + congestion_info: Option, + signer: &ValidatorSigner, rs: &ReedSolomon, protocol_version: ProtocolVersion, ) -> Result<(EncodedShardChunk, Vec), Error> { @@ -2003,12 +2009,13 @@ impl ShardsManagerActor { .map_err(|err| err.into()) } - pub fn distribute_encoded_chunk( + fn distribute_encoded_chunk( &mut self, partial_chunk: PartialEncodedChunk, encoded_chunk: EncodedShardChunk, merkle_paths: &Vec, outgoing_receipts: Vec, + me: Option<&AccountId>, ) -> Result<(), Error> { let shard_id = encoded_chunk.shard_id(); let _timer = metrics::DISTRIBUTE_ENCODED_CHUNK_TIME @@ -2064,7 +2071,7 @@ impl ShardsManagerActor { &merkle_paths, ); - if Some(&to_whom) != self.me.as_ref() { + if Some(&to_whom) != me { self.peer_manager_adapter.send(PeerManagerMessageRequest::NetworkRequests( NetworkRequests::PartialEncodedChunkMessage { account_id: to_whom.clone(), @@ -2088,9 +2095,11 @@ impl ShardsManagerActor { "type" = <&'static str>::from(&request) ) .entered(); + let me = self.validator_signer.get().map(|signer| signer.validator_id().clone()); + let me = me.as_ref(); match request { ShardsManagerRequestFromClient::ProcessChunkHeaderFromBlock(chunk_header) => { - if let Err(e) = self.process_chunk_header_from_block(&chunk_header) { + if let Err(e) = self.process_chunk_header_from_block(&chunk_header, me) { warn!(target: "chunks", "Error processing chunk header from block: {:?}", e); } } @@ -2108,29 +2117,30 @@ impl ShardsManagerActor { encoded_chunk, &merkle_paths, outgoing_receipts, + me, ) { warn!(target: "chunks", "Error distributing encoded chunk: {:?}", e); } } ShardsManagerRequestFromClient::RequestChunks { chunks_to_request, prev_hash } => { - self.request_chunks(chunks_to_request, prev_hash) + self.request_chunks(chunks_to_request, prev_hash, me) } ShardsManagerRequestFromClient::RequestChunksForOrphan { chunks_to_request, epoch_id, ancestor_hash, - } => self.request_chunks_for_orphan(chunks_to_request, &epoch_id, ancestor_hash), + } => self.request_chunks_for_orphan(chunks_to_request, &epoch_id, ancestor_hash, me), ShardsManagerRequestFromClient::CheckIncompleteChunks(prev_block_hash) => { - self.check_incomplete_chunks(&prev_block_hash) + self.check_incomplete_chunks(&prev_block_hash, me) } ShardsManagerRequestFromClient::ProcessOrRequestChunk { candidate_chunk, request_header, prev_hash, } => { - if let Err(err) = self.process_partial_encoded_chunk(candidate_chunk.into()) { + if let Err(err) = self.process_partial_encoded_chunk(candidate_chunk.into(), me) { warn!(target: "chunks", ?err, "Error processing partial encoded chunk"); - self.request_chunk_single(&request_header, prev_hash, false); + self.request_chunk_single(&request_header, prev_hash, false, me); } } ShardsManagerRequestFromClient::ProcessOrRequestChunkForOrphan { @@ -2139,9 +2149,14 @@ impl ShardsManagerActor { ancestor_hash, epoch_id, } => { - if let Err(e) = self.process_partial_encoded_chunk(candidate_chunk.into()) { + if let Err(e) = self.process_partial_encoded_chunk(candidate_chunk.into(), me) { warn!(target: "chunks", "Error processing partial encoded chunk: {:?}", e); - self.request_chunks_for_orphan(vec![request_header], &epoch_id, ancestor_hash); + self.request_chunks_for_orphan( + vec![request_header], + &epoch_id, + ancestor_hash, + me, + ); } } } @@ -2154,9 +2169,12 @@ impl ShardsManagerActor { "type" = <&'static str>::from(&request) ) .entered(); + let me = self.validator_signer.get().map(|signer| signer.validator_id().clone()); + let me = me.as_ref(); match request { ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunk(partial_encoded_chunk) => { - if let Err(e) = self.process_partial_encoded_chunk(partial_encoded_chunk.into()) { + if let Err(e) = self.process_partial_encoded_chunk(partial_encoded_chunk.into(), me) + { warn!(target: "chunks", "Error processing partial encoded chunk: {:?}", e); } } @@ -2164,7 +2182,7 @@ impl ShardsManagerActor { partial_encoded_chunk_forward, ) => { if let Err(e) = - self.process_partial_encoded_chunk_forward(partial_encoded_chunk_forward) + self.process_partial_encoded_chunk_forward(partial_encoded_chunk_forward, me) { warn!(target: "chunks", "Error processing partial encoded chunk forward: {:?}", e); } @@ -2177,7 +2195,7 @@ impl ShardsManagerActor { (self.clock.now().signed_duration_since(received_time)).as_seconds_f64(), ); if let Err(e) = - self.process_partial_encoded_chunk_response(partial_encoded_chunk_response) + self.process_partial_encoded_chunk_response(partial_encoded_chunk_response, me) { warn!(target: "chunks", "Error processing partial encoded chunk response: {:?}", e); } @@ -2189,6 +2207,7 @@ impl ShardsManagerActor { self.process_partial_encoded_chunk_request( partial_encoded_chunk_request, route_back, + me, ); } } @@ -2226,6 +2245,7 @@ mod test { use assert_matches::assert_matches; use near_async::messaging::IntoSender; use near_async::time::FakeClock; + use near_chain_configs::MutableConfigValue; use near_epoch_manager::shard_tracker::TrackedConfig; use near_epoch_manager::test_utils::setup_epoch_manager_with_block_and_chunk_producers; use near_network::test_utils::MockPeerManagerAdapter; @@ -2233,6 +2253,7 @@ mod test { use near_primitives::block::Tip; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::types::EpochId; + use near_primitives::validator_signer::EmptyValidatorSigner; use near_store::test_utils::create_test_store; use std::sync::Arc; @@ -2240,6 +2261,13 @@ mod test { use crate::logic::persist_chunk; use crate::test_utils::*; + fn mutable_validator_signer(account_id: &AccountId) -> MutableValidatorSigner { + MutableConfigValue::new( + Some(Arc::new(EmptyValidatorSigner::new(account_id.clone()))), + "validator_signer", + ) + } + /// should not request partial encoded chunk from self #[test] fn test_request_partial_encoded_chunk_from_self() { @@ -2265,7 +2293,7 @@ mod test { let clock = FakeClock::default(); let mut shards_manager = ShardsManagerActor::new( clock.clock(), - Some("test".parse().unwrap()), + mutable_validator_signer(&"test".parse().unwrap()), epoch_manager, shard_tracker, network_adapter.as_sender(), @@ -2309,7 +2337,7 @@ mod test { let clock = FakeClock::default(); let mut shards_manager = ShardsManagerActor::new( clock.clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2322,7 +2350,10 @@ mod test { // process chunk part 0 let partial_encoded_chunk = fixture.make_partial_encoded_chunk(&[0]); let result = shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunk)) + .process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunk), + Some(&fixture.mock_shard_tracker), + ) .unwrap(); assert_matches!(result, ProcessPartialEncodedChunkResult::NeedBlock); @@ -2331,6 +2362,7 @@ mod test { &fixture.mock_chunk_header, CryptoHash::default(), false, + Some(&fixture.mock_shard_tracker), ); let collect_request_parts = |fixture: &mut ChunkTestFixture| -> HashSet { let mut parts = HashSet::new(); @@ -2355,7 +2387,10 @@ mod test { // process chunk part 1 let partial_encoded_chunk = fixture.make_partial_encoded_chunk(&[1]); let result = shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunk)) + .process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunk), + Some(&fixture.mock_shard_tracker), + ) .unwrap(); assert_matches!(result, ProcessPartialEncodedChunkResult::NeedBlock); @@ -2382,7 +2417,7 @@ mod test { let fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2398,8 +2433,10 @@ mod test { if let PartialEncodedChunk::V2(ref mut chunk) = partial_encoded_chunk { chunk.parts[0].part_ord = fixture.mock_chunk_parts.len() as u64; } - let result = shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunk)); + let result = shards_manager.process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunk), + Some(&fixture.mock_shard_tracker), + ); assert_matches!(result, Err(Error::InvalidChunkPartId)); // TODO: add more test cases @@ -2411,7 +2448,7 @@ mod test { let fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_chunk_part_owner.clone()), + mutable_validator_signer(&fixture.mock_chunk_part_owner), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2448,15 +2485,24 @@ mod test { .make_partial_encoded_chunk(&[non_owned_part_ords[2], fixture.mock_part_ords[0]]), ]; shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunks.remove(0))) + .process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunks.remove(0)), + Some(&fixture.mock_chunk_part_owner), + ) .unwrap(); let num_forward_msgs_after_first_receiving = count_num_forward_msgs(&fixture); assert!(num_forward_msgs_after_first_receiving > 0); shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunks.remove(0))) + .process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunks.remove(0)), + Some(&fixture.mock_chunk_part_owner), + ) .unwrap(); shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunks.remove(0))) + .process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunks.remove(0)), + Some(&fixture.mock_chunk_part_owner), + ) .unwrap(); let num_forward_msgs_after_receiving_duplicates = count_num_forward_msgs(&fixture); assert_eq!( @@ -2478,9 +2524,13 @@ mod test { account_id: Option, ) -> RequestChunksResult { let clock = FakeClock::default(); + let validator = MutableConfigValue::new( + account_id.clone().map(|id| Arc::new(EmptyValidatorSigner::new(id))), + "validator_signer", + ); let mut shards_manager = ShardsManagerActor::new( clock.clock(), - account_id, + validator, Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2496,6 +2546,7 @@ mod test { shards_manager.request_chunks( vec![fixture.mock_chunk_header.clone()], *fixture.mock_chunk_header.prev_block_hash(), + account_id.as_ref(), ); let marked_as_requested = shards_manager .requested_partial_encoded_chunks @@ -2569,7 +2620,7 @@ mod test { let clock = FakeClock::default(); let mut shards_manager = ShardsManagerActor::new( clock.clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2590,7 +2641,9 @@ mod test { most_parts, ); // The validator receives the chunk forward - assert!(shards_manager.process_partial_encoded_chunk_forward(forward).is_ok()); + assert!(shards_manager + .process_partial_encoded_chunk_forward(forward, Some(&fixture.mock_shard_tracker)) + .is_ok()); let partial_encoded_chunk = PartialEncodedChunk::V2(PartialEncodedChunkV2 { header: fixture.mock_chunk_header.clone(), parts: other_parts, @@ -2598,7 +2651,10 @@ mod test { }); // The validator receives a chunk header with the rest of the parts it needed let result = shards_manager - .process_partial_encoded_chunk(MaybeValidated::from(partial_encoded_chunk)) + .process_partial_encoded_chunk( + MaybeValidated::from(partial_encoded_chunk), + Some(&fixture.mock_shard_tracker), + ) .unwrap(); match result { @@ -2612,6 +2668,7 @@ mod test { vec![fixture.mock_chunk_header.clone()], &EpochId::default(), CryptoHash::default(), + Some(&fixture.mock_shard_tracker), ); clock.advance(CHUNK_REQUEST_RETRY * 2); shards_manager.resend_chunk_requests(); @@ -2639,7 +2696,7 @@ mod test { let clock = FakeClock::default(); let mut shards_manager = ShardsManagerActor::new( clock.clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2654,7 +2711,9 @@ mod test { fixture.mock_chunk_parts.clone(), ); // The validator receives the chunk forward - assert!(shards_manager.process_partial_encoded_chunk_forward(forward).is_ok(),); + assert!(shards_manager + .process_partial_encoded_chunk_forward(forward, Some(&fixture.mock_shard_tracker)) + .is_ok(),); // The validator then receives the block, which is missing chunks; it notifies the // ShardsManager of the chunk header, and ShardsManager is able to complete the chunk // because of the forwarded parts.shards_manager @@ -2662,7 +2721,10 @@ mod test { &fixture.mock_chunk_header, ); let process_result = shards_manager - .try_process_chunk_parts_and_receipts(&fixture.mock_chunk_header) + .try_process_chunk_parts_and_receipts( + &fixture.mock_chunk_header, + Some(&fixture.mock_shard_tracker), + ) .unwrap(); match process_result { ProcessPartialEncodedChunkResult::HaveAllPartsAndReceipts => {} @@ -2677,6 +2739,7 @@ mod test { &fixture.mock_chunk_header, *fixture.mock_chunk_header.prev_block_hash(), false, + Some(&fixture.mock_shard_tracker), ); clock.advance(CHUNK_REQUEST_RETRY * 2); @@ -2701,7 +2764,7 @@ mod test { let fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2718,6 +2781,7 @@ mod test { fixture.mock_encoded_chunk.clone(), &fixture.mock_merkle_paths, fixture.mock_outgoing_receipts.clone(), + Some(&fixture.mock_shard_tracker), ) .unwrap(); @@ -2736,7 +2800,7 @@ mod test { let fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2750,6 +2814,7 @@ mod test { shards_manager .process_partial_encoded_chunk( fixture.make_partial_encoded_chunk(&fixture.all_part_ords).into(), + Some(&fixture.mock_shard_tracker), ) .unwrap(); @@ -2768,7 +2833,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2801,7 +2866,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2835,7 +2900,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2851,7 +2916,10 @@ mod test { fixture.all_part_ords.split_at(fixture.all_part_ords.len() / 2); shards_manager - .process_partial_encoded_chunk(fixture.make_partial_encoded_chunk(cache_ords).into()) + .process_partial_encoded_chunk( + fixture.make_partial_encoded_chunk(cache_ords).into(), + Some(&fixture.mock_shard_tracker), + ) .unwrap(); persist_chunk( @@ -2875,7 +2943,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2895,6 +2963,7 @@ mod test { &fixture.all_part_ords[0..fixture.all_part_ords.len() / 2], ) .into(), + Some(&fixture.mock_shard_tracker), ) .unwrap(); @@ -2921,7 +2990,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2939,7 +3008,10 @@ mod test { let partial_ords = &fixture.all_part_ords.as_slice()[n / 3..(n * 2 / 3)]; shards_manager - .process_partial_encoded_chunk(fixture.make_partial_encoded_chunk(cache_ords).into()) + .process_partial_encoded_chunk( + fixture.make_partial_encoded_chunk(cache_ords).into(), + Some(&fixture.mock_shard_tracker), + ) .unwrap(); persist_chunk( @@ -2963,7 +3035,7 @@ mod test { let fixture = ChunkTestFixture::default(); let shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -2988,7 +3060,7 @@ mod test { let fixture = ChunkTestFixture::default(); let shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -3013,7 +3085,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -3048,7 +3120,7 @@ mod test { let mut fixture = ChunkTestFixture::default(); let shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_shard_tracker.clone()), + mutable_validator_signer(&fixture.mock_shard_tracker), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -3081,7 +3153,7 @@ mod test { let fixture = ChunkTestFixture::default(); let mut shards_manager = ShardsManagerActor::new( FakeClock::default().clock(), - Some(fixture.mock_chunk_part_owner.clone()), + mutable_validator_signer(&fixture.mock_chunk_part_owner), Arc::new(fixture.epoch_manager.clone()), fixture.shard_tracker.clone(), fixture.mock_network.as_sender(), @@ -3092,11 +3164,18 @@ mod test { Duration::hours(1), ); let part = fixture.make_partial_encoded_chunk(&fixture.mock_part_ords); - shards_manager.process_partial_encoded_chunk(part.clone().into()).unwrap(); + shards_manager + .process_partial_encoded_chunk( + part.clone().into(), + Some(&fixture.mock_chunk_part_owner), + ) + .unwrap(); assert_eq!(fixture.count_chunk_ready_for_inclusion_messages(), 1); // test that chunk inclusion message is only sent once. - shards_manager.process_partial_encoded_chunk(part.into()).unwrap(); + shards_manager + .process_partial_encoded_chunk(part.into(), Some(&fixture.mock_chunk_part_owner)) + .unwrap(); assert_eq!(fixture.count_chunk_ready_for_inclusion_messages(), 0); } } diff --git a/chain/chunks/src/test/basic.rs b/chain/chunks/src/test/basic.rs deleted file mode 100644 index 39eeb4a40b8..00000000000 --- a/chain/chunks/src/test/basic.rs +++ /dev/null @@ -1,267 +0,0 @@ -use crate::{ - adapter::ShardsManagerRequestFromClient, - client::ShardsManagerResponse, - test_loop::{ - forward_client_request_to_shards_manager, forward_network_request_to_shards_manager, - MockChainForShardsManager, MockChainForShardsManagerConfig, - }, - test_utils::default_tip, - ShardsManagerActor, CHUNK_REQUEST_RETRY, -}; -use derive_enum_from_into::{EnumFrom, EnumTryInto}; -use near_async::messaging::noop; -use near_async::test_loop::futures::TestLoopDelayedActionEvent; -use near_async::time; -use near_async::{ - messaging::{CanSend, IntoSender}, - test_loop::{ - adhoc::{handle_adhoc_events, AdhocEvent, AdhocEventSender}, - event_handler::capture_events, - }, -}; -use near_chain::chunks_store::ReadOnlyChunksStore; -use near_epoch_manager::test_utils::hash_range; -use near_network::{ - shards_manager::ShardsManagerRequestFromNetwork, - types::{NetworkRequests, PeerManagerMessageRequest}, -}; -use near_primitives::types::{AccountId, BlockHeight}; -use near_store::test_utils::create_test_store; -use std::collections::HashSet; - -#[derive(derive_more::AsMut)] -struct TestData { - shards_manager: ShardsManagerActor, - chain: MockChainForShardsManager, - /// Captured events sent to the client. - client_events: Vec, - /// Captured events sent to the network. - network_events: Vec, -} - -impl AsMut for TestData { - fn as_mut(&mut self) -> &mut Self { - self - } -} - -impl TestData { - fn new(shards_manager: ShardsManagerActor, chain: MockChainForShardsManager) -> Self { - Self { shards_manager, chain, client_events: vec![], network_events: vec![] } - } -} - -#[derive(EnumTryInto, Debug, EnumFrom)] -enum TestEvent { - ClientToShardsManager(ShardsManagerRequestFromClient), - NetworkToShardsManager(ShardsManagerRequestFromNetwork), - ShardsManagerToClient(ShardsManagerResponse), - ShardsManagerToNetwork(PeerManagerMessageRequest), - Adhoc(AdhocEvent), - ShardsManagerDelayedActions(TestLoopDelayedActionEvent), -} - -type ShardsManagerTestLoopBuilder = near_async::test_loop::TestLoopBuilder; - -/// Basic test that sends a full chunk to one ShardsManager and checks that it -/// reports the complete chunk to the client. -#[test] -fn test_basic_receive_complete_chunk() { - let builder = ShardsManagerTestLoopBuilder::new(); - let validators = (0..5) - .map(|i| format!("validator_{}", i).parse::().unwrap()) - .collect::>(); - - let store = create_test_store(); - let chain = MockChainForShardsManager::new( - store.clone(), - MockChainForShardsManagerConfig { - account_id: validators[0].clone(), - block_producers: validators.clone(), - chunk_only_producers: vec![], - epoch_length: 2, - num_shards: 3, - track_all_shards: true, - shards_manager: builder.sender().into_sender(), - }, - ); - - let shards_manager = ShardsManagerActor::new( - builder.clock(), - Some(validators[0].clone()), - chain.epoch_manager.clone(), - chain.shard_tracker.clone(), - noop().into_sender(), - builder.sender().into_sender(), - ReadOnlyChunksStore::new(store), - default_tip(), - default_tip(), - CHUNK_REQUEST_RETRY, - ); - let test_data = TestData::new(shards_manager, chain); - let mut test = builder.build(test_data); - test.register_handler(forward_client_request_to_shards_manager().widen()); - test.register_handler(forward_network_request_to_shards_manager().widen()); - test.register_handler(capture_events::().widen()); - - // Have the ShardsManager receive a PartialEncodedChunk with all parts. - let chunk = test.data.chain.produce_chunk_signed_by_chunk_producer(2); - test.sender().send(TestEvent::NetworkToShardsManager( - ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunk( - chunk.make_partial_encoded_chunk(&chunk.part_ords(), &[]), - ), - )); - test.run_for(time::Duration::seconds(1)); - - assert_eq!(test.data.client_events.len(), 2); - match &test.data.client_events[0] { - ShardsManagerResponse::ChunkHeaderReadyForInclusion { .. } => {} - _ => panic!(), - } - match &test.data.client_events[1] { - ShardsManagerResponse::ChunkCompleted { partial_chunk, shard_chunk } => { - assert_eq!(partial_chunk.parts().len(), chunk.part_ords().len()); - assert!(shard_chunk.is_some()); - } - _ => panic!(), - } -} - -/// Tests that one node forwards chunk parts correctly to other nodes. -/// When a validator V receives a PartialEncodedChunk from the chunk producer, -/// containing the parts that the V owns, it is responsible for forwarding -/// these parts to the next chunk producer as well as the any block producer -/// that tracks the shard, as an optimization so that they don't have to -/// request what we already know they will request. This test checks that -/// behavior. -/// -/// Note that before Phase 2 of sharding is launched, we actually forward to -/// all block producers, not just those tracking the shard, because all -/// block producers are required to track all shards. -/// -/// See the comment on top of chain/chunks/src/lib.rs for more details. -#[test] -fn test_chunk_forward() { - let builder = ShardsManagerTestLoopBuilder::new(); - // For this test we need to ensure that 1 part is not enough to decode - // the whole chunk. For that, we need at least 7 block producers, so that - // the total number of parts is 7, and the number of parts needed to - // decode the chunk is 2, since the formula is (num_block_producers - 1)/3. - let block_producers: Vec = - (0..10).map(|idx| format!("bp_{}", idx).parse().unwrap()).collect(); - let chunk_only_producers: Vec = - (0..10).map(|idx| format!("cp_{}", idx).parse().unwrap()).collect(); - let store = create_test_store(); - let chain = MockChainForShardsManager::new( - store.clone(), - MockChainForShardsManagerConfig { - account_id: block_producers[0].clone(), - block_producers: block_producers.clone(), - chunk_only_producers: chunk_only_producers.clone(), - num_shards: 1, - epoch_length: 5, - track_all_shards: true, - shards_manager: builder.sender().into_sender(), - }, - ); - let shards_manager = ShardsManagerActor::new( - builder.clock(), - Some(block_producers[0].clone()), - chain.epoch_manager.clone(), - chain.shard_tracker.clone(), - builder.sender().into_sender(), - builder.sender().into_sender(), - ReadOnlyChunksStore::new(store), - default_tip(), - default_tip(), - CHUNK_REQUEST_RETRY, - ); - let mut test = builder.build(TestData::new(shards_manager, chain)); - test.register_handler(capture_events::().widen()); - test.register_handler(capture_events::().widen()); - test.register_handler(forward_client_request_to_shards_manager().widen()); - test.register_handler(forward_network_request_to_shards_manager().widen()); - test.register_handler(handle_adhoc_events::().widen()); - test.register_delayed_action_handler::(); - - test.data.shards_manager.periodically_resend_chunk_requests( - &mut test.sender().into_delayed_action_runner::(test.shutting_down()), - ); - - // We'll produce a single chunk whose next chunk producer is a chunk-only - // producer, so that we can test that the chunk is forwarded to the next - // chunk producer (since we forward it to block producers anyway). - // We do that by producing *blocks* until we get to a height where the - // next chunk producer is a chunk-only producer. - test.sender().send_adhoc_event("produce chunk", { - let sender = test.sender(); - let chunk_only_producers = chunk_only_producers.clone(); - move |data| { - let hashes = hash_range(100); // 100 blocks should be more than enough to get lucky. - for (i, hash) in hashes.iter().enumerate() { - let partial_encoded_chunk = data.chain.produce_chunk_signed_by_chunk_producer(0); - data.chain.record_block(*hash, i as BlockHeight + 1); - let next_chunk_producer = data.chain.next_chunk_producer(0); - if !chunk_only_producers.contains(&next_chunk_producer) { - tracing::info!(target: "test", "Trying again at height {} which has chunk producer {}, we want the next chunk producer to be a chunk only producer", - i + 1, next_chunk_producer); - continue; - } - sender.send(TestEvent::NetworkToShardsManager( - ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunk( - partial_encoded_chunk.make_partial_encoded_chunk(&[0], &[]), - ), - )); - break; - } - } - }); - test.run_instant(); - - // The logic implemented right now is that all block producers will get the - // forwarding (due to tracking all shards), and for chunk only producers, - // they will only get the forwarding if they are the next chunk producer. - let mut next_chunk_producer_forwarded = false; - let mut block_producer_forwarded = HashSet::new(); - while let Some(r) = test.data.network_events.pop() { - match r.as_network_requests_ref() { - NetworkRequests::PartialEncodedChunkForward { account_id, .. } => { - if account_id == &test.data.chain.next_chunk_producer(0) { - next_chunk_producer_forwarded = true; - } else { - assert!( - !chunk_only_producers.contains(&account_id), - "shouldn't forward to {:?}", - account_id - ); - block_producer_forwarded.insert(account_id.clone()); - } - } - NetworkRequests::PartialEncodedChunkRequest { .. } => { - panic!("Shouldn't request chunk part yet; should wait for forwarding"); - } - _ => { - panic!("Unexpected network request: {:?}", r); - } - } - } - assert!(next_chunk_producer_forwarded); - assert_eq!(block_producer_forwarded.len(), block_producers.len() - 1); - - // Now run for a bit longer to trigger resend. The validator should now - // request the missing parts. - test.run_for(CHUNK_REQUEST_RETRY); - let mut seen_part_request = false; - while let Some(r) = test.data.network_events.pop() { - match r.as_network_requests_ref() { - NetworkRequests::PartialEncodedChunkRequest { .. } => { - seen_part_request = true; - } - _ => { - panic!("Unexpected network request: {:?}", r); - } - } - } - assert!(seen_part_request); - test.shutdown_and_drain_remaining_events(time::Duration::seconds(1)); -} diff --git a/chain/chunks/src/test/multi.rs b/chain/chunks/src/test/multi.rs deleted file mode 100644 index 1d6e6e0f052..00000000000 --- a/chain/chunks/src/test/multi.rs +++ /dev/null @@ -1,365 +0,0 @@ -use crate::{ - adapter::ShardsManagerRequestFromClient, - client::ShardsManagerResponse, - test_loop::{ - forward_client_request_to_shards_manager, forward_network_request_to_shards_manager, - route_shards_manager_network_messages, MockChainForShardsManager, - MockChainForShardsManagerConfig, - }, - test_utils::default_tip, - ShardsManagerActor, CHUNK_REQUEST_RETRY, -}; -use derive_enum_from_into::{EnumFrom, EnumTryInto}; -use near_async::test_loop::futures::TestLoopDelayedActionEvent; -use near_async::{ - messaging::IntoSender, - test_loop::{ - adhoc::{handle_adhoc_events, AdhocEvent, AdhocEventSender}, - event_handler::capture_events, - }, - time, -}; -use near_chain::chunks_store::ReadOnlyChunksStore; -use near_epoch_manager::EpochManagerAdapter; -use near_network::{ - shards_manager::ShardsManagerRequestFromNetwork, test_loop::SupportsRoutingLookup, - types::PeerManagerMessageRequest, -}; -use near_primitives::{ - checked_feature, - types::{AccountId, NumShards}, - version::PROTOCOL_VERSION, -}; -use near_store::test_utils::create_test_store; - -#[derive(derive_more::AsMut, derive_more::AsRef)] -struct TestData { - shards_manager: ShardsManagerActor, - chain: MockChainForShardsManager, - client_events: Vec, - account_id: AccountId, -} - -impl AsMut for TestData { - fn as_mut(&mut self) -> &mut Self { - self - } -} - -#[derive(EnumTryInto, Debug, EnumFrom)] -enum TestEvent { - Adhoc(AdhocEvent), - ShardsManagerDelayedActions(TestLoopDelayedActionEvent), - ClientToShardsManager(ShardsManagerRequestFromClient), - NetworkToShardsManager(ShardsManagerRequestFromNetwork), - ShardsManagerToClient(ShardsManagerResponse), - OutboundNetwork(PeerManagerMessageRequest), -} - -type ShardsManagerTestLoop = near_async::test_loop::TestLoop, (usize, TestEvent)>; -type ShardsManagerTestLoopBuilder = near_async::test_loop::TestLoopBuilder<(usize, TestEvent)>; - -struct BasicSetupConfig { - block_producers: Vec, - chunk_only_producers: Vec, - epoch_length: u64, - num_shards: NumShards, - track_all_shards: bool, -} - -const NETWORK_DELAY: time::Duration = time::Duration::milliseconds(10); - -fn basic_setup(config: BasicSetupConfig) -> ShardsManagerTestLoop { - let builder = ShardsManagerTestLoopBuilder::new(); - let all_accounts = config.block_producers.iter().chain(config.chunk_only_producers.iter()); - let data = all_accounts - .enumerate() - .map(|(idx, account)| { - let store = create_test_store(); - let chain = MockChainForShardsManager::new( - store.clone(), - MockChainForShardsManagerConfig { - account_id: account.clone(), - block_producers: config.block_producers.clone(), - chunk_only_producers: config.chunk_only_producers.clone(), - epoch_length: config.epoch_length, - num_shards: config.num_shards, - track_all_shards: config.track_all_shards, - shards_manager: builder.sender().for_index(idx).into_sender(), - }, - ); - let shards_manager = ShardsManagerActor::new( - builder.clock(), - Some(account.clone()), - chain.epoch_manager.clone(), - chain.shard_tracker.clone(), - builder.sender().for_index(idx).into_sender(), - builder.sender().for_index(idx).into_sender(), - ReadOnlyChunksStore::new(store), - default_tip(), - default_tip(), - CHUNK_REQUEST_RETRY, - ); - TestData { shards_manager, chain, client_events: vec![], account_id: account.clone() } - }) - .collect::>(); - let mut test = builder.build(data); - for idx in 0..test.data.len() { - test.register_handler(handle_adhoc_events::().widen().for_index(idx)); - test.register_delayed_action_handler_for_index::(idx); - test.register_handler(forward_client_request_to_shards_manager().widen().for_index(idx)); - test.register_handler(forward_network_request_to_shards_manager().widen().for_index(idx)); - test.register_handler(capture_events::().widen().for_index(idx)); - test.register_handler(route_shards_manager_network_messages( - test.sender(), - test.clock(), - NETWORK_DELAY, - )); - - let sender = test.sender().for_index(idx); - let shutting_down = test.shutting_down(); - test.sender().for_index(idx).send_adhoc_event("start_shards_manager", |data| { - data.shards_manager.periodically_resend_chunk_requests( - &mut sender.into_delayed_action_runner(shutting_down), - ); - }) - } - test -} - -/// Tests that when we have some block producers (validators) in the network, -/// and one block producer produces a chunk, the chunk is distributed to the -/// other block producers properly and all other block producers report the -/// completion of the chunk to the client. -#[test] -fn test_distribute_chunk_basic() { - let mut test = basic_setup(BasicSetupConfig { - block_producers: (0..10).map(|idx| format!("validator_{}", idx).parse().unwrap()).collect(), - chunk_only_producers: Vec::new(), - epoch_length: 4, // arbitrary - num_shards: 3, // arbitrary - track_all_shards: false, - }); - let chunk_producer = test.data[0].chain.next_chunk_producer(1); - let chunk_producer_idx = test.data.index_for_account(&chunk_producer); - test.sender().for_index(chunk_producer_idx).send_adhoc_event("produce chunk", |data| { - let chunk = data.chain.produce_chunk(1); - data.chain.distribute_chunk(&chunk); - }); - // Two network rounds is enough because each node should have - // forwarded the parts to those block producers that need them. - test.run_for(NETWORK_DELAY * 2); - - // All other nodes should have received the chunk header and their owned - // parts, but only those that track the shard should have persisted the - // entire chunk. - for idx in 0..test.data.len() { - if idx == chunk_producer_idx { - continue; - } - let data = test.data.get(idx).unwrap(); - assert_eq!(data.client_events.len(), 2); - match &data.client_events[0] { - ShardsManagerResponse::ChunkHeaderReadyForInclusion { - chunk_producer: producer, - .. - } => { - assert_eq!(producer, &chunk_producer); - } - _ => panic!("Unexpected event"), - } - match &data.client_events[1] { - ShardsManagerResponse::ChunkCompleted { partial_chunk, shard_chunk } => { - if data.chain.cares_about_shard_this_or_next_epoch(1) { - assert_eq!( - partial_chunk.parts().len(), - data.chain.epoch_manager.num_total_parts() - ); - assert!(shard_chunk.is_some()); - } else { - assert_eq!(partial_chunk.parts().len(), 1); - assert_eq!(partial_chunk.parts()[0].part_ord as usize, idx); - assert!(shard_chunk.is_none()); - } - } - _ => panic!("Unexpected event"), - } - } - - test.shutdown_and_drain_remaining_events(time::Duration::seconds(1)); -} - -/// Tests that when we have some block producers (validators) in the network, -/// and one block producer produces a chunk, the chunk is distributed to the -/// other block producers properly and all other block producers report the -/// completion of the chunk to the client. Unlike test_distribute_chunk_basic, -/// this test has all block producers track all shards. -#[test] -fn test_distribute_chunk_track_all_shards() { - let mut test = basic_setup(BasicSetupConfig { - block_producers: (0..10).map(|idx| format!("validator_{}", idx).parse().unwrap()).collect(), - chunk_only_producers: Vec::new(), - epoch_length: 4, // arbitrary - num_shards: 3, // arbitrary - track_all_shards: true, - }); - let chunk_producer = test.data[0].chain.next_chunk_producer(1); - let chunk_producer_idx = test.data.index_for_account(&chunk_producer); - test.sender().for_index(chunk_producer_idx).send_adhoc_event("produce chunk", |data| { - let chunk = data.chain.produce_chunk(1); - data.chain.distribute_chunk(&chunk); - }); - if checked_feature!("stable", SingleShardTracking, PROTOCOL_VERSION) { - // After SingleShardTracking protocol upgrade, we need a longer - // delay because validators that don't track the shard will not get - // parts forwarded to them. - // We need to wait for 2x CHUNK_REQUEST_DELAY because the first - // time that the timer fires it is not yet enough to trigger the - // request (due to misalignment of the timer). So we wait twice. After - // the second timer fires, another round trip will be enough to get - // the needed parts and receipts. - test.run_for(CHUNK_REQUEST_RETRY * 2 + NETWORK_DELAY * 2); - } else { - // Two network rounds is enough because each node should have - // forwarded the parts to those block producers that need them. - test.run_for(NETWORK_DELAY * 2); - } - - // All other nodes should have received the complete chunk. - for idx in 0..test.data.len() { - if idx == chunk_producer_idx { - continue; - } - let data = test.data.get(idx).unwrap(); - assert_eq!(data.client_events.len(), 2); - match &data.client_events[0] { - ShardsManagerResponse::ChunkHeaderReadyForInclusion { - chunk_producer: producer, - .. - } => { - assert_eq!(producer, &chunk_producer); - } - _ => panic!("Unexpected event"), - } - match &data.client_events[1] { - ShardsManagerResponse::ChunkCompleted { partial_chunk, shard_chunk } => { - assert_eq!(partial_chunk.parts().len(), data.chain.epoch_manager.num_total_parts()); - assert!(shard_chunk.is_some()); - } - _ => panic!("Unexpected event"), - } - } - test.shutdown_and_drain_remaining_events(time::Duration::seconds(1)); -} - -/// Tests that when the network has some block producers and also some chunk- -/// only producers, the chunk-only producers are also able to request and -/// complete chunks. -#[test] -fn test_distribute_chunk_with_chunk_only_producers() { - const NUM_BLOCK_PRODUCERS: usize = 8; // arbitrary - const NUM_CHUNK_ONLY_PRODUCERS: usize = 6; // arbitrary - let mut test = basic_setup(BasicSetupConfig { - block_producers: (0..NUM_BLOCK_PRODUCERS) - .map(|idx| format!("bp_{}", idx).parse().unwrap()) - .collect(), - chunk_only_producers: (0..NUM_CHUNK_ONLY_PRODUCERS) - .map(|idx| format!("cp_{}", idx).parse().unwrap()) - .collect(), - epoch_length: 4, // arbitrary - num_shards: 3, // arbitrary - track_all_shards: false, - }); - - let chunk_producer = test.data[0].chain.next_chunk_producer(1); - let chunk_producer_idx = test.data.index_for_account(&chunk_producer); - - let sender = test.sender(); - // Typically the chunk-only producer would receive a block later because the block - // producer would need to first produce a block. So we'll just have some arbitrary - // delay here. - let chunk_producer_receive_block_at: time::Duration = time::Duration::milliseconds(50); - test.sender().for_index(chunk_producer_idx).send_adhoc_event("produce chunk", move |data| { - let chunk = data.chain.produce_chunk(1); - data.chain.distribute_chunk(&chunk); - - // The chunk producers may not be forwarded the chunk header, so we - // trigger their processing of it manually, as if they had received it - // via a produced block. - for idx in NUM_BLOCK_PRODUCERS..NUM_BLOCK_PRODUCERS + NUM_CHUNK_ONLY_PRODUCERS { - let chunk_header = chunk.header(); - sender.clone().for_index(idx).schedule_adhoc_event( - "request chunk from block", - move |data| data.chain.request_chunk_for_block(chunk_header), - chunk_producer_receive_block_at, - ); - } - }); - // Run for a network roundtrip after the chunk producers receive the block. - // This should be enough time for the chunk producers to request and - // receive the missing chunk parts. - test.run_for(chunk_producer_receive_block_at + NETWORK_DELAY * 2); - - for idx in 0..test.data.len() { - if idx == chunk_producer_idx { - continue; - } - let chunk_producer = chunk_producer.clone(); - // Run assertions in the test loop so if something fails we know which - // instance failed. - test.sender().for_index(idx).send_adhoc_event("assertions", move |data| { - assert_eq!(data.client_events.len(), 2); - match &data.client_events[0] { - ShardsManagerResponse::ChunkHeaderReadyForInclusion { - chunk_producer: producer, - .. - } => { - assert_eq!(producer, &chunk_producer); - } - _ => panic!("Unexpected event"), - } - match &data.client_events[1] { - ShardsManagerResponse::ChunkCompleted { partial_chunk, shard_chunk } => { - if data.chain.cares_about_shard_this_or_next_epoch(1) { - // If we track this shard we should have all parts. - assert_eq!( - partial_chunk.parts().len(), - data.chain.epoch_manager.num_total_parts() - ); - assert!(shard_chunk.is_some()); - } else { - assert!(shard_chunk.is_none()); - if idx < NUM_BLOCK_PRODUCERS { - // Block producers own one part each. - assert_eq!(partial_chunk.parts().len(), 1); - assert_eq!(partial_chunk.parts()[0].part_ord as usize, idx); - } else { - // Chunk producers don't own any parts. - assert_eq!(partial_chunk.parts().len(), 0); - } - } - for shard in 0..3 { - if data.chain.cares_about_shard_this_or_next_epoch(shard) { - if data.chain.cares_about_shard_this_or_next_epoch(1) { - // If we track shard 1, we should have the full chunk - // and thus have every receipt proof. - assert_eq!(partial_chunk.prev_outgoing_receipts().len(), 3); - } else { - // Otherwise, we should only have the receipt proof for the - // shard we care about. - assert_eq!(partial_chunk.prev_outgoing_receipts().len(), 1); - assert_eq!( - partial_chunk.prev_outgoing_receipts()[0].1.to_shard_id, - shard - ); - } - } - } - } - _ => panic!("Unexpected event"), - } - }); - } - test.run_instant(); - test.shutdown_and_drain_remaining_events(time::Duration::seconds(1)); -} diff --git a/chain/chunks/src/test_loop.rs b/chain/chunks/src/test_loop.rs deleted file mode 100644 index bed47f841e7..00000000000 --- a/chain/chunks/src/test_loop.rs +++ /dev/null @@ -1,378 +0,0 @@ -use crate::{ - adapter::ShardsManagerRequestFromClient, - logic::{cares_about_shard_this_or_next_epoch, make_outgoing_receipts_proofs}, - shards_manager_actor::ShardsManagerActor, - test_utils::{default_tip, tip}, -}; -use near_async::test_loop::delay_sender::DelaySender; -use near_async::time; -use near_async::time::Clock; -use near_async::{ - messaging::Sender, - test_loop::event_handler::{LoopEventHandler, TryIntoOrSelf}, -}; -use near_chain::{types::Tip, Chain}; -use near_epoch_manager::{ - shard_tracker::{ShardTracker, TrackedConfig}, - test_utils::{record_block, setup_epoch_manager_with_block_and_chunk_producers}, - EpochManagerAdapter, EpochManagerHandle, -}; -use near_network::{ - shards_manager::ShardsManagerRequestFromNetwork, - test_loop::SupportsRoutingLookup, - types::{NetworkRequests, PeerManagerMessageRequest}, -}; -use near_primitives::congestion_info::CongestionInfo; -use near_primitives::{ - hash::CryptoHash, - merkle::{self, MerklePath}, - sharding::{ - EncodedShardChunk, PartialEncodedChunk, PartialEncodedChunkV2, ReceiptProof, - ShardChunkHeader, - }, - test_utils::create_test_signer, - types::{AccountId, BlockHeight, BlockHeightDelta, MerkleHash, NumShards, ShardId}, - version::PROTOCOL_VERSION, -}; -use near_store::Store; -use reed_solomon_erasure::galois_8::ReedSolomon; -use std::{collections::HashMap, sync::Arc}; - -pub fn forward_client_request_to_shards_manager( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|event, data: &mut ShardsManagerActor| { - data.handle_client_request(event); - }) -} - -pub fn forward_network_request_to_shards_manager( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|event, data: &mut ShardsManagerActor| { - data.handle_network_request(event); - }) -} - -/// Routes network messages that are issued by ShardsManager to other instances -/// in a multi-instance test. -/// -/// TODO: This logic should ideally not be duplicated from the real -/// PeerManagerActor and PeerActor. -pub fn route_shards_manager_network_messages< - Data: SupportsRoutingLookup, - Event: TryIntoOrSelf - + From - + From, ->( - sender: DelaySender<(usize, Event)>, - clock: Clock, - network_delay: time::Duration, -) -> LoopEventHandler { - let mut route_back_lookup: HashMap = HashMap::new(); - let mut next_hash: u64 = 0; - LoopEventHandler::new(move |event: (usize, Event), data: &mut Data| { - let (idx, event) = event; - let message = event.try_into_or_self().map_err(|e| (idx, e.into()))?; - match message { - PeerManagerMessageRequest::NetworkRequests(request) => { - match request { - NetworkRequests::PartialEncodedChunkRequest { target, request, .. } => { - let target_idx = data.index_for_account(&target.account_id.unwrap()); - let route_back = CryptoHash::hash_borsh(next_hash); - route_back_lookup.insert(route_back, idx); - next_hash += 1; - sender.send_with_delay( - (target_idx, - ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunkRequest { - partial_encoded_chunk_request: request, - route_back, - }.into()), - network_delay, - ); - Ok(()) - } - NetworkRequests::PartialEncodedChunkResponse { route_back, response } => { - let target_idx = - *route_back_lookup.get(&route_back).expect("Route back not found"); - sender.send_with_delay( - (target_idx, - ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunkResponse { - partial_encoded_chunk_response: response, - received_time: clock.now().into(), // TODO: use clock - }.into()), - network_delay, - ); - Ok(()) - } - NetworkRequests::PartialEncodedChunkMessage { - account_id, - partial_encoded_chunk, - } => { - let target_idx = data.index_for_account(&account_id); - sender.send_with_delay( - ( - target_idx, - ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunk( - partial_encoded_chunk.into(), - ) - .into(), - ), - network_delay, - ); - Ok(()) - } - NetworkRequests::PartialEncodedChunkForward { account_id, forward } => { - let target_idx = data.index_for_account(&account_id); - sender.send_with_delay( - ( - target_idx, - ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunkForward( - forward, - ) - .into(), - ), - network_delay, - ); - Ok(()) - } - other_message => { - Err((idx, PeerManagerMessageRequest::NetworkRequests(other_message).into())) - } - } - } - message => Err((idx, message.into())), - } - }) -} - -// NOTE: this is no longer needed for TestLoop, but some other non-TestLoop tests depend on it. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ShardsManagerResendChunkRequests; - -/// A simple implementation of the chain side that interacts with -/// ShardsManager. -pub struct MockChainForShardsManager { - pub account_id: AccountId, - pub epoch_manager: Arc, - pub shard_tracker: ShardTracker, - pub shards_manager: Sender, - pub tip: Tip, -} - -pub struct MockChainForShardsManagerConfig { - pub account_id: AccountId, - pub num_shards: NumShards, - pub epoch_length: BlockHeightDelta, - pub block_producers: Vec, - pub chunk_only_producers: Vec, - pub track_all_shards: bool, - pub shards_manager: Sender, -} - -impl MockChainForShardsManager { - pub fn new(store: Store, config: MockChainForShardsManagerConfig) -> Self { - let epoch_manager = Arc::new( - setup_epoch_manager_with_block_and_chunk_producers( - store, - config.block_producers, - config.chunk_only_producers, - config.num_shards, - config.epoch_length, - ) - .into_handle(), - ); - let tracking = if config.track_all_shards { - TrackedConfig::AllShards - } else { - TrackedConfig::new_empty() - }; - let shard_tracker = ShardTracker::new(tracking, epoch_manager.clone()); - Self { - account_id: config.account_id, - epoch_manager, - shard_tracker, - shards_manager: config.shards_manager, - tip: default_tip(), - } - } - - /// Adds a new block to the chain. Automatically takes care of Epoch - /// transitions, and automatically updates the tip, and notifies the - /// ShardsManager about the new tip. - pub fn record_block(&mut self, last: CryptoHash, height: BlockHeight) { - record_block( - &mut *self.epoch_manager.write(), - self.tip.last_block_hash, - last, - height, - vec![], - ); - self.tip = tip(self.epoch_manager.as_ref(), last); - self.shards_manager.send(ShardsManagerRequestFromClient::UpdateChainHeads { - head: self.tip.clone(), - header_head: self.tip.clone(), - }); - } - - /// Makes a request to the ShardsManager to fetch this chunk. - pub fn request_chunk_for_block(&mut self, chunk_header: ShardChunkHeader) { - // TODO: this request and the next request are somewhat redundant, and the next - // request does not work without the first one. We should consolidate the two - // requests. - self.shards_manager.send(ShardsManagerRequestFromClient::ProcessChunkHeaderFromBlock( - chunk_header.clone(), - )); - if &self.tip.last_block_hash == chunk_header.prev_block_hash() { - self.shards_manager.send(ShardsManagerRequestFromClient::RequestChunks { - chunks_to_request: vec![chunk_header.clone()], - prev_hash: *chunk_header.prev_block_hash(), - }); - } else { - self.shards_manager.send(ShardsManagerRequestFromClient::RequestChunksForOrphan { - chunks_to_request: vec![chunk_header], - epoch_id: self - .epoch_manager - .get_epoch_id_from_prev_block(&self.tip.last_block_hash) - .unwrap(), - ancestor_hash: self.tip.last_block_hash, - }); - } - } - - /// Calculates the next chunk producer (for tip.height + 1) - pub fn next_chunk_producer(&mut self, shard_id: ShardId) -> AccountId { - let epoch_id = - self.epoch_manager.get_epoch_id_from_prev_block(&self.tip.last_block_hash).unwrap(); - self.epoch_manager.get_chunk_producer(&epoch_id, self.tip.height + 1, shard_id).unwrap() - } - - /// Produces the next chunk for the given shard, signed by the chunk - /// producer who is supposed to produce it. - pub fn produce_chunk_signed_by_chunk_producer( - &mut self, - shard_id: ShardId, - ) -> TestChunkEncoder { - let receipts = Vec::new(); - let epoch_id = - self.epoch_manager.get_epoch_id_from_prev_block(&self.tip.last_block_hash).unwrap(); - let shard_layout = self.epoch_manager.get_shard_layout(&epoch_id).unwrap(); - let receipts_hashes = Chain::build_receipts_hashes(&receipts, &shard_layout); - let (receipts_root, _) = merkle::merklize(&receipts_hashes); - let chunk_producer = self.next_chunk_producer(shard_id); - let signer = create_test_signer(chunk_producer.as_str()); - let data_parts = self.epoch_manager.num_data_parts(); - let parity_parts = self.epoch_manager.num_total_parts() - data_parts; - let rs = ReedSolomon::new(data_parts, parity_parts).unwrap(); - let (chunk, merkle_paths) = ShardsManagerActor::create_encoded_shard_chunk( - self.tip.last_block_hash, - CryptoHash::default(), - CryptoHash::default(), - self.tip.height + 1, - shard_id, - 0, - 1000, - 0, - Vec::new(), - Vec::new(), - &receipts, - receipts_root, - MerkleHash::default(), - CongestionInfo::default(), - &signer, - &rs, - PROTOCOL_VERSION, - ) - .unwrap(); - let receipt_proofs = - make_outgoing_receipts_proofs(&chunk.cloned_header(), &[], self.epoch_manager.as_ref()) - .unwrap() - .collect(); - TestChunkEncoder::new(chunk, merkle_paths, receipt_proofs) - } - - /// Produces the next chunk, asserting that we are the chunk producer. - pub fn produce_chunk(&mut self, shard_id: ShardId) -> TestChunkEncoder { - assert!(self.next_chunk_producer(shard_id) == self.account_id, "Cannot use produce_chunk if we are not the chunk producer; try produce_chunk_signed_by_chunk_producer."); - self.produce_chunk_signed_by_chunk_producer(shard_id) - } - - /// Distributes the produced chunk via the ShardsManager. - pub fn distribute_chunk(&mut self, chunk: &TestChunkEncoder) { - self.shards_manager.send(ShardsManagerRequestFromClient::DistributeEncodedChunk { - encoded_chunk: chunk.encoded_chunk.clone(), - merkle_paths: chunk.merkle_paths.clone(), - outgoing_receipts: Vec::new(), - partial_chunk: chunk.full_partial_chunk.clone(), - }); - } - - /// Whether we care about this shard this or next epoch, - /// as of the current tip. - pub fn cares_about_shard_this_or_next_epoch(&self, shard_id: ShardId) -> bool { - cares_about_shard_this_or_next_epoch( - Some(&self.account_id), - &self.tip.last_block_hash, - shard_id, - true, - &self.shard_tracker, - ) - } -} - -/// A helper struct for encoding partial chunks, for a specific chunk. -pub struct TestChunkEncoder { - encoded_chunk: EncodedShardChunk, - full_partial_chunk: PartialEncodedChunk, - merkle_paths: Vec, -} - -impl TestChunkEncoder { - pub fn new( - encoded_chunk: EncodedShardChunk, - merkle_paths: Vec, - receipt_proofs: Vec, - ) -> Self { - let all_part_ords = - encoded_chunk.content().parts.iter().enumerate().map(|(i, _)| i as u64).collect(); - let full_partial_chunk = encoded_chunk.create_partial_encoded_chunk( - all_part_ords, - receipt_proofs, - &merkle_paths, - ); - Self { encoded_chunk, full_partial_chunk, merkle_paths } - } - - pub fn part_ords(&self) -> Vec { - self.full_partial_chunk.parts().iter().map(|part| part.part_ord).collect() - } - - pub fn make_partial_encoded_chunk( - &self, - part_ords: &[u64], - receipt_shards: &[ShardId], - ) -> PartialEncodedChunk { - let parts = part_ords - .iter() - .copied() - .flat_map(|ord| { - self.full_partial_chunk.parts().iter().find(|part| part.part_ord == ord) - }) - .cloned() - .collect(); - PartialEncodedChunk::V2(PartialEncodedChunkV2 { - header: self.encoded_chunk.cloned_header(), - parts, - prev_outgoing_receipts: self - .full_partial_chunk - .prev_outgoing_receipts() - .iter() - .enumerate() - .filter(|(i, _)| receipt_shards.contains(&(*i as ShardId))) - .map(|(_, receipt)| receipt.clone()) - .collect(), - }) - } - - pub fn header(&self) -> ShardChunkHeader { - self.encoded_chunk.cloned_header() - } -} diff --git a/chain/chunks/src/test_utils.rs b/chain/chunks/src/test_utils.rs index bc4b7cea7e0..4a2e18d2140 100644 --- a/chain/chunks/src/test_utils.rs +++ b/chain/chunks/src/test_utils.rs @@ -17,7 +17,7 @@ use near_primitives::sharding::{ use near_primitives::test_utils::create_test_signer; use near_primitives::types::MerkleHash; use near_primitives::types::{AccountId, EpochId, ShardId}; -use near_primitives::version::PROTOCOL_VERSION; +use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use near_store::test_utils::create_test_store; use near_store::Store; use reed_solomon_erasure::galois_8::ReedSolomon; @@ -27,9 +27,7 @@ use std::sync::{Arc, Mutex, RwLock}; use crate::adapter::ShardsManagerRequestFromClient; use crate::client::ShardsManagerResponse; use crate::shards_manager_actor::ShardsManagerActor; -use crate::test_loop::ShardsManagerResendChunkRequests; -/// Deprecated. Use `MockChainForShardsManager`. pub struct ChunkTestFixture { pub store: Store, pub epoch_manager: EpochManagerHandle, @@ -136,6 +134,9 @@ impl ChunkTestFixture { let shard_layout = epoch_manager.get_shard_layout(&EpochId::default()).unwrap(); let receipts_hashes = Chain::build_receipts_hashes(&receipts, &shard_layout); let (receipts_root, _) = merkle::merklize(&receipts_hashes); + let congestion_info = ProtocolFeature::CongestionControl + .enabled(PROTOCOL_VERSION) + .then_some(CongestionInfo::default()); let (mock_chunk, mock_merkle_paths) = ShardsManagerActor::create_encoded_shard_chunk( mock_parent_hash, Default::default(), @@ -150,7 +151,7 @@ impl ChunkTestFixture { &receipts, receipts_root, MerkleHash::default(), - CongestionInfo::default(), + congestion_info, &signer, &rs, PROTOCOL_VERSION, @@ -278,6 +279,9 @@ impl MockClientAdapterForShardsManager { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ShardsManagerResendChunkRequests; + // Allows ShardsManagerActor-like behavior, except without having to spawn an actor, // and without having to manually route ShardsManagerRequest messages. This only works // for single-threaded (synchronous) tests. The ShardsManager is immediately called diff --git a/chain/client-primitives/Cargo.toml b/chain/client-primitives/Cargo.toml index 803d566e950..d2ad45f7b5a 100644 --- a/chain/client-primitives/Cargo.toml +++ b/chain/client-primitives/Cargo.toml @@ -22,7 +22,7 @@ time.workspace = true tracing.workspace = true yansi.workspace = true -near-async.workspace = true +near-time.workspace = true near-chain-primitives.workspace = true near-chain-configs.workspace = true near-chunks-primitives.workspace = true @@ -31,17 +31,13 @@ near-primitives.workspace = true [features] nightly_protocol = [ - "near-async/nightly_protocol", "near-chain-configs/nightly_protocol", "near-primitives/nightly_protocol", ] nightly = [ - "near-async/nightly", "near-chain-configs/nightly", "near-primitives/nightly", "nightly_protocol", ] sandbox = [] -test_features = [ - "near-primitives/test_features", -] +test_features = ["near-primitives/test_features"] diff --git a/chain/client-primitives/src/debug.rs b/chain/client-primitives/src/debug.rs index 0926db64b9b..aacf1128e06 100644 --- a/chain/client-primitives/src/debug.rs +++ b/chain/client-primitives/src/debug.rs @@ -1,7 +1,7 @@ //! Structs in this module are used for debug purposes, and might change at any time //! without backwards compatibility of JSON encoding. use crate::types::StatusError; -use near_async::time::Utc; +use near_primitives::congestion_info::CongestionInfo; use near_primitives::types::EpochId; use near_primitives::views::{ CatchupStatusView, ChainProcessingInfo, EpochValidatorInfo, RequestedStatePartsView, @@ -14,6 +14,7 @@ use near_primitives::{ types::{AccountId, BlockHeight}, views::ValidatorInfo, }; +use near_time::Utc; use std::collections::HashMap; #[derive(serde::Serialize, serde::Deserialize, Debug)] @@ -24,6 +25,7 @@ pub struct TrackedShardsView { #[derive(serde::Serialize, serde::Deserialize, Debug)] pub struct EpochInfoView { + pub epoch_height: u64, pub epoch_id: CryptoHash, pub height: BlockHeight, pub first_block: Option<(CryptoHash, Utc)>, @@ -42,6 +44,12 @@ pub struct DebugChunkStatus { pub gas_used: u64, #[serde(skip_serializing_if = "Option::is_none")] pub processing_time_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub congestion_level: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub congestion_info: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub endorsement_ratio: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] diff --git a/chain/client-primitives/src/types.rs b/chain/client-primitives/src/types.rs index 5088e1dc5c1..8b81916423a 100644 --- a/chain/client-primitives/src/types.rs +++ b/chain/client-primitives/src/types.rs @@ -93,7 +93,7 @@ pub enum ShardSyncStatus { StateDownloadHeader, StateDownloadParts, StateApplyScheduling, - StateApplyComplete, + StateApplyInProgress, StateApplyFinalizing, ReshardingScheduling, ReshardingApplying, @@ -106,7 +106,7 @@ impl ShardSyncStatus { ShardSyncStatus::StateDownloadHeader => 0, ShardSyncStatus::StateDownloadParts => 1, ShardSyncStatus::StateApplyScheduling => 2, - ShardSyncStatus::StateApplyComplete => 3, + ShardSyncStatus::StateApplyInProgress => 3, ShardSyncStatus::StateApplyFinalizing => 4, ShardSyncStatus::ReshardingScheduling => 5, ShardSyncStatus::ReshardingApplying => 6, @@ -130,7 +130,7 @@ impl ToString for ShardSyncStatus { ShardSyncStatus::StateDownloadHeader => "header".to_string(), ShardSyncStatus::StateDownloadParts => "parts".to_string(), ShardSyncStatus::StateApplyScheduling => "apply scheduling".to_string(), - ShardSyncStatus::StateApplyComplete => "apply complete".to_string(), + ShardSyncStatus::StateApplyInProgress => "apply in progress".to_string(), ShardSyncStatus::StateApplyFinalizing => "apply finalizing".to_string(), ShardSyncStatus::ReshardingScheduling => "resharding scheduling".to_string(), ShardSyncStatus::ReshardingApplying => "resharding applying".to_string(), diff --git a/chain/client/Cargo.toml b/chain/client/Cargo.toml index 00ff4c9905f..2d0b35d7445 100644 --- a/chain/client/Cargo.toml +++ b/chain/client/Cargo.toml @@ -60,13 +60,14 @@ near-parameters.workspace = true near-performance-metrics-macros.workspace = true near-performance-metrics.workspace = true near-pool.workspace = true -near-primitives.workspace = true +near-primitives = { workspace = true, features = ["clock"] } near-store.workspace = true near-telemetry.workspace = true near-vm-runner.workspace = true [dev-dependencies] assert_matches.workspace = true +near-primitives = { workspace = true, features = ["clock", "solomon", "rand"] } near-actix-test-utils.workspace = true [features] @@ -121,10 +122,7 @@ nightly = [ sandbox = [ "near-client-primitives/sandbox", "near-chain/sandbox", + "near-o11y/sandbox", ] -new_epoch_sync = [ - "near-chain/new_epoch_sync" -] -statelessnet_protocol = [ - "near-chain/statelessnet_protocol", -] +new_epoch_sync = ["near-chain/new_epoch_sync"] +statelessnet_protocol = ["near-chain/statelessnet_protocol"] diff --git a/chain/client/src/chunk_distribution_network.rs b/chain/client/src/chunk_distribution_network.rs index 51b2e973752..3a1b716f9f6 100644 --- a/chain/client/src/chunk_distribution_network.rs +++ b/chain/client/src/chunk_distribution_network.rs @@ -81,7 +81,7 @@ pub fn request_missing_chunks( client.clone(), chunk, shards_manager_adapter, - epoch_id.clone(), + epoch_id, ancestor_hash, ); } @@ -396,7 +396,7 @@ mod tests { hash(&[height.to_le_bytes().as_slice(), shard_id.to_le_bytes().as_slice()].concat()); let mut mock_hashes = MockHashes::new(prev_block_hash); - let signer = EmptyValidatorSigner::default(); + let signer = EmptyValidatorSigner::default().into(); let header_inner = ShardChunkHeaderInner::V3(ShardChunkHeaderInnerV3 { prev_block_hash, prev_state_root: mock_hashes.next().unwrap(), diff --git a/chain/client/src/chunk_inclusion_tracker.rs b/chain/client/src/chunk_inclusion_tracker.rs index 0e19a747ebb..0dd82a33928 100644 --- a/chain/client/src/chunk_inclusion_tracker.rs +++ b/chain/client/src/chunk_inclusion_tracker.rs @@ -8,6 +8,7 @@ use near_primitives::hash::CryptoHash; use near_primitives::sharding::{ChunkHash, ShardChunkHeader}; use near_primitives::types::{AccountId, EpochId, ShardId}; use std::collections::HashMap; +use std::num::NonZeroUsize; use crate::metrics; use crate::stateless_validation::chunk_endorsement_tracker::{ @@ -51,9 +52,13 @@ pub struct ChunkInclusionTracker { impl ChunkInclusionTracker { pub fn new() -> Self { Self { - prev_block_to_chunk_hash_ready: LruCache::new(CHUNK_HEADERS_FOR_INCLUSION_CACHE_SIZE), + prev_block_to_chunk_hash_ready: LruCache::new( + NonZeroUsize::new(CHUNK_HEADERS_FOR_INCLUSION_CACHE_SIZE).unwrap(), + ), chunk_hash_to_chunk_info: HashMap::new(), - banned_chunk_producers: LruCache::new(NUM_EPOCH_CHUNK_PRODUCERS_TO_KEEP_IN_BLOCKLIST), + banned_chunk_producers: LruCache::new( + NonZeroUsize::new(NUM_EPOCH_CHUNK_PRODUCERS_TO_KEEP_IN_BLOCKLIST).unwrap(), + ), } } @@ -120,9 +125,8 @@ impl ChunkInclusionTracker { } fn is_banned(&self, epoch_id: &EpochId, chunk_info: &ChunkInfo) -> bool { - let banned = self - .banned_chunk_producers - .contains(&(epoch_id.clone(), chunk_info.chunk_producer.clone())); + let banned = + self.banned_chunk_producers.contains(&(*epoch_id, chunk_info.chunk_producer.clone())); if banned { tracing::warn!( target: "client", @@ -181,7 +185,7 @@ impl ChunkInclusionTracker { pub fn get_banned_chunk_producers(&self) -> Vec<(EpochId, Vec)> { let mut banned_chunk_producers: HashMap> = HashMap::new(); for ((epoch_id, account_id), _) in self.banned_chunk_producers.iter() { - banned_chunk_producers.entry(epoch_id.clone()).or_default().push(account_id.clone()); + banned_chunk_producers.entry(*epoch_id).or_default().push(account_id.clone()); } banned_chunk_producers.into_iter().collect_vec() } diff --git a/chain/client/src/client.rs b/chain/client/src/client.rs index 5e1ef5cc7c3..5883f967d18 100644 --- a/chain/client/src/client.rs +++ b/chain/client/src/client.rs @@ -39,12 +39,13 @@ use near_chain::{ BlockProcessingArtifact, BlockStatus, Chain, ChainGenesis, ChainStoreAccess, Doomslug, DoomslugThresholdMode, Provenance, }; -use near_chain_configs::{ClientConfig, LogSummaryStyle, UpdateableClientConfig}; +use near_chain_configs::{ + ClientConfig, LogSummaryStyle, MutableValidatorSigner, UpdateableClientConfig, +}; use near_chunks::adapter::ShardsManagerRequestFromClient; use near_chunks::client::ShardedTransactionPool; use near_chunks::logic::{ - cares_about_shard_this_or_next_epoch, decode_encoded_chunk, - get_shards_cares_about_this_or_next_epoch, persist_chunk, + cares_about_shard_this_or_next_epoch, decode_encoded_chunk, persist_chunk, }; use near_chunks::shards_manager_actor::ShardsManagerActor; use near_client_primitives::debug::ChunkProduction; @@ -71,7 +72,7 @@ use near_primitives::network::PeerId; use near_primitives::receipt::Receipt; use near_primitives::sharding::StateSyncInfo; use near_primitives::sharding::{ - ChunkHash, EncodedShardChunk, PartialEncodedChunk, ShardChunk, ShardChunkHeader, ShardInfo, + EncodedShardChunk, PartialEncodedChunk, ShardChunk, ShardChunkHeader, ShardInfo, }; use near_primitives::transaction::SignedTransaction; use near_primitives::types::chunk_extra::ChunkExtra; @@ -85,6 +86,7 @@ use near_store::ShardUId; use reed_solomon_erasure::galois_8::ReedSolomon; use std::cmp::max; use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; use std::sync::Arc; use std::sync::RwLock; use time::ext::InstantExt as _; @@ -145,8 +147,10 @@ pub struct Client { pub sharded_tx_pool: ShardedTransactionPool, /// Network adapter. pub network_adapter: PeerManagerAdapter, - /// Signer for block producer (if present). - pub validator_signer: Option>, + /// Signer for block producer (if present). This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + pub validator_signer: MutableValidatorSigner, /// Approvals for which we do not have the block yet pub pending_approvals: lru::LruCache>, @@ -197,38 +201,32 @@ pub struct Client { chunk_distribution_network: Option, } +impl AsRef for Client { + fn as_ref(&self) -> &Client { + self + } +} + impl Client { - pub(crate) fn update_client_config(&self, update_client_config: UpdateableClientConfig) { - self.config.expected_shutdown.update(update_client_config.expected_shutdown); - self.config.resharding_config.update(update_client_config.resharding_config); - self.config + pub(crate) fn update_client_config( + &self, + update_client_config: UpdateableClientConfig, + ) -> bool { + let mut is_updated = false; + is_updated |= self.config.expected_shutdown.update(update_client_config.expected_shutdown); + is_updated |= self.config.resharding_config.update(update_client_config.resharding_config); + is_updated |= self + .config .produce_chunk_add_transactions_time_limit .update(update_client_config.produce_chunk_add_transactions_time_limit); + is_updated } -} -// Debug information about the upcoming block. -#[derive(Default)] -pub struct BlockDebugStatus { - // How long is this block 'in progress' (time since we first saw it). - pub in_progress_for: Option, - // How long is this block in orphan pool. - pub in_orphan_for: Option, - // List of chunk hashes that belong to this block. - pub chunk_hashes: Vec, - - // Chunk statuses are below: - // We first sent the request to fetch the chunk - // Later we get the response from the peer and we try to reconstruct it. - // If reconstructions succeeds, the chunk will be marked as complete. - // If it fails (or fragments are missing) - we're going to re-request the chunk again. - - // Chunks that we reqeusted (sent the request to peers). - pub chunks_requested: HashSet, - // Chunks for which we've received the response. - pub chunks_received: HashSet, - // Chunks completed - fully rebuild and present in database. - pub chunks_completed: HashSet, + /// Updates client's mutable validator signer. + /// It will update all validator signers that synchronize with it. + pub(crate) fn update_validator_signer(&self, signer: Arc) -> bool { + self.validator_signer.update(Some(signer)) + } } pub struct ProduceChunkResult { @@ -248,8 +246,8 @@ impl Client { state_sync_adapter: Arc>, runtime_adapter: Arc, network_adapter: PeerManagerAdapter, - shards_manager_adapter: Sender, - validator_signer: Option>, + shards_manager_sender: Sender, + validator_signer: MutableValidatorSigner, enable_doomslug: bool, rng_seed: RngSeed, snapshot_callbacks: Option, @@ -276,7 +274,7 @@ impl Client { chain_config.clone(), snapshot_callbacks, async_computation_spawner.clone(), - validator_signer.as_ref().map(|x| x.validator_id()), + validator_signer.clone(), )?; // Create flat storage or initiate migration to flat storage. let flat_storage_creator = FlatStorageCreator::new( @@ -292,8 +290,8 @@ impl Client { let epoch_sync = EpochSync::new( clock.clone(), network_adapter.clone(), - genesis_block.header().epoch_id().clone(), - genesis_block.header().next_epoch_id().clone(), + *genesis_block.header().epoch_id(), + *genesis_block.header().next_epoch_id(), epoch_manager .get_epoch_block_producers_ordered( genesis_block.header().epoch_id(), @@ -355,7 +353,6 @@ impl Client { config.max_block_production_delay, config.max_block_production_delay / 10, config.max_block_wait_delay, - validator_signer.clone(), doomslug_threshold_mode, ); let chunk_endorsement_tracker = @@ -364,7 +361,6 @@ impl Client { let panic_on_validation_error = config.chain_id != near_primitives::chains::MAINNET && config.chain_id != near_primitives::chains::TESTNET; let chunk_validator = ChunkValidator::new( - validator_signer.clone(), epoch_manager.clone(), network_adapter.clone().into_sender(), runtime_adapter.clone(), @@ -394,11 +390,13 @@ impl Client { epoch_manager, shard_tracker, runtime_adapter, - shards_manager_adapter, + shards_manager_adapter: shards_manager_sender, sharded_tx_pool, network_adapter, validator_signer, - pending_approvals: lru::LruCache::new(num_block_producer_seats), + pending_approvals: lru::LruCache::new( + NonZeroUsize::new(num_block_producer_seats).unwrap(), + ), catchup_state_syncs: HashMap::new(), epoch_sync, header_sync, @@ -406,10 +404,14 @@ impl Client { state_sync, challenges: Default::default(), rs_for_chunk_production: ReedSolomon::new(data_parts, parity_parts).unwrap(), - rebroadcasted_blocks: lru::LruCache::new(NUM_REBROADCAST_BLOCKS), + rebroadcasted_blocks: lru::LruCache::new( + NonZeroUsize::new(NUM_REBROADCAST_BLOCKS).unwrap(), + ), last_time_head_progress_made: clock.now(), block_production_info: BlockProductionTracker::new(), - chunk_production_info: lru::LruCache::new(PRODUCTION_TIMES_CACHE_SIZE), + chunk_production_info: lru::LruCache::new( + NonZeroUsize::new(PRODUCTION_TIMES_CACHE_SIZE).unwrap(), + ), tier1_accounts_cache: None, flat_storage_creator, last_time_sync_block_requested: HashMap::new(), @@ -596,11 +598,9 @@ impl Client { height: BlockHeight, prev_hash: CryptoHash, ) -> Result, Error> { - let validator_signer = self - .validator_signer - .as_ref() - .ok_or_else(|| Error::BlockProducer("Called without block producer info.".to_string()))? - .clone(); + let validator_signer = self.validator_signer.get().ok_or_else(|| { + Error::BlockProducer("Called without block producer info.".to_string()) + })?; // Check that we are were called at the block that we are producer for. let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(&prev_hash).unwrap(); @@ -608,7 +608,7 @@ impl Client { let prev = self.chain.get_block_header(&prev_hash)?; let prev_height = prev.height(); - let prev_epoch_id = prev.epoch_id().clone(); + let prev_epoch_id = *prev.epoch_id(); let prev_next_bp_hash = *prev.next_bp_hash(); // Check and update the doomslug tip here. This guarantees that our endorsement will be in the @@ -710,7 +710,7 @@ impl Client { Chain::compute_bp_hash( self.epoch_manager.as_ref(), next_epoch_id, - epoch_id.clone(), + epoch_id, &prev_hash, )? } else { @@ -718,9 +718,9 @@ impl Client { }; #[cfg(feature = "sandbox")] - let block_timestamp = self.clock.now_utc() + self.sandbox_delta_time(); + let sandbox_delta_time = Some(self.sandbox_delta_time()); #[cfg(not(feature = "sandbox"))] - let block_timestamp = self.clock.now_utc(); + let sandbox_delta_time = None; // Get block extra from previous block. let block_merkle_tree = self.chain.chain_store().get_block_merkle_tree(&prev_hash)?; @@ -813,7 +813,8 @@ impl Client { &*validator_signer, next_bp_hash, block_merkle_root, - block_timestamp, + self.clock.clone(), + sandbox_delta_time, ); // Update latest known even before returning block out, to prevent race conditions. @@ -833,18 +834,17 @@ impl Client { last_header: ShardChunkHeader, next_height: BlockHeight, shard_id: ShardId, + signer: Option<&Arc>, ) -> Result, Error> { - let validator_signer = self - .validator_signer - .as_ref() - .ok_or_else(|| Error::ChunkProducer("Called without block producer info.".to_string()))? - .clone(); + let signer = signer.ok_or_else(|| { + Error::ChunkProducer("Called without block producer info.".to_string()) + })?; let chunk_proposer = self.epoch_manager.get_chunk_producer(epoch_id, next_height, shard_id).unwrap(); - if validator_signer.validator_id() != &chunk_proposer { + if signer.validator_id() != &chunk_proposer { debug!(target: "client", - me = ?validator_signer.validator_id(), + me = ?signer.as_ref().validator_id(), ?chunk_proposer, next_height, shard_id, @@ -852,14 +852,7 @@ impl Client { return Ok(None); } - self.produce_chunk( - prev_block, - epoch_id, - last_header, - next_height, - shard_id, - validator_signer, - ) + self.produce_chunk(prev_block, epoch_id, last_header, next_height, shard_id, signer) } #[instrument(target = "client", level = "debug", "produce_chunk", skip_all, fields( @@ -875,7 +868,7 @@ impl Client { last_header: ShardChunkHeader, next_height: BlockHeight, shard_id: ShardId, - validator_signer: Arc, + validator_signer: &Arc, ) -> Result, Error> { let span = tracing::Span::current(); let timer = Instant::now(); @@ -934,11 +927,7 @@ impl Client { #[cfg(feature = "test_features")] let gas_used = if self.produce_invalid_chunks { gas_used + 1 } else { gas_used }; - // The congestion info is set to default if it is not present. If the - // congestion control feature is not enabled the congestion info will be - // stripped from the chunk header anyway. In the first chunk where - // feature is enabled the header will contain the default congestion info. - let congestion_info = chunk_extra.congestion_info().unwrap_or_default(); + let congestion_info = chunk_extra.congestion_info(); let (encoded_chunk, merkle_paths) = ShardsManagerActor::create_encoded_shard_chunk( prev_block_hash, *chunk_extra.state_root(), @@ -1097,8 +1086,12 @@ impl Client { Ok(prepared_transactions) } - pub fn send_challenges(&mut self, challenges: Vec) { - if let Some(validator_signer) = &self.validator_signer { + fn send_challenges( + &mut self, + challenges: Vec, + signer: &Option>, + ) { + if let Some(validator_signer) = &signer { for body in challenges { let challenge = Challenge::produce(body, &**validator_signer); self.challenges.insert(challenge.hash, challenge.clone()); @@ -1117,13 +1110,14 @@ impl Client { peer_id: PeerId, was_requested: bool, apply_chunks_done_sender: Option>, + signer: &Option>, ) { let hash = *block.hash(); let prev_hash = *block.header().prev_hash(); let _span = tracing::debug_span!( target: "client", "receive_block", - me = ?self.validator_signer.as_ref().map(|vs| vs.validator_id()), + me = ?signer.as_ref().map(|vs| vs.validator_id()), %prev_hash, %hash, height = block.header().height(), @@ -1131,7 +1125,13 @@ impl Client { was_requested) .entered(); - let res = self.receive_block_impl(block, peer_id, was_requested, apply_chunks_done_sender); + let res = self.receive_block_impl( + block, + peer_id, + was_requested, + apply_chunks_done_sender, + signer, + ); // Log the errors here. Note that the real error handling logic is already // done within process_block_impl, this is just for logging. if let Err(err) = res { @@ -1166,6 +1166,7 @@ impl Client { peer_id: PeerId, was_requested: bool, apply_chunks_done_sender: Option>, + signer: &Option>, ) -> Result<(), near_chain::Error> { let _span = debug_span!(target: "chain", "receive_block_impl", was_requested, ?peer_id).entered(); @@ -1194,7 +1195,7 @@ impl Client { self.verify_and_rebroadcast_block(&block, was_requested, &peer_id)?; let provenance = if was_requested { near_chain::Provenance::SYNC } else { near_chain::Provenance::NONE }; - let res = self.start_process_block(block, provenance, apply_chunks_done_sender); + let res = self.start_process_block(block, provenance, apply_chunks_done_sender, signer); match &res { Err(near_chain::Error::Orphan) => { debug!(target: "chain", ?prev_hash, "Orphan error"); @@ -1299,6 +1300,7 @@ impl Client { block: MaybeValidated, provenance: Provenance, apply_chunks_done_sender: Option>, + signer: &Option>, ) -> Result<(), near_chain::Error> { let _span = debug_span!( target: "chain", @@ -1309,10 +1311,7 @@ impl Client { let mut block_processing_artifacts = BlockProcessingArtifact::default(); let result = { - let me = self - .validator_signer - .as_ref() - .map(|validator_signer| validator_signer.validator_id().clone()); + let me = signer.as_ref().map(|vs| vs.validator_id().clone()); self.chain.start_process_block_async( &me, block, @@ -1322,17 +1321,17 @@ impl Client { ) }; - self.process_block_processing_artifact(block_processing_artifacts); + self.process_block_processing_artifact(block_processing_artifacts, &signer); // Send out challenge if the block was found to be invalid. - if let Some(validator_signer) = self.validator_signer.as_ref() { + if let Some(signer) = signer { if let Err(e) = &result { match e { near_chain::Error::InvalidChunkProofs(chunk_proofs) => { self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( NetworkRequests::Challenge(Challenge::produce( ChallengeBody::ChunkProofs(*chunk_proofs.clone()), - &**validator_signer, + &*signer, )), )); } @@ -1340,7 +1339,7 @@ impl Client { self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( NetworkRequests::Challenge(Challenge::produce( ChallengeBody::ChunkState(*chunk_state.clone()), - &**validator_signer, + &*signer, )), )); } @@ -1358,13 +1357,11 @@ impl Client { &mut self, apply_chunks_done_sender: Option>, should_produce_chunk: bool, + signer: &Option>, ) -> (Vec, HashMap) { let _span = debug_span!(target: "client", "postprocess_ready_blocks", should_produce_chunk) .entered(); - let me = self - .validator_signer - .as_ref() - .map(|validator_signer| validator_signer.validator_id().clone()); + let me = signer.as_ref().map(|signer| signer.validator_id().clone()); let mut block_processing_artifacts = BlockProcessingArtifact::default(); let (accepted_blocks, errors) = self.chain.postprocess_ready_blocks( &me, @@ -1377,7 +1374,7 @@ impl Client { header_head: self.chain.header_head().unwrap(), }); } - self.process_block_processing_artifact(block_processing_artifacts); + self.process_block_processing_artifact(block_processing_artifacts, signer); let accepted_blocks_hashes = accepted_blocks.iter().map(|accepted_block| accepted_block.hash).collect(); for accepted_block in accepted_blocks { @@ -1386,6 +1383,7 @@ impl Client { accepted_block.status, accepted_block.provenance, !should_produce_chunk, + signer, ); } self.last_time_head_progress_made = @@ -1400,6 +1398,7 @@ impl Client { pub(crate) fn process_block_processing_artifact( &mut self, block_processing_artifacts: BlockProcessingArtifact, + signer: &Option>, ) { let BlockProcessingArtifact { orphans_missing_chunks, @@ -1408,7 +1407,7 @@ impl Client { invalid_chunks, } = block_processing_artifacts; // Send out challenges that accumulated via on_challenge. - self.send_challenges(challenges); + self.send_challenges(challenges, &signer); // For any missing chunk, let the ShardsManager know of the chunk header so that it may // apply forwarded parts. This may end up completing the chunk. let missing_chunks = blocks_missing_chunks @@ -1467,6 +1466,7 @@ impl Client { partial_chunk: PartialEncodedChunk, shard_chunk: Option, apply_chunks_done_sender: Option>, + signer: &Option>, ) { let chunk_header = partial_chunk.cloned_header(); self.chain.blocks_delay_tracker.mark_chunk_completed(&chunk_header); @@ -1481,7 +1481,7 @@ impl Client { // We're marking chunk as accepted. self.chain.blocks_with_missing_chunks.accept_chunk(&chunk_header.chunk_hash()); // If this was the last chunk that was missing for a block, it will be processed now. - self.process_blocks_with_missing_chunks(apply_chunks_done_sender) + self.process_blocks_with_missing_chunks(apply_chunks_done_sender, &signer); } /// Called asynchronously when the ShardsManager finishes processing a chunk but the chunk @@ -1497,10 +1497,11 @@ impl Client { pub fn sync_block_headers( &mut self, headers: Vec, + signer: &Option>, ) -> Result<(), near_chain::Error> { let mut challenges = vec![]; self.chain.sync_block_headers(headers, &mut challenges)?; - self.send_challenges(challenges); + self.send_challenges(challenges, signer); self.shards_manager_adapter.send(ShardsManagerRequestFromClient::UpdateChainHeads { head: self.chain.head().unwrap(), header_head: self.chain.header_head().unwrap(), @@ -1566,12 +1567,14 @@ impl Client { &mut self, parent_hash: &CryptoHash, approval: Approval, + signer: &Option>, ) -> Result<(), Error> { let next_epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(parent_hash)?; let next_block_producer = self.epoch_manager.get_block_producer(&next_epoch_id, approval.target_height)?; - if Some(&next_block_producer) == self.validator_signer.as_ref().map(|x| x.validator_id()) { - self.collect_block_approval(&approval, ApprovalType::SelfApproval); + let next_block_producer_id = signer.as_ref().map(|x| x.validator_id()); + if Some(&next_block_producer) == next_block_producer_id { + self.collect_block_approval(&approval, ApprovalType::SelfApproval, signer); } else { debug!(target: "client", approval_inner = ?approval.inner, @@ -1591,12 +1594,13 @@ impl Client { /// Gets called when block got accepted. /// Only produce chunk if `skip_produce_chunk` is false. /// `skip_produce_chunk` is set to true to simulate when there are missing chunks in a block - pub fn on_block_accepted_with_optional_chunk_produce( + fn on_block_accepted_with_optional_chunk_produce( &mut self, block_hash: CryptoHash, status: BlockStatus, provenance: Provenance, skip_produce_chunk: bool, + signer: &Option>, ) { let _span = tracing::debug_span!( target: "client", @@ -1633,7 +1637,7 @@ impl Client { for (_account_id, (approval, approval_type)) in endorsements.into_iter().chain(skips.into_iter()) { - self.collect_block_approval(&approval, approval_type); + self.collect_block_approval(&approval, approval_type, signer); } } @@ -1674,10 +1678,10 @@ impl Client { } } - if let Some(validator_signer) = self.validator_signer.clone() { - let validator_id = validator_signer.validator_id().clone(); + if let Some(signer) = signer.clone() { + let validator_id = signer.validator_id().clone(); - if !self.reconcile_transaction_pool(validator_id.clone(), status, &block) { + if !self.reconcile_transaction_pool(validator_id, status, &block) { return; } @@ -1685,7 +1689,7 @@ impl Client { && !self.sync_status.is_syncing() && !skip_produce_chunk { - self.produce_chunks(&block, validator_id); + self.produce_chunks(&block, &signer); } else { info!(target: "client", "not producing a chunk"); } @@ -1708,7 +1712,7 @@ impl Client { self.shards_manager_adapter .send(ShardsManagerRequestFromClient::CheckIncompleteChunks(*block.hash())); - self.process_ready_orphan_witnesses_and_clean_old(&block); + self.process_ready_orphan_witnesses_and_clean_old(&block, signer); } /// Reconcile the transaction pool after processing a block. @@ -1780,7 +1784,8 @@ impl Client { } // Produce new chunks - fn produce_chunks(&mut self, block: &Block, validator_id: AccountId) { + fn produce_chunks(&mut self, block: &Block, signer: &Arc) { + let validator_id = signer.validator_id().clone(); let _span = debug_span!( target: "client", "produce_chunks", @@ -1828,6 +1833,7 @@ impl Client { last_header.clone(), next_height, shard_id, + Some(signer), ) { Ok(Some(result)) => { let shard_chunk = self @@ -1844,6 +1850,7 @@ impl Client { &last_header, &shard_chunk, result.transactions_storage_proof, + &Some(signer.clone()), ) { tracing::error!(target: "client", ?err, "Failed to send chunk state witness to chunk validators"); } @@ -1856,6 +1863,18 @@ impl Client { } } + pub fn mark_chunk_header_ready_for_inclusion( + &mut self, + chunk_header: ShardChunkHeader, + chunk_producer: AccountId, + ) { + // If endorsement was received before chunk header, we can process it + // only now. + self.chunk_endorsement_tracker.process_pending_endorsements(&chunk_header); + self.chunk_inclusion_tracker + .mark_chunk_header_ready_for_inclusion(chunk_header, chunk_producer); + } + pub fn persist_and_distribute_encoded_chunk( &mut self, encoded_chunk: EncodedShardChunk, @@ -1889,8 +1908,7 @@ impl Client { } } - self.chunk_inclusion_tracker - .mark_chunk_header_ready_for_inclusion(chunk_header, validator_id); + self.mark_chunk_header_ready_for_inclusion(chunk_header, validator_id); self.shards_manager_adapter.send(ShardsManagerRequestFromClient::DistributeEncodedChunk { partial_chunk, encoded_chunk, @@ -1960,21 +1978,26 @@ impl Client { pub fn process_blocks_with_missing_chunks( &mut self, apply_chunks_done_sender: Option>, + signer: &Option>, ) { let _span = debug_span!(target: "client", "process_blocks_with_missing_chunks").entered(); - let me = - self.validator_signer.as_ref().map(|validator_signer| validator_signer.validator_id()); + let me = signer.as_ref().map(|signer| signer.validator_id()); let mut blocks_processing_artifacts = BlockProcessingArtifact::default(); self.chain.check_blocks_with_missing_chunks( &me.map(|x| x.clone()), &mut blocks_processing_artifacts, apply_chunks_done_sender, ); - self.process_block_processing_artifact(blocks_processing_artifacts); + self.process_block_processing_artifact(blocks_processing_artifacts, signer); } - pub fn is_validator(&self, epoch_id: &EpochId, block_hash: &CryptoHash) -> bool { - match self.validator_signer.as_ref() { + pub fn is_validator( + &self, + epoch_id: &EpochId, + block_hash: &CryptoHash, + signer: &Option>, + ) -> bool { + match signer { None => false, Some(signer) => { let account_id = signer.validator_id(); @@ -2041,7 +2064,12 @@ impl Client { /// * `approval` - the approval to be collected /// * `approval_type` - whether the approval was just produced by us (in which case skip validation, /// only check whether we are the next block producer and store in Doomslug) - pub fn collect_block_approval(&mut self, approval: &Approval, approval_type: ApprovalType) { + pub fn collect_block_approval( + &mut self, + approval: &Approval, + approval_type: ApprovalType, + signer: &Option>, + ) { let Approval { inner, account_id, target_height, signature } = approval; let parent_hash = match inner { @@ -2099,7 +2127,7 @@ impl Client { &parent_hash, account_id, ) { - Ok(_) => next_block_epoch_id.clone(), + Ok(_) => next_block_epoch_id, Err(EpochError::NotAValidator(_, _)) => { match self.epoch_manager.get_next_epoch_id_from_prev_block(&parent_hash) { Ok(next_block_next_epoch_id) => next_block_next_epoch_id, @@ -2124,8 +2152,7 @@ impl Client { match self.epoch_manager.get_block_producer(&next_block_epoch_id, *target_height) { Err(_) => false, Ok(target_block_producer) => { - Some(&target_block_producer) - == self.validator_signer.as_ref().map(|x| x.validator_id()) + Some(&target_block_producer) == signer.as_ref().map(|x| x.validator_id()) } }; @@ -2157,7 +2184,12 @@ impl Client { } /// Forwards given transaction to upcoming validators. - fn forward_tx(&self, epoch_id: &EpochId, tx: &SignedTransaction) -> Result<(), Error> { + fn forward_tx( + &self, + epoch_id: &EpochId, + tx: &SignedTransaction, + signer: &Option>, + ) -> Result<(), Error> { let shard_id = self.epoch_manager.account_id_to_shard_id(tx.transaction.signer_id(), epoch_id)?; // Use the header head to make sure the list of validators is as @@ -2186,11 +2218,11 @@ impl Client { } } - if let Some(account_id) = self.validator_signer.as_ref().map(|bp| bp.validator_id()) { + if let Some(account_id) = signer.as_ref().map(|bp| bp.validator_id()) { validators.remove(account_id); } for validator in validators { - trace!(target: "client", me = ?self.validator_signer.as_ref().map(|bp| bp.validator_id()), ?tx, ?validator, shard_id, "Routing a transaction"); + trace!(target: "client", me = ?signer.as_ref().map(|bp| bp.validator_id()), ?tx, ?validator, shard_id, "Routing a transaction"); // Send message to network to actually forward transaction. self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( @@ -2212,8 +2244,9 @@ impl Client { is_forwarded: bool, check_only: bool, ) -> ProcessTxResponse { - unwrap_or_return!(self.process_tx_internal(&tx, is_forwarded, check_only), { - let me = self.validator_signer.as_ref().map(|vs| vs.validator_id()); + let signer = self.validator_signer.get(); + unwrap_or_return!(self.process_tx_internal(&tx, is_forwarded, check_only, &signer), { + let me = signer.as_ref().map(|signer| signer.validator_id()); warn!(target: "client", ?me, ?tx, "Dropping tx"); ProcessTxResponse::NoResponse }) @@ -2241,12 +2274,16 @@ impl Client { /// If we're a validator in one of the next few chunks, but epoch switch could happen soon, /// we forward to a validator from next epoch. - fn possibly_forward_tx_to_next_epoch(&mut self, tx: &SignedTransaction) -> Result<(), Error> { + fn possibly_forward_tx_to_next_epoch( + &mut self, + tx: &SignedTransaction, + signer: &Option>, + ) -> Result<(), Error> { let head = self.chain.head()?; if let Some(next_epoch_id) = self.get_next_epoch_id_if_at_boundary(&head)? { - self.forward_tx(&next_epoch_id, tx)?; + self.forward_tx(&next_epoch_id, tx, signer)?; } else { - self.forward_tx(&head.epoch_id, tx)?; + self.forward_tx(&head.epoch_id, tx, signer)?; } Ok(()) } @@ -2257,10 +2294,12 @@ impl Client { tx: &SignedTransaction, is_forwarded: bool, check_only: bool, + signer: &Option>, ) -> Result { let head = self.chain.head()?; - let me = self.validator_signer.as_ref().map(|vs| vs.validator_id()); - let cur_block_header = self.chain.head_header()?; + let me = signer.as_ref().map(|vs| vs.validator_id()); + let cur_block = self.chain.get_head_block()?; + let cur_block_header = cur_block.header(); let transaction_validity_period = self.chain.transaction_validity_period; // here it is fine to use `cur_block_header` as it is a best effort estimate. If the transaction // were to be included, the block that the chunk points to will have height >= height of @@ -2275,12 +2314,23 @@ impl Client { } let gas_price = cur_block_header.next_gas_price(); let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(&head.last_block_hash)?; - + let receiver_shard = + self.epoch_manager.account_id_to_shard_id(tx.transaction.receiver_id(), &epoch_id)?; + let receiver_congestion_info = + cur_block.block_congestion_info().get(&receiver_shard).copied(); let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id)?; if let Some(err) = self .runtime_adapter - .validate_tx(gas_price, None, tx, true, &epoch_id, protocol_version) + .validate_tx( + gas_price, + None, + tx, + true, + &epoch_id, + protocol_version, + receiver_congestion_info, + ) .expect("no storage errors") { debug!(target: "client", tx_hash = ?tx.get_hash(), ?err, "Invalid tx during basic validation"); @@ -2305,14 +2355,22 @@ impl Client { if is_forwarded { return Err(Error::Other("Node has not caught up yet".to_string())); } else { - self.forward_tx(&epoch_id, tx)?; + self.forward_tx(&epoch_id, tx, signer)?; return Ok(ProcessTxResponse::RequestRouted); } } }; if let Some(err) = self .runtime_adapter - .validate_tx(gas_price, Some(state_root), tx, false, &epoch_id, protocol_version) + .validate_tx( + gas_price, + Some(state_root), + tx, + false, + &epoch_id, + protocol_version, + receiver_congestion_info, + ) .expect("no storage errors") { debug!(target: "client", ?err, "Invalid tx"); @@ -2345,18 +2403,18 @@ impl Client { // Not active validator: // forward to current epoch validators, // possibly forward to next epoch validators - if self.active_validator(shard_id)? { + if self.active_validator(shard_id, signer)? { trace!(target: "client", account = ?me, shard_id, tx_hash = ?tx.get_hash(), is_forwarded, "Recording a transaction."); metrics::TRANSACTION_RECEIVED_VALIDATOR.inc(); if !is_forwarded { - self.possibly_forward_tx_to_next_epoch(tx)?; + self.possibly_forward_tx_to_next_epoch(tx, signer)?; } Ok(ProcessTxResponse::ValidTx) } else if !is_forwarded { trace!(target: "client", shard_id, tx_hash = ?tx.get_hash(), "Forwarding a transaction."); metrics::TRANSACTION_RECEIVED_NON_VALIDATOR.inc(); - self.forward_tx(&epoch_id, tx)?; + self.forward_tx(&epoch_id, tx, signer)?; Ok(ProcessTxResponse::RequestRouted) } else { trace!(target: "client", shard_id, tx_hash = ?tx.get_hash(), "Non-validator received a forwarded transaction, dropping it."); @@ -2372,17 +2430,21 @@ impl Client { Ok(ProcessTxResponse::NoResponse) } else { // We are not tracking this shard, so there is no way to validate this tx. Just rerouting. - self.forward_tx(&epoch_id, tx)?; + self.forward_tx(&epoch_id, tx, signer)?; Ok(ProcessTxResponse::RequestRouted) } } /// Determine if I am a validator in next few blocks for specified shard, assuming epoch doesn't change. - fn active_validator(&self, shard_id: ShardId) -> Result { + fn active_validator( + &self, + shard_id: ShardId, + signer: &Option>, + ) -> Result { let head = self.chain.head()?; let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(&head.last_block_hash)?; - let account_id = if let Some(vs) = self.validator_signer.as_ref() { + let account_id = if let Some(vs) = signer.as_ref() { vs.validator_id() } else { return Ok(false); @@ -2408,16 +2470,17 @@ impl Client { resharding_scheduler: &Sender, apply_chunks_done_sender: Option>, state_parts_future_spawner: &dyn FutureSpawner, + signer: &Option>, ) -> Result<(), Error> { let _span = debug_span!(target: "sync", "run_catchup").entered(); let mut notify_state_sync = false; - let me = &self.validator_signer.as_ref().map(|x| x.validator_id().clone()); + let me = signer.as_ref().map(|x| x.validator_id().clone()); for (sync_hash, state_sync_info) in self.chain.chain_store().iterate_state_sync_infos()? { assert_eq!(sync_hash, state_sync_info.epoch_tail_hash); let network_adapter = self.network_adapter.clone(); - let shards_to_split = self.get_shards_to_split(sync_hash, &state_sync_info, me)?; + let shards_to_split = self.get_shards_to_split(sync_hash, &state_sync_info, &me)?; let state_sync_timeout = self.config.state_sync_timeout; let block_header = self.chain.get_block(&sync_hash)?.header().clone(); let epoch_id = block_header.epoch_id(); @@ -2436,7 +2499,7 @@ impl Client { true, ), shards_to_split, - BlocksCatchUpState::new(sync_hash, epoch_id.clone()), + BlocksCatchUpState::new(sync_hash, *epoch_id), ) }); @@ -2453,20 +2516,6 @@ impl Client { .get_shard_layout(&epoch_id) .expect("Cannot get shard layout"); - // Make sure mem-tries for shards we do not care about are unloaded before we start a new state sync. - let shards_cares_this_or_next_epoch = get_shards_cares_about_this_or_next_epoch( - me.as_ref(), - true, - &block_header, - &self.shard_tracker, - self.epoch_manager.as_ref(), - ); - let shard_uids: Vec<_> = shards_cares_this_or_next_epoch - .iter() - .map(|id| self.epoch_manager.shard_id_to_uid(*id, &epoch_id).unwrap()) - .collect(); - self.runtime_adapter.get_tries().retain_mem_tries(&shard_uids); - for &shard_id in &tracking_shards { let shard_uid = ShardUId::from_shard_id_and_layout(shard_id, &shard_layout); match self.state_sync_adapter.clone().read() { @@ -2486,7 +2535,7 @@ impl Client { // for other shards later. let new_shard_sync = shards_to_split; match state_sync.run( - me, + &me, sync_hash, new_shard_sync, &mut self.chain, @@ -2504,7 +2553,7 @@ impl Client { StateSyncResult::Completed => { debug!(target: "catchup", "state sync completed now catch up blocks"); self.chain.catchup_blocks_step( - me, + &me, &sync_hash, blocks_catch_up_state, block_catch_up_task_scheduler, @@ -2514,14 +2563,14 @@ impl Client { let mut block_processing_artifacts = BlockProcessingArtifact::default(); self.chain.finish_catchup_blocks( - me, + &me, &sync_hash, &mut block_processing_artifacts, apply_chunks_done_sender.clone(), &blocks_catch_up_state.done_blocks, )?; - self.process_block_processing_artifact(block_processing_artifacts); + self.process_block_processing_artifact(block_processing_artifacts, &signer); } } } @@ -2711,7 +2760,7 @@ impl Client { } } let account_keys = Arc::new(account_keys); - self.tier1_accounts_cache = Some((tip.epoch_id.clone(), account_keys.clone())); + self.tier1_accounts_cache = Some((tip.epoch_id, account_keys.clone())); Ok(account_keys) } diff --git a/chain/client/src/client_actor.rs b/chain/client/src/client_actor.rs index 01ead9b0fde..4899dca9167 100644 --- a/chain/client/src/client_actor.rs +++ b/chain/client/src/client_actor.rs @@ -3,7 +3,7 @@ //! pass the control to Client. This means, any real block processing or production logic should //! be put in Client. //! Unfortunately, this is not the case today. We are in the process of refactoring ClientActor -//! https://github.com/near/nearcore/issues/7899 +//! #[cfg(feature = "test_features")] use crate::client::AdvProduceBlocksMode; @@ -25,7 +25,7 @@ use near_async::futures::{ use near_async::messaging::{self, CanSend, Handler, IntoMultiSender, LateBoundSender, Sender}; use near_async::time::{Clock, Utc}; use near_async::time::{Duration, Instant}; -use near_async::{MultiSend, MultiSendMessage, MultiSenderFrom}; +use near_async::{MultiSend, MultiSenderFrom}; use near_chain::chain::{ ApplyChunksDoneMessage, ApplyStatePartsRequest, ApplyStatePartsResponse, BlockCatchUpRequest, BlockCatchUpResponse, ChunkStateWitnessMessage, LoadMemtrieRequest, LoadMemtrieResponse, @@ -41,7 +41,7 @@ use near_chain::{ byzantine_assert, near_chain_primitives, Block, BlockHeader, BlockProcessingArtifact, ChainGenesis, Provenance, }; -use near_chain_configs::{ClientConfig, LogSummaryStyle, ReshardingHandle}; +use near_chain_configs::{ClientConfig, LogSummaryStyle, MutableValidatorSigner, ReshardingHandle}; use near_chain_primitives::error::EpochErrorResultToChainError; use near_chunks::adapter::ShardsManagerRequestFromClient; use near_chunks::client::ShardsManagerResponse; @@ -134,7 +134,7 @@ pub fn start_client( state_sync_adapter: Arc>, network_adapter: PeerManagerAdapter, shards_manager_adapter: Sender, - validator_signer: Option>, + validator_signer: MutableValidatorSigner, telemetry_sender: Sender, snapshot_callbacks: Option, sender: Option>, @@ -158,7 +158,7 @@ pub fn start_client( runtime.clone(), network_adapter.clone(), shards_manager_adapter, - validator_signer.clone(), + validator_signer, enable_doomslug, seed.unwrap_or_else(random_seed_from_thread), snapshot_callbacks, @@ -182,7 +182,6 @@ pub fn start_client( client_config, node_id, network_adapter, - validator_signer, telemetry_sender, sender, adv, @@ -201,14 +200,12 @@ pub fn start_client( StartClientResult { client_actor: client_addr, client_arbiter_handle, resharding_handle } } -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct ClientSenderForClient { pub apply_chunks_done: Sender, } -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct SyncJobsSenderForClient { pub apply_state_parts: Sender, pub load_memtrie: Sender, @@ -216,8 +213,7 @@ pub struct SyncJobsSenderForClient { pub resharding: Sender, } -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct ClientSenderForPartialWitness { pub chunk_state_witness: Sender, } @@ -257,7 +253,6 @@ pub struct ClientActorInner { // Last time when log_summary method was called. log_summary_timer_next_attempt: near_async::time::Utc, - block_production_started: bool, doomslug_timer_next_attempt: near_async::time::Utc, sync_timer_next_attempt: near_async::time::Utc, sync_started: bool, @@ -313,7 +308,6 @@ impl ClientActorInner { config: ClientConfig, node_id: PeerId, network_adapter: PeerManagerAdapter, - validator_signer: Option>, telemetry_sender: Sender, shutdown_signal: Option>, adv: crate::adversarial::Controls, @@ -321,11 +315,10 @@ impl ClientActorInner { sync_jobs_sender: SyncJobsSenderForClient, state_parts_future_spawner: Box, ) -> Result { - if let Some(vs) = &validator_signer { + if let Some(vs) = &client.validator_signer.get() { info!(target: "client", "Starting validator node: {}", vs.validator_id()); } - let info_helper = - InfoHelper::new(clock.clone(), telemetry_sender, &config, validator_signer.clone()); + let info_helper = InfoHelper::new(clock.clone(), telemetry_sender, &config); let now = clock.now_utc(); Ok(ClientActorInner { @@ -351,7 +344,6 @@ impl ClientActorInner { info_helper, block_production_next_attempt: now, log_summary_timer_next_attempt: now, - block_production_started: false, doomslug_timer_next_attempt: now, sync_timer_next_attempt: now, sync_started: false, @@ -412,6 +404,7 @@ impl Handler for ClientActorInner { } let start_height = self.client.chain.mut_chain_store().get_latest_known().unwrap().height + 1; + let signer = self.client.validator_signer.get(); let mut blocks_produced = 0; for height in start_height.. { let block = @@ -428,6 +421,7 @@ impl Handler for ClientActorInner { block.into(), Provenance::PRODUCED, Some(self.myself_sender.apply_chunks_done.clone()), + &signer, ); blocks_produced += 1; if blocks_produced == num_blocks { @@ -462,7 +456,7 @@ impl Handler for ClientActorInner { let mut genesis = near_chain_configs::GenesisConfig::default(); genesis.genesis_height = self.client.chain.chain_store().get_genesis_height(); let mut store_validator = near_chain::store_validator::StoreValidator::new( - self.client.validator_signer.as_ref().map(|x| x.validator_id().clone()), + self.client.validator_signer.get().map(|x| x.validator_id().clone()), genesis, self.client.epoch_manager.clone(), self.client.shard_tracker.clone(), @@ -511,11 +505,13 @@ impl Handler for ClientActorInner { // blocks other than the few special ones that State Sync expects. return; } + let signer = self.client.validator_signer.get(); self.client.receive_block( block, peer_id, was_requested, Some(self.myself_sender.apply_chunks_done.clone()), + &signer, ); } else { match self.client.epoch_manager.get_epoch_id_from_prev_block(block.header().prev_hash()) @@ -536,7 +532,8 @@ impl Handler for ClientActorInner { impl Handler for ClientActorInner { fn handle(&mut self, msg: BlockHeadersResponse) -> Result<(), ReasonForBan> { let BlockHeadersResponse(headers, peer_id) = msg; - if self.receive_headers(headers, peer_id) { + let validator_signer = self.client.validator_signer.get(); + if self.receive_headers(headers, peer_id, &validator_signer) { Ok(()) } else { warn!(target: "client", "Banning node for sending invalid block headers"); @@ -549,7 +546,12 @@ impl Handler for ClientActorInner { fn handle(&mut self, msg: BlockApproval) { let BlockApproval(approval, peer_id) = msg; debug!(target: "client", "Receive approval {:?} from peer {:?}", approval, peer_id); - self.client.collect_block_approval(&approval, ApprovalType::PeerApproval(peer_id)); + let validator_signer = self.client.validator_signer.get(); + self.client.collect_block_approval( + &approval, + ApprovalType::PeerApproval(peer_id), + &validator_signer, + ); } } @@ -708,7 +710,8 @@ impl Handler for ClientActorInner { .into_chain_error()?; let node_public_key = self.node_id.public_key().clone(); - let (validator_account_id, validator_public_key) = match &self.client.validator_signer { + let (validator_account_id, validator_public_key) = match &self.client.validator_signer.get() + { Some(vs) => (Some(vs.validator_id().clone()), Some(vs.public_key())), None => (None, None), }; @@ -827,7 +830,8 @@ impl Handler for ClientActorInner { impl Handler for ClientActorInner { fn handle(&mut self, _msg: ApplyChunksDoneMessage) { - self.try_process_unfinished_blocks(); + let validator_signer = self.client.validator_signer.get(); + self.try_process_unfinished_blocks(&validator_signer); } } @@ -889,11 +893,6 @@ impl ClientActorInner { // Start syncing job. self.start_sync(ctx); - // Start block production tracking if have block producer info. - if self.client.validator_signer.is_some() { - self.block_production_started = true; - } - // Start triggers self.schedule_triggers(ctx); @@ -907,7 +906,11 @@ impl ClientActorInner { /// Check if client Account Id should be sent and send it. /// Account Id is sent when is not current a validator but are becoming a validator soon. - fn check_send_announce_account(&mut self, prev_block_hash: CryptoHash) { + fn check_send_announce_account( + &mut self, + prev_block_hash: CryptoHash, + validator_signer: &Option>, + ) { // If no peers, there is no one to announce to. if self.network_info.num_connected_peers == 0 { debug!(target: "client", "No peers: skip account announce"); @@ -915,7 +918,7 @@ impl ClientActorInner { } // First check that we currently have an AccountId - let validator_signer = match self.client.validator_signer.as_ref() { + let signer = match validator_signer { None => return, Some(signer) => signer, }; @@ -930,7 +933,7 @@ impl ClientActorInner { } } - debug!(target: "client", "Check announce account for {}, last announce time {:?}", validator_signer.validator_id(), self.last_validator_announce_time); + debug!(target: "client", "Check announce account for {}, last announce time {:?}", signer.validator_id(), self.last_validator_announce_time); // Announce AccountId if client is becoming a validator soon. let next_epoch_id = unwrap_or_return!(self @@ -939,18 +942,15 @@ impl ClientActorInner { .get_next_epoch_id_from_prev_block(&prev_block_hash)); // Check client is part of the futures validators - if self.client.is_validator(&next_epoch_id, &prev_block_hash) { - debug!(target: "client", "Sending announce account for {}", validator_signer.validator_id()); + if self.client.is_validator(&next_epoch_id, &prev_block_hash, validator_signer) { + debug!(target: "client", "Sending announce account for {}", signer.validator_id()); self.last_validator_announce_time = Some(now); - let signature = validator_signer.sign_account_announce( - validator_signer.validator_id(), - &self.node_id, - &next_epoch_id, - ); + let signature = + signer.sign_account_announce(signer.validator_id(), &self.node_id, &next_epoch_id); self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( NetworkRequests::AnnounceAccount(AnnounceAccount { - account_id: validator_signer.validator_id().clone(), + account_id: signer.validator_id().clone(), peer_id: self.node_id.clone(), epoch_id: next_epoch_id, signature, @@ -1039,7 +1039,10 @@ impl ClientActorInner { /// Retrieves latest height, and checks if must produce next block. /// Otherwise wait for block arrival or suggest to skip after timeout. - fn handle_block_production(&mut self) -> Result<(), Error> { + fn handle_block_production( + &mut self, + signer: &Option>, + ) -> Result<(), Error> { let _span = tracing::debug_span!(target: "client", "handle_block_production").entered(); // If syncing, don't try to produce blocks. if self.client.sync_status.is_syncing() { @@ -1079,7 +1082,7 @@ impl ClientActorInner { debug!(target: "client", "Cannot produce any block: not enough approvals beyond {}", latest_known.height); } - let me = if let Some(me) = &self.client.validator_signer { + let me = if let Some(me) = signer { me.validator_id().clone() } else { return Ok(()); @@ -1124,7 +1127,7 @@ impl ClientActorInner { self.client .chunk_inclusion_tracker .record_endorsement_metrics(&head.last_block_hash); - if let Err(err) = self.produce_block(height) { + if let Err(err) = self.produce_block(height, signer) { // If there is an error, report it and let it retry on the next loop step. error!(target: "client", height, "Block production failed: {}", err); } else { @@ -1157,12 +1160,20 @@ impl ClientActorInner { /// /// Returns the delay before the next time `check_triggers` should be called, which is /// min(time until the closest trigger, 1 second). - pub(crate) fn check_triggers(&mut self, ctx: &mut dyn DelayedActionRunner) -> Duration { + fn check_triggers(&mut self, ctx: &mut dyn DelayedActionRunner) -> Duration { let _span = tracing::debug_span!(target: "client", "check_triggers").entered(); if let Some(config_updater) = &mut self.config_updater { - config_updater.try_update(&|updateable_client_config| { - self.client.update_client_config(updateable_client_config) - }); + let update_result = config_updater.try_update( + &|updateable_client_config| { + self.client.update_client_config(updateable_client_config) + }, + &|validator_signer| self.client.update_validator_signer(validator_signer), + ); + if update_result.validator_signer_updated { + // Request PeerManager to advertise tier1 proxies. + // It is needed to advertise that our validator key changed. + self.network_adapter.send(PeerManagerMessageRequest::AdvertiseTier1Proxies); + } } // Check block height to trigger expected shutdown @@ -1177,7 +1188,8 @@ impl ClientActorInner { } } - self.try_process_unfinished_blocks(); + let validator_signer = self.client.validator_signer.get(); + self.try_process_unfinished_blocks(&validator_signer); let mut delay = near_async::time::Duration::seconds(1); let now = self.clock.now_utc(); @@ -1203,7 +1215,7 @@ impl ClientActorInner { ); delay = core::cmp::min(delay, self.doomslug_timer_next_attempt - now) } - if self.block_production_started { + if validator_signer.is_some() { self.block_production_next_attempt = self.run_timer( self.client.config.block_production_tracking_delay, self.block_production_next_attempt, @@ -1241,20 +1253,23 @@ impl ClientActorInner { /// calls this function to finish processing the unfinished blocks. ClientActor also calls /// this function in `check_triggers`, because the actix queue may be blocked by other messages /// and we want to prioritize block processing. - fn try_process_unfinished_blocks(&mut self) { + fn try_process_unfinished_blocks(&mut self, signer: &Option>) { let _span = debug_span!(target: "client", "try_process_unfinished_blocks").entered(); - let (accepted_blocks, errors) = self - .client - .postprocess_ready_blocks(Some(self.myself_sender.apply_chunks_done.clone()), true); + let (accepted_blocks, errors) = self.client.postprocess_ready_blocks( + Some(self.myself_sender.apply_chunks_done.clone()), + true, + signer, + ); if !errors.is_empty() { error!(target: "client", ?errors, "try_process_unfinished_blocks got errors"); } - self.process_accepted_blocks(accepted_blocks); + self.process_accepted_blocks(accepted_blocks, signer); } fn try_handle_block_production(&mut self) { let _span = debug_span!(target: "client", "try_handle_block_production").entered(); - if let Err(err) = self.handle_block_production() { + let signer = self.client.validator_signer.get(); + if let Err(err) = self.handle_block_production(&signer) { tracing::error!(target: "client", ?err, "Handle block production failed") } } @@ -1262,7 +1277,8 @@ impl ClientActorInner { fn try_doomslug_timer(&mut self) { let _span = tracing::debug_span!(target: "client", "try_doomslug_timer").entered(); let _ = self.client.check_and_update_doomslug_tip(); - let approvals = self.client.doomslug.process_timer(); + let signer = self.client.validator_signer.get(); + let approvals = self.client.doomslug.process_timer(&signer); // Important to save the largest approval target height before sending approvals, so // that if the node crashes in the meantime, we cannot get slashed on recovery @@ -1273,13 +1289,15 @@ impl ClientActorInner { match chain_store_update.commit() { Ok(_) => { let head = unwrap_or_return!(self.client.chain.head()); - if self.client.is_validator(&head.epoch_id, &head.last_block_hash) - || self.client.is_validator(&head.next_epoch_id, &head.last_block_hash) + if self.client.is_validator(&head.epoch_id, &head.last_block_hash, &signer) + || self.client.is_validator(&head.next_epoch_id, &head.last_block_hash, &signer) { for approval in approvals { - if let Err(e) = - self.client.send_approval(&self.client.doomslug.get_tip().0, approval) - { + if let Err(e) = self.client.send_approval( + &self.client.doomslug.get_tip().0, + approval, + &signer, + ) { error!("Error while sending an approval {:?}", e); } } @@ -1291,37 +1309,45 @@ impl ClientActorInner { /// Produce block if we are block producer for given `next_height` height. /// Can return error, should be called with `produce_block` to handle errors and reschedule. - fn produce_block(&mut self, next_height: BlockHeight) -> Result<(), Error> { + fn produce_block( + &mut self, + next_height: BlockHeight, + signer: &Option>, + ) -> Result<(), Error> { let _span = tracing::debug_span!(target: "client", "produce_block", next_height).entered(); - if let Some(block) = self.client.produce_block_on_head(next_height, false)? { - // If we produced the block, send it out before we apply the block. - self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( - NetworkRequests::Block { block: block.clone() }, - )); - // We’ve produced the block so that counts as validated block. - let block = MaybeValidated::from_validated(block); - let res = self.client.start_process_block( - block, - Provenance::PRODUCED, - Some(self.myself_sender.apply_chunks_done.clone()), - ); - if let Err(e) = &res { - match e { - near_chain::Error::ChunksMissing(_) => { - debug!(target: "client", "chunks missing"); - // missing chunks were already handled in Client::process_block, we don't need to - // do anything here - return Ok(()); - } - _ => { - error!(target: "client", ?res, "Failed to process freshly produced block"); - byzantine_assert!(false); - return res.map_err(|err| err.into()); - } - } + let Some(block) = self.client.produce_block_on_head(next_height, false)? else { + return Ok(()); + }; + + // If we produced the block, send it out before we apply the block. + self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( + NetworkRequests::Block { block: block.clone() }, + )); + // We’ve produced the block so that counts as validated block. + let block = MaybeValidated::from_validated(block); + let res = self.client.start_process_block( + block, + Provenance::PRODUCED, + Some(self.myself_sender.apply_chunks_done.clone()), + signer, + ); + let Err(error) = res else { + return Ok(()); + }; + + match error { + near_chain::Error::ChunksMissing(_) => { + debug!(target: "client", "chunks missing"); + // missing chunks were already handled in Client::process_block, we don't need to + // do anything here + Ok(()) + } + _ => { + error!(target: "client", ?error, "Failed to process freshly produced block"); + byzantine_assert!(false); + Err(error.into()) } } - Ok(()) } fn send_chunks_metrics(&mut self, block: &Block) { @@ -1379,7 +1405,11 @@ impl ClientActorInner { } /// Process all blocks that were accepted by calling other relevant services. - fn process_accepted_blocks(&mut self, accepted_blocks: Vec) { + fn process_accepted_blocks( + &mut self, + accepted_blocks: Vec, + signer: &Option>, + ) { let _span = tracing::debug_span!( target: "client", "process_accepted_blocks", @@ -1389,11 +1419,16 @@ impl ClientActorInner { let block = self.client.chain.get_block(&accepted_block).unwrap().clone(); self.send_chunks_metrics(&block); self.send_block_metrics(&block); - self.check_send_announce_account(*block.header().last_final_block()); + self.check_send_announce_account(*block.header().last_final_block(), signer); } } - fn receive_headers(&mut self, headers: Vec, peer_id: PeerId) -> bool { + fn receive_headers( + &mut self, + headers: Vec, + peer_id: PeerId, + signer: &Option>, + ) -> bool { let _span = debug_span!(target: "client", "receive_headers", num_headers = headers.len(), ?peer_id) .entered(); @@ -1401,7 +1436,7 @@ impl ClientActorInner { info!(target: "client", "Received an empty set of block headers"); return true; } - match self.client.sync_block_headers(headers) { + match self.client.sync_block_headers(headers, signer) { Ok(_) => true, Err(err) => { if err.is_bad_data() { @@ -1531,6 +1566,7 @@ impl ClientActorInner { { // An extra scope to limit the lifetime of the span. let _span = tracing::debug_span!(target: "client", "catchup").entered(); + let validator_signer = self.client.validator_signer.get(); if let Err(err) = self.client.run_catchup( &self.network_info.highest_height_peers, &self.sync_jobs_sender.apply_state_parts, @@ -1539,8 +1575,9 @@ impl ClientActorInner { &self.sync_jobs_sender.resharding, Some(self.myself_sender.apply_chunks_done.clone()), self.state_parts_future_spawner.as_ref(), + &validator_signer, ) { - error!(target: "client", "{:?} Error occurred during catchup for the next epoch: {:?}", self.client.validator_signer.as_ref().map(|vs| vs.validator_id()), err); + error!(target: "client", "{:?} Error occurred during catchup for the next epoch: {:?}", validator_signer.as_ref().map(|vs| vs.validator_id()), err); } } @@ -1598,6 +1635,7 @@ impl ClientActorInner { /// finishing state part job fn run_sync_step(&mut self) { let _span = tracing::debug_span!(target: "client", "run_sync_step").entered(); + let signer = self.client.validator_signer.get(); let currently_syncing = self.client.sync_status.is_syncing(); let sync = unwrap_and_report_state_sync_result!(self.syncing_info()); @@ -1613,7 +1651,7 @@ impl ClientActorInner { self.client.sync_status.update(SyncStatus::NoSync); // Announce this client's account id if their epoch is coming up. let head = unwrap_and_report_state_sync_result!(self.client.chain.head()); - self.check_send_announce_account(head.prev_block_hash); + self.check_send_announce_account(head.prev_block_hash, &signer); } } @@ -1622,7 +1660,7 @@ impl ClientActorInner { info!(target: "client", ?sync, "enabling sync"); } - self.handle_sync_needed(highest_height); + self.handle_sync_needed(highest_height, &signer); } } } @@ -1630,7 +1668,7 @@ impl ClientActorInner { /// Handle the SyncRequirement::SyncNeeded. /// /// This method runs the header sync, the block sync - fn handle_sync_needed(&mut self, highest_height: u64) { + fn handle_sync_needed(&mut self, highest_height: u64, signer: &Option>) { // Run each step of syncing separately. let header_sync_result = self.client.header_sync.run( &mut self.client.sync_status, @@ -1657,10 +1695,10 @@ impl ClientActorInner { _ => unreachable!("Sync status should have been StateSync!"), }; - let me = self.client.validator_signer.as_ref().map(|x| x.validator_id().clone()); + let me = signer.as_ref().map(|x| x.validator_id().clone()); let block_header = self.client.chain.get_block_header(&sync_hash); let block_header = unwrap_and_report_state_sync_result!(block_header); - let epoch_id = block_header.epoch_id().clone(); + let epoch_id = *block_header.epoch_id(); let shards_to_sync = get_shards_cares_about_this_or_next_epoch( me.as_ref(), true, @@ -1722,7 +1760,7 @@ impl ClientActorInner { ); unwrap_and_report_state_sync_result!(reset_heads_result); - self.client.process_block_processing_artifact(block_processing_artifacts); + self.client.process_block_processing_artifact(block_processing_artifacts, signer); self.client.sync_status.update(SyncStatus::BlockSync { start_height: 0, current_height: 0, @@ -1809,7 +1847,7 @@ impl ClientActorInner { break; }; - if next_header.height() < min_height_included - 1 { + if next_header.height() + 1 < min_height_included { break; } @@ -1942,11 +1980,13 @@ impl ClientActorInner { /// Print current summary. fn log_summary(&mut self) { let _span = tracing::debug_span!(target: "client", "log_summary").entered(); + let signer = self.client.validator_signer.get(); self.info_helper.log_summary( &self.client, &self.node_id, &self.network_info, &self.config_updater, + &signer, ) } @@ -2079,10 +2119,12 @@ impl Handler for ClientActorInner { fn handle(&mut self, msg: ShardsManagerResponse) { match msg { ShardsManagerResponse::ChunkCompleted { partial_chunk, shard_chunk } => { + let signer = self.client.validator_signer.get(); self.client.on_chunk_completed( partial_chunk, shard_chunk, Some(self.myself_sender.apply_chunks_done.clone()), + &signer, ); } ShardsManagerResponse::InvalidChunk(encoded_chunk) => { @@ -2092,9 +2134,7 @@ impl Handler for ClientActorInner { chunk_header, chunk_producer, } => { - self.client - .chunk_inclusion_tracker - .mark_chunk_header_ready_for_inclusion(chunk_header, chunk_producer); + self.client.mark_chunk_header_ready_for_inclusion(chunk_header, chunk_producer); } } } @@ -2119,7 +2159,11 @@ impl Handler for ClientActorInner { impl Handler for ClientActorInner { #[perf] fn handle(&mut self, msg: ChunkStateWitnessMessage) { - if let Err(err) = self.client.process_chunk_state_witness(msg.0, None) { + let ChunkStateWitnessMessage { witness, raw_witness_size } = msg; + let signer = self.client.validator_signer.get(); + if let Err(err) = + self.client.process_chunk_state_witness(witness, raw_witness_size, None, signer) + { tracing::error!(target: "client", ?err, "Error processing chunk state witness"); } } diff --git a/chain/client/src/config_updater.rs b/chain/client/src/config_updater.rs index bc33f76d370..441d1bd4970 100644 --- a/chain/client/src/config_updater.rs +++ b/chain/client/src/config_updater.rs @@ -1,5 +1,6 @@ use near_chain_configs::UpdateableClientConfig; use near_dyn_configs::{UpdateableConfigLoaderError, UpdateableConfigs}; +use near_primitives::validator_signer::ValidatorSigner; use std::sync::Arc; use tokio::sync::broadcast::Receiver; @@ -12,6 +13,14 @@ pub struct ConfigUpdater { updateable_configs_error: Option>, } +/// Return type of `ConfigUpdater::try_update()`. +/// Represents which values have been updated. +#[derive(Default)] +pub struct ConfigUpdaterResult { + pub client_config_updated: bool, + pub validator_signer_updated: bool, +} + impl ConfigUpdater { pub fn new( rx_config_update: Receiver>>, @@ -21,14 +30,25 @@ impl ConfigUpdater { /// Check if any of the configs were updated. /// If they did, the receiver (rx_config_update) will contain a clone of the new configs. - pub fn try_update(&mut self, update_client_config_fn: &dyn Fn(UpdateableClientConfig)) { + pub fn try_update( + &mut self, + update_client_config_fn: &dyn Fn(UpdateableClientConfig) -> bool, + update_validator_signer_fn: &dyn Fn(Arc) -> bool, + ) -> ConfigUpdaterResult { + let mut update_result = ConfigUpdaterResult::default(); while let Ok(maybe_updateable_configs) = self.rx_config_update.try_recv() { match maybe_updateable_configs { Ok(updateable_configs) => { if let Some(client_config) = updateable_configs.client_config { - update_client_config_fn(client_config); + update_result.client_config_updated |= + update_client_config_fn(client_config); tracing::info!(target: "config", "Updated ClientConfig"); } + if let Some(validator_signer) = updateable_configs.validator_signer { + update_result.validator_signer_updated |= + update_validator_signer_fn(validator_signer); + tracing::info!(target: "config", "Updated validator key"); + } self.updateable_configs_error = None; } Err(err) => { @@ -36,6 +56,7 @@ impl ConfigUpdater { } } } + update_result } /// Prints an error if it's present. diff --git a/chain/client/src/debug.rs b/chain/client/src/debug.rs index ec111062008..898da1e2bbf 100644 --- a/chain/client/src/debug.rs +++ b/chain/client/src/debug.rs @@ -5,7 +5,7 @@ use crate::client_actor::ClientActorInner; use near_async::messaging::Handler; use near_async::time::{Clock, Instant}; use near_chain::crypto_hash_timer::CryptoHashTimer; -use near_chain::{near_chain_primitives, Chain, ChainStoreAccess}; +use near_chain::{near_chain_primitives, Block, Chain, ChainStoreAccess}; use near_client_primitives::debug::{ ApprovalAtHeightStatus, BlockProduction, ChunkCollection, DebugBlockStatusData, DebugStatus, DebugStatusResponse, MissedHeightInfo, ProductionAtHeight, ValidatorStatus, @@ -18,7 +18,9 @@ use near_client_primitives::{ use near_epoch_manager::EpochManagerAdapter; use near_o11y::log_assert; use near_performance_metrics_macros::perf; +use near_primitives::congestion_info::CongestionControl; use near_primitives::state_sync::get_num_state_parts; +use near_primitives::stateless_validation::ChunkEndorsement; use near_primitives::types::{AccountId, BlockHeight, NumShards, ShardId, ValidatorInfoIdentifier}; use near_primitives::{ hash::CryptoHash, @@ -29,6 +31,7 @@ use near_primitives::{ use near_store::DBCol; use std::cmp::{max, min}; use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; use time::ext::InstantExt as _; use near_client_primitives::debug::{DebugBlockStatus, DebugChunkStatus}; @@ -55,7 +58,7 @@ pub struct BlockProductionTracker(lru::LruCache); impl BlockProductionTracker { pub(crate) fn new() -> Self { - Self(lru::LruCache::new(PRODUCTION_TIMES_CACHE_SIZE)) + Self(lru::LruCache::new(NonZeroUsize::new(PRODUCTION_TIMES_CACHE_SIZE).unwrap())) } pub(crate) fn get(&mut self, height: BlockHeight) -> BlockProduction { @@ -281,10 +284,15 @@ impl ClientActorInner { } else { self.client .epoch_manager - .get_validator_info(ValidatorInfoIdentifier::EpochId(epoch_id.clone()))? + .get_validator_info(ValidatorInfoIdentifier::EpochId(*epoch_id))? }; return Ok(( EpochInfoView { + epoch_height: self + .client + .epoch_manager + .get_epoch_info(&epoch_id) + .map(|info| info.epoch_height())?, epoch_id: epoch_id.0, height: block.header().height(), first_block: Some((*block.header().hash(), block.header().timestamp())), @@ -311,6 +319,11 @@ impl ClientActorInner { self.get_producers_for_epoch(&head.next_epoch_id, &head.last_block_hash)?; Ok(EpochInfoView { + epoch_height: self + .client + .epoch_manager + .get_epoch_info(&head.next_epoch_id) + .map(|info| info.epoch_height())?, epoch_id: head.next_epoch_id.0, // Expected height of the next epoch. height: epoch_start_height + self.client.config.epoch_length, @@ -329,7 +342,7 @@ impl ClientActorInner { fn get_tracked_shards_view(&self) -> Result { let epoch_id = self.client.chain.header_head()?.epoch_id; let fetch_hash = self.client.chain.header_head()?.last_block_hash; - let me = self.client.validator_signer.as_ref().map(|x| x.validator_id().clone()); + let me = self.client.validator_signer.get().map(|x| x.validator_id().clone()); let shard_ids = self.client.epoch_manager.shard_ids(&epoch_id).unwrap(); let shards_tracked_this_epoch = shard_ids .iter() @@ -443,27 +456,60 @@ impl ClientActorInner { .get_block_producer(block_header.epoch_id(), block_header.height()) .ok(); + let chunk_endorsements = self.compute_chunk_endorsements_ratio(&block); + let congestion_control_config = self + .client + .runtime_adapter + .get_protocol_config(block_header.epoch_id())? + .runtime_config + .congestion_control_config; + let block_congestion_info = + block.as_ref().map(|block| block.block_congestion_info()); + let chunks = match &block { Some(block) => block .chunks() .iter() - .map(|chunk| DebugChunkStatus { - shard_id: chunk.shard_id(), - chunk_hash: chunk.chunk_hash(), - chunk_producer: self - .client - .epoch_manager - .get_chunk_producer( - block_header.epoch_id(), - block_header.height(), - chunk.shard_id(), + .map(|chunk| { + let endorsement_ratio = chunk_endorsements + .as_ref() + .map(|chunks| chunks.get(&chunk.chunk_hash())) + .flatten() + .copied(); + + let congestion_level = + block_congestion_info.as_ref().and_then(|shards_info| { + shards_info.get(&chunk.shard_id()).map(|ext_info| { + CongestionControl::new( + congestion_control_config, + ext_info.congestion_info, + ext_info.missed_chunks_count, + ) + .congestion_level() + }) + }); + + DebugChunkStatus { + shard_id: chunk.shard_id(), + chunk_hash: chunk.chunk_hash(), + chunk_producer: self + .client + .epoch_manager + .get_chunk_producer( + block_header.epoch_id(), + block_header.height(), + chunk.shard_id(), + ) + .ok(), + gas_used: chunk.prev_gas_used(), + processing_time_ms: CryptoHashTimer::get_timer_value( + chunk.chunk_hash().0, ) - .ok(), - gas_used: chunk.prev_gas_used(), - processing_time_ms: CryptoHashTimer::get_timer_value( - chunk.chunk_hash().0, - ) - .map(|s| s.whole_milliseconds() as u64), + .map(|s| s.whole_milliseconds() as u64), + congestion_level, + congestion_info: chunk.congestion_info(), + endorsement_ratio, + } }) .collect(), None => vec![], @@ -488,7 +534,7 @@ impl ClientActorInner { ); // TODO(robin): using last epoch id when iterating in reverse height direction is // not a good idea for calculating producer of missing heights. Revisit this. - last_epoch_id = block_header.epoch_id().clone(); + last_epoch_id = *block_header.epoch_id(); if let Some(prev_height) = block_header.prev_height() { if block_header.height() != prev_height + 1 { // This block was produced using a Skip approval; make sure to fetch the @@ -515,7 +561,7 @@ impl ClientActorInner { let head = self.client.chain.head()?; let mut productions = vec![]; - if let Some(signer) = &self.client.validator_signer { + if let Some(signer) = &self.client.validator_signer.get() { let validator_id = signer.validator_id().to_string(); // We want to show some older blocks (up to DEBUG_PRODUCTION_OLD_BLOCKS_TO_SHOW in the past) @@ -532,7 +578,7 @@ impl ClientActorInner { ); #[allow(clippy::redundant_clone)] - let mut epoch_id = head.epoch_id.clone(); + let mut epoch_id = head.epoch_id; for height in head.height.saturating_sub(DEBUG_PRODUCTION_OLD_BLOCKS_TO_SHOW)..=max_height { @@ -541,7 +587,7 @@ impl ClientActorInner { // The block may be in the last epoch from head, we need to account for that. if let Ok(header) = self.client.chain.get_block_header_by_height(height) { - epoch_id = header.epoch_id().clone(); + epoch_id = *header.epoch_id(); } // And if we are the block (or chunk) producer for this height - collect some timing info. @@ -593,7 +639,7 @@ impl ClientActorInner { validator_name: self .client .validator_signer - .as_ref() + .get() .map(|signer| signer.validator_id().clone()), // TODO: this might not work correctly when we're at the epoch boundary (as it will // just return the validators for the current epoch). We can fix it in the future, if @@ -625,7 +671,79 @@ impl ClientActorInner { .get_banned_chunk_producers(), }) } + + /// Computes the ratio of stake endorsed to all chunks in `block`. + /// The logic is based on `Chain::validate_chunk_endorsements_in_block`. + fn compute_chunk_endorsements_ratio( + &self, + block: &Option, + ) -> Option> { + let Some(block) = block else { + return None; + }; + let mut chunk_endorsements = HashMap::new(); + if block.chunks().len() != block.chunk_endorsements().len() { + return None; + } + // Get the epoch id. + let Ok(epoch_id) = + self.client.epoch_manager.get_epoch_id_from_prev_block(block.header().prev_hash()) + else { + return None; + }; + // Iterate all shards and compute the endorsed stake from the endorsement signatures. + for (chunk_header, signatures) in block.chunks().iter().zip(block.chunk_endorsements()) { + // Validation checks. + if chunk_header.height_included() != block.header().height() { + chunk_endorsements.insert(chunk_header.chunk_hash(), 0.0); + continue; + } + let Ok(chunk_validator_assignments) = + self.client.epoch_manager.get_chunk_validator_assignments( + &epoch_id, + chunk_header.shard_id(), + chunk_header.height_created(), + ) + else { + chunk_endorsements.insert(chunk_header.chunk_hash(), f64::NAN); + continue; + }; + let ordered_chunk_validators = chunk_validator_assignments.ordered_chunk_validators(); + if ordered_chunk_validators.len() != signatures.len() { + chunk_endorsements.insert(chunk_header.chunk_hash(), f64::NAN); + continue; + } + // Compute total stake and endorsed stake. + let mut endorsed_chunk_validators = HashSet::new(); + for (account_id, signature) in ordered_chunk_validators.iter().zip(signatures) { + let Some(signature) = signature else { continue }; + let Ok((validator, _)) = self.client.epoch_manager.get_validator_by_account_id( + &epoch_id, + block.header().prev_hash(), + account_id, + ) else { + continue; + }; + if !ChunkEndorsement::validate_signature( + chunk_header.chunk_hash(), + signature, + validator.public_key(), + ) { + continue; + } + endorsed_chunk_validators.insert(account_id); + } + let endorsement_stats = + chunk_validator_assignments.compute_endorsement_stats(&endorsed_chunk_validators); + chunk_endorsements.insert( + chunk_header.chunk_hash(), + endorsement_stats.endorsed_stake as f64 / endorsement_stats.total_stake as f64, + ); + } + Some(chunk_endorsements) + } } + fn new_peer_info_view(chain: &Chain, connected_peer_info: &ConnectedPeerInfo) -> PeerInfoView { let full_peer_info = &connected_peer_info.full_peer_info; let now = Instant::now(); diff --git a/chain/client/src/info.rs b/chain/client/src/info.rs index 8670d6762b8..d0dbf5fa7de 100644 --- a/chain/client/src/info.rs +++ b/chain/client/src/info.rs @@ -9,7 +9,6 @@ use near_client_primitives::types::StateSyncStatus; use near_epoch_manager::EpochManagerAdapter; use near_network::types::NetworkInfo; use near_primitives::block::Tip; -use near_primitives::hash::CryptoHash; use near_primitives::network::PeerId; use near_primitives::telemetry::{ TelemetryAgentInfo, TelemetryChainInfo, TelemetryInfo, TelemetrySystemInfo, @@ -20,15 +19,16 @@ use near_primitives::types::{ }; use near_primitives::unwrap_or_return; use near_primitives::validator_signer::ValidatorSigner; -use near_primitives::version::Version; +use near_primitives::version::{Version, PROTOCOL_VERSION}; use near_primitives::views::{ CatchupStatusView, ChunkProcessingStatus, CurrentEpochValidatorInfo, EpochValidatorInfo, ValidatorKickoutView, }; use near_telemetry::TelemetryEvent; use std::cmp::min; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::fmt::Write; +use std::num::NonZeroUsize; use std::sync::Arc; use sysinfo::{get_current_pid, set_open_files_limit, Pid, ProcessExt, System, SystemExt}; use time::ext::InstantExt as _; @@ -58,8 +58,6 @@ pub struct InfoHelper { num_chunks_in_blocks_processed: u64, /// Total gas used during period. gas_used: u64, - /// Sign telemetry with block producer key if available. - validator_signer: Option>, /// Telemetry event sender. telemetry_sender: Sender, /// Log coloring enabled. @@ -81,7 +79,6 @@ impl InfoHelper { clock: Clock, telemetry_sender: Sender, client_config: &ClientConfig, - validator_signer: Option>, ) -> Self { set_open_files_limit(0); metrics::export_version(&client_config.version); @@ -95,13 +92,12 @@ impl InfoHelper { num_chunks_in_blocks_processed: 0, gas_used: 0, telemetry_sender, - validator_signer, log_summary_style: client_config.log_summary_style, boot_time_seconds: clock.now_utc().unix_timestamp(), epoch_id: None, enable_multiline_logging: client_config.enable_multiline_logging, prev_sync_requirement: None, - num_validators_per_epoch: LruCache::new(3), + num_validators_per_epoch: LruCache::new(NonZeroUsize::new(3).unwrap()), } } @@ -139,7 +135,7 @@ impl InfoHelper { metrics::FINAL_DOOMSLUG_BLOCK_HEIGHT.set(last_final_ds_block_height as i64); metrics::EPOCH_HEIGHT.set(epoch_height as i64); if let Some(last_final_block_height_in_epoch) = last_final_block_height_in_epoch { - // In rare cases cases the final height isn't updated, for example right after a state sync. + // In rare cases the final height isn't updated, for example right after a state sync. // Don't update the metric in such cases. metrics::FINAL_BLOCK_HEIGHT_IN_EPOCH.set(last_final_block_height_in_epoch as i64); } @@ -147,7 +143,8 @@ impl InfoHelper { /// Count which shards are tracked by the node in the epoch indicated by head parameter. fn record_tracked_shards(head: &Tip, client: &crate::client::Client) { - let me = client.validator_signer.as_ref().map(|x| x.validator_id()); + let validator_signer = client.validator_signer.get(); + let me = validator_signer.as_ref().map(|x| x.validator_id()); if let Ok(shard_ids) = client.epoch_manager.shard_ids(&head.epoch_id) { for shard_id in shard_ids { let tracked = client.shard_tracker.care_about_shard( @@ -164,7 +161,7 @@ impl InfoHelper { } fn record_block_producers(head: &Tip, client: &crate::client::Client) { - let me = client.validator_signer.as_ref().map(|x| x.validator_id().clone()); + let me = client.validator_signer.get().map(|x| x.validator_id().clone()); if let Some(is_bp) = me.map_or(Some(false), |account_id| { // In rare cases block producer information isn't available. // Don't set the metric in this case. @@ -179,7 +176,7 @@ impl InfoHelper { fn record_chunk_producers(head: &Tip, client: &crate::client::Client) { if let (Some(account_id), Ok(epoch_info)) = ( - client.validator_signer.as_ref().map(|x| x.validator_id().clone()), + client.validator_signer.get().map(|x| x.validator_id().clone()), client.epoch_manager.get_epoch_info(&head.epoch_id), ) { for (shard_id, validators) in epoch_info.chunk_producers_settlement().iter().enumerate() @@ -291,25 +288,10 @@ impl InfoHelper { &mut self, epoch_manager: &dyn EpochManagerAdapter, epoch_id: &EpochId, - last_block_hash: &CryptoHash, ) -> usize { - self.num_validators_per_epoch - .get_or_insert(epoch_id.clone(), || { - let block_producers: HashSet = epoch_manager - .get_epoch_block_producers_ordered(epoch_id, last_block_hash) - .unwrap_or(vec![]) - .into_iter() - .map(|(validator_stake, _)| validator_stake.account_id().clone()) - .collect(); - let chunk_producers: HashSet = epoch_manager - .get_epoch_chunk_producers(epoch_id) - .unwrap_or(vec![]) - .into_iter() - .map(|validator_stake| validator_stake.account_id().clone()) - .collect(); - block_producers.union(&chunk_producers).count() - }) - .map_or(0, |num_validators| *num_validators) + *self.num_validators_per_epoch.get_or_insert(*epoch_id, || { + epoch_manager.get_epoch_all_validators(epoch_id).unwrap_or_default().len() + }) } /// Print current summary. @@ -319,16 +301,14 @@ impl InfoHelper { node_id: &PeerId, network_info: &NetworkInfo, config_updater: &Option, + signer: &Option>, ) { let is_syncing = client.sync_status.is_syncing(); let head = unwrap_or_return!(client.chain.head()); let validator_info = if !is_syncing { - let num_validators = self.get_num_validators( - client.epoch_manager.as_ref(), - &head.epoch_id, - &head.last_block_hash, - ); - let account_id = client.validator_signer.as_ref().map(|x| x.validator_id()); + let num_validators = + self.get_num_validators(client.epoch_manager.as_ref(), &head.epoch_id); + let account_id = signer.as_ref().map(|x| x.validator_id()); let is_validator = if let Some(account_id) = account_id { match client.epoch_manager.get_validator_by_account_id( &head.epoch_id, @@ -369,7 +349,7 @@ impl InfoHelper { InfoHelper::record_tracked_shards(&head, &client); InfoHelper::record_block_producers(&head, &client); InfoHelper::record_chunk_producers(&head, &client); - let next_epoch_id = Some(head.epoch_id.clone()); + let next_epoch_id = Some(head.epoch_id); if self.epoch_id.ne(&next_epoch_id) { // We only want to compute this once per epoch to avoid heavy computational work, that can last up to 100ms. InfoHelper::record_epoch_settlement_info(&head, &client); @@ -394,6 +374,7 @@ impl InfoHelper { .unwrap_or(0), &client.config, config_updater, + signer, ); self.log_chain_processing_info(client, &head.epoch_id); } @@ -410,6 +391,7 @@ impl InfoHelper { protocol_upgrade_block_height: BlockHeight, client_config: &ClientConfig, config_updater: &Option, + signer: &Option>, ) { let use_color = matches!(self.log_summary_style, LogSummaryStyle::Colored); let paint = |color: yansi::Color, text: Option| match text { @@ -529,6 +511,7 @@ impl InfoHelper { cpu_usage, memory_usage, is_validator, + signer, ), }; self.telemetry_sender.send(telemetry_event); @@ -544,12 +527,14 @@ impl InfoHelper { cpu_usage: f32, memory_usage: u64, is_validator: bool, + signer: &Option>, ) -> serde_json::Value { let info = TelemetryInfo { agent: TelemetryAgentInfo { name: "near-rs".to_string(), version: self.nearcore_version.version.clone(), build: self.nearcore_version.build.clone(), + protocol_version: PROTOCOL_VERSION, }, system: TelemetrySystemInfo { bandwidth_download: network_info.received_bytes_per_sec, @@ -559,8 +544,9 @@ impl InfoHelper { boot_time_seconds: self.boot_time_seconds, }, chain: TelemetryChainInfo { + chain_id: client_config.chain_id.clone(), node_id: node_id.to_string(), - account_id: self.validator_signer.as_ref().map(|bp| bp.validator_id().clone()), + account_id: signer.as_ref().map(|bp| bp.validator_id().clone()), is_validator, status: sync_status.as_variant_name().to_string(), latest_block_hash: head.last_block_hash, @@ -580,8 +566,8 @@ impl InfoHelper { extra_info: serde_json::to_string(&extra_telemetry_info(client_config)).unwrap(), }; // Sign telemetry if there is a signer present. - if let Some(vs) = self.validator_signer.as_ref() { - vs.sign_telemetry(&info) + if let Some(signer) = signer { + signer.sign_telemetry(&info) } else { serde_json::to_value(&info).expect("Telemetry must serialize to json") } @@ -914,11 +900,12 @@ mod tests { use near_chain::runtime::NightshadeRuntime; use near_chain::types::ChainConfig; use near_chain::{Chain, ChainGenesis, DoomslugThresholdMode}; - use near_chain_configs::Genesis; + use near_chain_configs::{Genesis, MutableConfigValue}; use near_epoch_manager::shard_tracker::ShardTracker; use near_epoch_manager::test_utils::*; use near_epoch_manager::EpochManager; use near_network::test_utils::peer_id_from_seed; + use near_primitives::hash::CryptoHash; use near_store::genesis::initialize_genesis_state; #[test] @@ -944,7 +931,8 @@ mod tests { #[test] fn test_telemetry_info() { let config = ClientConfig::test(false, 1230, 2340, 50, false, true, true, true); - let info_helper = InfoHelper::new(Clock::real(), noop().into_sender(), &config, None); + let validator = MutableConfigValue::new(None, "validator_signer"); + let info_helper = InfoHelper::new(Clock::real(), noop().into_sender(), &config); let store = near_store::test_utils::create_test_store(); let mut genesis = Genesis::test(vec!["test".parse::().unwrap()], 1); @@ -967,7 +955,7 @@ mod tests { ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + validator.clone(), ) .unwrap(); @@ -991,6 +979,7 @@ mod tests { 0.0, 0, false, + &validator.get(), ); println!("Got telemetry info: {:?}", telemetry); assert_matches!( @@ -1027,10 +1016,9 @@ mod tests { epoch_length, num_shards, num_block_producer_seats.try_into().unwrap(), - 0, - 90, 90, 90, + 0, default_reward_calculator(), ) .into_handle(); @@ -1050,11 +1038,10 @@ mod tests { // Then check that get_num_validators returns the correct number of validators. let client_config = ClientConfig::test(false, 1230, 2340, 50, false, true, true, true); - let mut info_helper = - InfoHelper::new(Clock::real(), noop().into_sender(), &client_config, None); + let mut info_helper = InfoHelper::new(Clock::real(), noop().into_sender(), &client_config); assert_eq!( num_validators, - info_helper.get_num_validators(&epoch_manager_adapter, &epoch_id, &last_block_hash) + info_helper.get_num_validators(&epoch_manager_adapter, &epoch_id) ); } } diff --git a/chain/client/src/lib.rs b/chain/client/src/lib.rs index ca712e95fae..8882150fbfb 100644 --- a/chain/client/src/lib.rs +++ b/chain/client/src/lib.rs @@ -16,14 +16,16 @@ pub use crate::config_updater::ConfigUpdater; pub use crate::stateless_validation::chunk_validator::orphan_witness_handling::HandleOrphanWitnessOutcome; pub use crate::sync::adapter::{SyncAdapter, SyncMessage}; pub use crate::view_client_actor::{ViewClientActor, ViewClientActorInner}; +pub use near_chain::stateless_validation::processing_tracker::{ + ProcessingDoneTracker, ProcessingDoneWaiter, +}; pub use near_client_primitives::debug::DebugStatus; pub use near_network::client::{ BlockApproval, BlockResponse, ProcessTxRequest, ProcessTxResponse, SetNetworkInfo, }; pub use stateless_validation::partial_witness::partial_witness_actor::{ - DistributeStateWitnessRequest, PartialWitnessActor, PartialWitnessSenderForClientMessage, + DistributeStateWitnessRequest, PartialWitnessActor, }; -pub use stateless_validation::processing_tracker::{ProcessingDoneTracker, ProcessingDoneWaiter}; pub mod adapter; pub mod adversarial; diff --git a/chain/client/src/metrics.rs b/chain/client/src/metrics.rs index 552693c3ca6..3af3356ad60 100644 --- a/chain/client/src/metrics.rs +++ b/chain/client/src/metrics.rs @@ -4,7 +4,6 @@ use near_o11y::metrics::{ try_create_int_counter_vec, try_create_int_gauge, try_create_int_gauge_vec, Counter, Gauge, Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, }; -use near_primitives::stateless_validation::ChunkStateWitness; use once_cell::sync::Lazy; pub(crate) static BLOCK_PRODUCED_TOTAL: Lazy = Lazy::new(|| { @@ -561,175 +560,6 @@ pub(crate) static SYNC_REQUIREMENT_CURRENT: Lazy = Lazy::new(|| { .unwrap() }); -pub(crate) static SHADOW_CHUNK_VALIDATION_FAILED_TOTAL: Lazy = Lazy::new(|| { - try_create_int_counter( - "near_shadow_chunk_validation_failed_total", - "Shadow chunk validation failures count", - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_VALIDATION_TIME: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_validation_time", - "State witness validation latency in seconds", - &["shard_id"], - Some(exponential_buckets(0.01, 2.0, 12).unwrap()), - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_TOTAL_SIZE: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_total_size", - "Stateless validation compressed state witness size in bytes", - &["shard_id"], - Some(exponential_buckets(100_000.0, 1.2, 32).unwrap()), - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_RAW_SIZE: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_raw_size", - "Stateless validation uncompressed (raw) state witness size in bytes", - &["shard_id"], - Some(exponential_buckets(100_000.0, 1.2, 32).unwrap()), - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_ENCODE_TIME: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_encode_time", - "State witness encoding (serialization + compression) latency in seconds", - &["shard_id"], - Some(linear_buckets(0.025, 0.025, 20).unwrap()), - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_DECODE_TIME: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_decode_time", - "State witness decoding (decompression + deserialization) latency in seconds", - &["shard_id"], - Some(linear_buckets(0.025, 0.025, 20).unwrap()), - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_MAIN_STATE_TRANSISTION_SIZE: Lazy = Lazy::new( - || { - try_create_histogram_vec( - "near_chunk_state_witness_main_state_transition_size", - "Size of ChunkStateWitness::main_state_transition (storage proof needed to execute receipts)", - &["shard_id"], - Some(buckets_for_witness_field_size()), - ) - .unwrap() - }, -); - -pub(crate) static CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_SIZE: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_new_transactions_size", - "Size of ChunkStateWitness::new_transactions (new proposed transactions)", - &["shard_id"], - Some(buckets_for_witness_field_size()), - ) - .unwrap() -}); - -pub(crate) static CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_STATE_SIZE: Lazy = Lazy::new( - || { - try_create_histogram_vec( - "near_chunk_state_witness_new_transactions_state_size", - "Size of ChunkStateWitness::new_transactions_validation_state (storage proof to validate new proposed transactions)", - &["shard_id"], - Some(buckets_for_witness_field_size()), - ) - .unwrap() - }, -); - -pub(crate) static CHUNK_STATE_WITNESS_SOURCE_RECEIPT_PROOFS_SIZE: Lazy = - Lazy::new(|| { - try_create_histogram_vec( - "near_chunk_state_witness_source_receipt_proofs_size", - "Size of ChunkStateWitness::source_receipt_proofs (incoming receipts proofs)", - &["shard_id"], - Some(buckets_for_witness_field_size()), - ) - .unwrap() - }); - -pub(crate) fn record_witness_size_metrics( - decoded_size: usize, - encoded_size: usize, - witness: &ChunkStateWitness, -) { - if let Err(err) = record_witness_size_metrics_fallible(decoded_size, encoded_size, witness) { - tracing::warn!(target:"client", "Failed to record witness size metrics!, error: {}", err); - } -} - -fn record_witness_size_metrics_fallible( - decoded_size: usize, - encoded_size: usize, - witness: &ChunkStateWitness, -) -> Result<(), std::io::Error> { - let shard_id = witness.chunk_header.shard_id().to_string(); - CHUNK_STATE_WITNESS_RAW_SIZE - .with_label_values(&[shard_id.as_str()]) - .observe(decoded_size as f64); - CHUNK_STATE_WITNESS_TOTAL_SIZE - .with_label_values(&[&shard_id.as_str()]) - .observe(encoded_size as f64); - CHUNK_STATE_WITNESS_MAIN_STATE_TRANSISTION_SIZE - .with_label_values(&[shard_id.as_str()]) - .observe(borsh::to_vec(&witness.main_state_transition)?.len() as f64); - CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_SIZE - .with_label_values(&[&shard_id.as_str()]) - .observe(borsh::to_vec(&witness.new_transactions)?.len() as f64); - CHUNK_STATE_WITNESS_NEW_TRANSACTIONS_STATE_SIZE - .with_label_values(&[&shard_id.as_str()]) - .observe(borsh::to_vec(&witness.new_transactions_validation_state)?.len() as f64); - CHUNK_STATE_WITNESS_SOURCE_RECEIPT_PROOFS_SIZE - .with_label_values(&[&shard_id.as_str()]) - .observe(borsh::to_vec(&witness.source_receipt_proofs)?.len() as f64); - Ok(()) -} - -/// Buckets from 0 to 10MB -/// Meant for measuring size of a single field inside ChunkSizeWitness. -fn buckets_for_witness_field_size() -> Vec { - vec![ - 10_000., - 20_000., - 50_000., - 100_000., - 200_000., - 300_000., - 500_000., - 750_000., - 1000_000., - 1500_000., - 2000_000., - 2500_000., - 3000_000., - 3500_000., - 4000_000., - 4500_000., - 5000_000., - 6000_000., - 7000_000., - 8000_000., - 9000_000., - 10_000_000., - ] -} - pub(crate) static ORPHAN_CHUNK_STATE_WITNESSES_TOTAL_COUNT: Lazy = Lazy::new(|| { try_create_int_counter_vec( "near_orphan_chunk_state_witness_total_count", @@ -804,9 +634,9 @@ pub(crate) static PARTIAL_WITNESS_ENCODE_TIME: Lazy = Lazy::new(|| .unwrap() }); -pub(crate) static PARTIAL_WITNESS_DECODE_TIME: Lazy = Lazy::new(|| { +pub(crate) static PARTIAL_WITNESS_TIME_TO_LAST_PART: Lazy = Lazy::new(|| { try_create_histogram_vec( - "near_partial_witness_decode_time", + "near_partial_witness_time_to_last_part", "Time taken from receiving first partial witness part to receiving enough parts to decode the state witness", &["shard_id"], Some(exponential_buckets(0.001, 2.0, 13).unwrap()), @@ -814,26 +644,6 @@ pub(crate) static PARTIAL_WITNESS_DECODE_TIME: Lazy = Lazy::new(|| .unwrap() }); -pub(crate) static PARTIAL_WITNESS_TOTAL_TIME: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_partial_witness_total_time", - "Time taken from receiving first partial witness part to receiving last partial witness part", - &["shard_id"], - Some(exponential_buckets(0.001, 2.0, 13).unwrap()), - ) - .unwrap() -}); - -pub(crate) static PARTIAL_WITNESS_PARTS_RECEIVED_RATIO: Lazy = Lazy::new(|| { - try_create_histogram_vec( - "near_partial_witness_parts_received_ratio", - "Ratio (the value is between 0.0 and 1.0) of the number of parts received for the partial state witness", - &["shard_id"], - Some(linear_buckets(0.0, 0.05, 20).unwrap()), - ) - .unwrap() -}); - pub(crate) static PARTIAL_WITNESS_CACHE_SIZE: Lazy = Lazy::new(|| { try_create_gauge( "near_partial_witness_cache_size", diff --git a/chain/client/src/stateless_validation/chunk_endorsement_tracker.rs b/chain/client/src/stateless_validation/chunk_endorsement_tracker.rs index 4117811b099..b74c4c8f5d2 100644 --- a/chain/client/src/stateless_validation/chunk_endorsement_tracker.rs +++ b/chain/client/src/stateless_validation/chunk_endorsement_tracker.rs @@ -1,7 +1,8 @@ -use near_cache::SyncLruCache; +use lru::LruCache; use near_chain::ChainStoreAccess; use std::collections::HashMap; -use std::sync::Arc; +use std::num::NonZeroUsize; +use std::sync::{Arc, Mutex}; use near_chain_primitives::Error; use near_epoch_manager::EpochManagerAdapter; @@ -33,15 +34,21 @@ impl ChunkEndorsementsState { /// Module to track chunk endorsements received from chunk validators. pub struct ChunkEndorsementTracker { + epoch_manager: Arc, + inner: Mutex, +} + +struct ChunkEndorsementTrackerInner { epoch_manager: Arc, /// We store the validated chunk endorsements received from chunk validators /// This is keyed on chunk_hash and account_id of validator to avoid duplicates. /// Chunk endorsements would later be used as a part of block production. - chunk_endorsements: SyncLruCache>, + chunk_endorsements: + LruCache)>, /// We store chunk endorsements to be processed later because we did not have /// chunks ready at the time we received that endorsements from validators. /// This is keyed on chunk_hash and account_id of validator to avoid duplicates. - pending_chunk_endorsements: SyncLruCache>, + pending_chunk_endorsements: LruCache>, } impl Client { @@ -69,30 +76,24 @@ impl Client { impl ChunkEndorsementTracker { pub fn new(epoch_manager: Arc) -> Self { Self { - epoch_manager, - chunk_endorsements: SyncLruCache::new(NUM_CHUNKS_IN_CHUNK_ENDORSEMENTS_CACHE), - // We can use a different cache size if needed, it does not have to be the same as for `chunk_endorsements`. - pending_chunk_endorsements: SyncLruCache::new(NUM_CHUNKS_IN_CHUNK_ENDORSEMENTS_CACHE), + epoch_manager: epoch_manager.clone(), + inner: Mutex::new(ChunkEndorsementTrackerInner { + epoch_manager, + chunk_endorsements: LruCache::new( + NonZeroUsize::new(NUM_CHUNKS_IN_CHUNK_ENDORSEMENTS_CACHE).unwrap(), + ), + // We can use a different cache size if needed, it does not have to be the same as for `chunk_endorsements`. + pending_chunk_endorsements: LruCache::new( + NonZeroUsize::new(NUM_CHUNKS_IN_CHUNK_ENDORSEMENTS_CACHE).unwrap(), + ), + }), } } /// Process pending endorsements for the given chunk header. /// It removes these endorsements from the `pending_chunk_endorsements` cache. pub fn process_pending_endorsements(&self, chunk_header: &ShardChunkHeader) { - let chunk_hash = &chunk_header.chunk_hash(); - let chunk_endorsements = { - let mut guard = self.pending_chunk_endorsements.lock(); - guard.pop(chunk_hash) - }; - let Some(chunk_endorsements) = chunk_endorsements else { - return; - }; - tracing::debug!(target: "client", ?chunk_hash, "Processing pending chunk endorsements."); - for endorsement in chunk_endorsements.values() { - if let Err(error) = self.process_chunk_endorsement(chunk_header, endorsement.clone()) { - tracing::debug!(target: "client", ?endorsement, ?error, "Error processing pending chunk endorsement"); - } - } + self.inner.lock().unwrap().process_pending_endorsements(chunk_header); } /// Add the chunk endorsement to a cache of pending chunk endorsements (if not yet there). @@ -100,7 +101,7 @@ impl ChunkEndorsementTracker { &self, endorsement: ChunkEndorsement, ) -> Result<(), Error> { - self.process_chunk_endorsement_impl(endorsement, None) + self.inner.lock().unwrap().process_chunk_endorsement_impl(endorsement, None, false) } /// Function to process an incoming chunk endorsement from chunk validators. @@ -112,41 +113,74 @@ impl ChunkEndorsementTracker { endorsement: ChunkEndorsement, ) -> Result<(), Error> { let _span = tracing::debug_span!(target: "client", "process_chunk_endorsement", chunk_hash=?chunk_header.chunk_hash()).entered(); - self.process_chunk_endorsement_impl(endorsement, Some(chunk_header)) + + // Validate the endorsement before locking the mutex to improve performance. + if !self.epoch_manager.verify_chunk_endorsement(&chunk_header, &endorsement)? { + tracing::error!(target: "client", ?endorsement, "Invalid chunk endorsement."); + return Err(Error::InvalidChunkEndorsement); + } + self.inner.lock().unwrap().process_chunk_endorsement_impl( + endorsement, + Some(chunk_header), + true, + ) + } + + /// Called by block producer. + /// Returns ChunkEndorsementsState::Endorsed if node has enough signed stake for the chunk + /// represented by chunk_header. + /// Signatures have the same order as ordered_chunk_validators, thus ready to be included in a block as is. + /// Returns ChunkEndorsementsState::NotEnoughStake if chunk doesn't have enough stake. + /// For older protocol version, we return ChunkEndorsementsState::Endorsed with an empty array of + /// chunk endorsements. + pub fn compute_chunk_endorsements( + &self, + chunk_header: &ShardChunkHeader, + ) -> Result { + self.inner.lock().unwrap().compute_chunk_endorsements_impl(chunk_header) + } +} + +impl ChunkEndorsementTrackerInner { + /// Process pending endorsements for the given chunk header. + /// It removes these endorsements from the `pending_chunk_endorsements` cache. + pub fn process_pending_endorsements(&mut self, chunk_header: &ShardChunkHeader) { + let chunk_hash = &chunk_header.chunk_hash(); + let chunk_endorsements = self.pending_chunk_endorsements.pop(chunk_hash); + let Some(chunk_endorsements) = chunk_endorsements else { + return; + }; + tracing::debug!(target: "client", ?chunk_hash, "Processing pending chunk endorsements."); + for endorsement in chunk_endorsements.values() { + if let Err(error) = + self.process_chunk_endorsement_impl(endorsement.clone(), Some(chunk_header), false) + { + tracing::debug!(target: "client", ?endorsement, ?error, "Error processing pending chunk endorsement"); + } + } } /// If the chunk header is available, we will verify the chunk endorsement and then store it in a cache. /// Otherwise, we store the endorsement in a separate cache of endorsements to be processed when the chunk is ready. fn process_chunk_endorsement_impl( - &self, + &mut self, endorsement: ChunkEndorsement, chunk_header: Option<&ShardChunkHeader>, + already_validated: bool, ) -> Result<(), Error> { let chunk_hash = endorsement.chunk_hash(); let account_id = &endorsement.account_id; - let endorsement_cache = if chunk_header.is_some() { - &self.chunk_endorsements - } else { - &self.pending_chunk_endorsements - }; + let existing_entry = self.chunk_endorsements.peek(chunk_hash); // If we have already processed this chunk endorsement, return early. - if endorsement_cache - .get(chunk_hash) - .is_some_and(|existing_endorsements| existing_endorsements.contains_key(account_id)) - { + if existing_entry.is_some_and(|(_, existing_endorsements)| { + existing_endorsements.contains_key(account_id) + }) { tracing::debug!(target: "client", ?endorsement, "Already received chunk endorsement."); return Ok(()); } - if let Some(chunk_header) = chunk_header { - if !self.epoch_manager.verify_chunk_endorsement(&chunk_header, &endorsement)? { - tracing::error!(target: "client", ?endorsement, "Invalid chunk endorsement."); - return Err(Error::InvalidChunkEndorsement); - } - } - // If we are the current block producer, we store the chunk endorsement for each chunk which // would later be used during block production to check whether to include the chunk or not. // TODO(stateless_validation): It's possible for a malicious validator to send endorsements @@ -154,23 +188,42 @@ impl ChunkEndorsementTracker { // Maybe add check to ensure we don't accept endorsements from chunks already included in some block? // Maybe add check to ensure we don't accept endorsements from chunks that have too old height_created? tracing::debug!(target: "client", ?endorsement, "Received and saved chunk endorsement."); - let mut guard = endorsement_cache.lock(); - guard.get_or_insert(chunk_hash.clone(), || HashMap::new()); - let chunk_endorsements = guard.get_mut(chunk_hash).unwrap(); - chunk_endorsements.insert(account_id.clone(), endorsement); + + // The header might be available in the endorsement cache, even if it isn't provided. + // In such case it should be treated as a non-pending endorsement. + let header = chunk_header.or_else(|| existing_entry.map(|(header, _)| header)); + + if let Some(chunk_header) = header { + if !already_validated + && !self.epoch_manager.verify_chunk_endorsement(&chunk_header, &endorsement)? + { + tracing::error!(target: "client", ?endorsement, "Invalid chunk endorsement."); + return Err(Error::InvalidChunkEndorsement); + } + + if self.chunk_endorsements.peek(chunk_hash).is_none() { + self.chunk_endorsements + .put(chunk_hash.clone(), (chunk_header.clone(), HashMap::new())); + } + self.chunk_endorsements + .get_mut(chunk_hash) + .unwrap() + .1 + .insert(account_id.clone(), endorsement); + } else { + // Chunk header is not available, store the endorsement in the pending cache. + self.pending_chunk_endorsements.get_or_insert(chunk_hash.clone(), || HashMap::new()); + self.pending_chunk_endorsements + .get_mut(chunk_hash) + .unwrap() + .insert(account_id.clone(), endorsement); + } Ok(()) } - /// Called by block producer. - /// Returns ChunkEndorsementsState::Endorsed if node has enough signed stake for the chunk - /// represented by chunk_header. - /// Signatures have the same order as ordered_chunk_validators, thus ready to be included in a block as is. - /// Returns ChunkEndorsementsState::NotEnoughStake if chunk doesn't have enough stake. - /// For older protocol version, we return ChunkEndorsementsState::Endorsed with an empty array of - /// chunk endorsements. - pub fn compute_chunk_endorsements( - &self, + pub fn compute_chunk_endorsements_impl( + &mut self, chunk_header: &ShardChunkHeader, ) -> Result { let epoch_id = @@ -191,7 +244,8 @@ impl ChunkEndorsementTracker { // We can safely rely on the following details // 1. The chunk endorsements are from valid chunk_validator for this chunk. // 2. The chunk endorsements signatures are valid. - let Some(chunk_endorsements) = self.chunk_endorsements.get(&chunk_header.chunk_hash()) + let Some((_header, chunk_endorsements)) = + self.chunk_endorsements.get(&chunk_header.chunk_hash()) else { // Early return if no chunk_endorsements found in our cache. return Ok(ChunkEndorsementsState::NotEnoughStake(None)); diff --git a/chain/client/src/stateless_validation/chunk_validator/mod.rs b/chain/client/src/stateless_validation/chunk_validator/mod.rs index c39d72e381e..7abef6fb317 100644 --- a/chain/client/src/stateless_validation/chunk_validator/mod.rs +++ b/chain/client/src/stateless_validation/chunk_validator/mod.rs @@ -1,48 +1,27 @@ pub mod orphan_witness_handling; pub mod orphan_witness_pool; -use super::processing_tracker::ProcessingDoneTracker; use crate::stateless_validation::chunk_endorsement_tracker::ChunkEndorsementTracker; -use crate::{metrics, Client}; +use crate::Client; use itertools::Itertools; -use lru::LruCache; use near_async::futures::{AsyncComputationSpawner, AsyncComputationSpawnerExt}; use near_async::messaging::{CanSend, Sender}; -use near_chain::chain::{ - apply_new_chunk, apply_old_chunk, NewChunkData, NewChunkResult, OldChunkData, OldChunkResult, - ShardContext, StorageContext, -}; -use near_chain::sharding::shuffle_receipt_proofs; -use near_chain::types::{ - ApplyChunkBlockContext, ApplyChunkResult, PrepareTransactionsChunkContext, - PreparedTransactions, RuntimeAdapter, RuntimeStorageConfig, StorageDataSource, -}; -use near_chain::validate::{ - validate_chunk_with_chunk_extra, validate_chunk_with_chunk_extra_and_receipts_root, -}; -use near_chain::{Block, Chain, ChainStoreAccess}; +use near_chain::stateless_validation::chunk_validation; +use near_chain::stateless_validation::processing_tracker::ProcessingDoneTracker; +use near_chain::types::RuntimeAdapter; +use near_chain::validate::validate_chunk_with_chunk_extra; +use near_chain::{Block, Chain}; use near_chain_primitives::Error; use near_epoch_manager::EpochManagerAdapter; use near_network::types::{NetworkRequests, PeerManagerMessageRequest}; -use near_pool::TransactionGroupIteratorWrapper; -use near_primitives::apply::ApplyChunkReason; -use near_primitives::hash::{hash, CryptoHash}; -use near_primitives::merkle::merklize; -use near_primitives::receipt::Receipt; -use near_primitives::sharding::{ChunkHash, ReceiptProof, ShardChunkHeader}; +use near_o11y::log_assert; +use near_primitives::sharding::ShardChunkHeader; use near_primitives::stateless_validation::{ ChunkEndorsement, ChunkStateWitness, ChunkStateWitnessAck, ChunkStateWitnessSize, - EncodedChunkStateWitness, }; -use near_primitives::transaction::SignedTransaction; -use near_primitives::types::chunk_extra::ChunkExtra; -use near_primitives::types::ShardId; use near_primitives::validator_signer::ValidatorSigner; -use near_store::{PartialStorage, ShardUId}; -use near_vm_runner::logic::ProtocolVersion; use orphan_witness_pool::OrphanStateWitnessPool; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; // After validating a chunk state witness, we ideally need to send the chunk endorsement // to just the next block producer at height h. However, it's possible that blocks at height @@ -51,34 +30,19 @@ use std::sync::{Arc, Mutex}; // Keeping a threshold of 5 block producers should be sufficient for most scenarios. const NUM_NEXT_BLOCK_PRODUCERS_TO_SEND_CHUNK_ENDORSEMENT: u64 = 5; -/// The number of state witness validation results to cache per shard. -/// This number needs to be small because result contains outgoing receipts, which can be large. -const NUM_WITNESS_RESULT_CACHE_ENTRIES: usize = 20; - -#[derive(Clone)] -pub struct ChunkStateWitnessValidationResult { - pub chunk_extra: ChunkExtra, - pub outgoing_receipts: Vec, -} - -pub type MainStateTransitionCache = - Arc>>>; - /// A module that handles chunk validation logic. Chunk validation refers to a /// critical process of stateless validation, where chunk validators (certain /// validators selected to validate the chunk) verify that the chunk's state /// witness is correct, and then send chunk endorsements to the block producer /// so that the chunk can be included in the block. pub struct ChunkValidator { - /// The signer for our own node, if we are a validator. If not, this is None. - my_signer: Option>, epoch_manager: Arc, network_sender: Sender, runtime_adapter: Arc, chunk_endorsement_tracker: Arc, orphan_witness_pool: OrphanStateWitnessPool, validation_spawner: Arc, - main_state_transition_result_cache: MainStateTransitionCache, + main_state_transition_result_cache: chunk_validation::MainStateTransitionCache, /// If true, a chunk-witness validation error will lead to a panic. /// This is used for non-production environments, eg. mocknet and localnet, /// to quickly detect issues in validation code, and must NOT be set to true @@ -88,7 +52,6 @@ pub struct ChunkValidator { impl ChunkValidator { pub fn new( - my_signer: Option>, epoch_manager: Arc, network_sender: Sender, runtime_adapter: Arc, @@ -98,14 +61,14 @@ impl ChunkValidator { panic_on_validation_error: bool, ) -> Self { Self { - my_signer, epoch_manager, network_sender, runtime_adapter, chunk_endorsement_tracker, orphan_witness_pool: OrphanStateWitnessPool::new(orphan_witness_pool_size), validation_spawner, - main_state_transition_result_cache: MainStateTransitionCache::default(), + main_state_transition_result_cache: chunk_validation::MainStateTransitionCache::default( + ), panic_on_validation_error, } } @@ -115,11 +78,12 @@ impl ChunkValidator { /// happens in a separate thread. /// The chunk is validated asynchronously, if you want to wait for the processing to finish /// you can use the `processing_done_tracker` argument (but it's optional, it's safe to pass None there). - pub fn start_validating_chunk( + fn start_validating_chunk( &self, state_witness: ChunkStateWitness, chain: &Chain, processing_done_tracker: Option, + signer: &Arc, ) -> Result<(), Error> { let prev_block_hash = state_witness.chunk_header.prev_block_hash(); let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(prev_block_hash)?; @@ -130,7 +94,7 @@ impl ChunkValidator { ))); } - let pre_validation_result = pre_validate_chunk_state_witness( + let pre_validation_result = chunk_validation::pre_validate_chunk_state_witness( &state_witness, chain, self.epoch_manager.as_ref(), @@ -139,7 +103,6 @@ impl ChunkValidator { let chunk_header = state_witness.chunk_header.clone(); let network_sender = self.network_sender.clone(); - let signer = self.my_signer.as_ref().ok_or(Error::NotAValidator)?.clone(); let chunk_endorsement_tracker = self.chunk_endorsement_tracker.clone(); let epoch_manager = self.epoch_manager.clone(); // If we have the chunk extra for the previous block, we can validate the chunk without state witness. @@ -168,7 +131,7 @@ impl ChunkValidator { send_chunk_endorsement_to_block_producers( &chunk_header, epoch_manager.as_ref(), - signer.as_ref(), + signer, &network_sender, chunk_endorsement_tracker.as_ref(), ); @@ -190,12 +153,13 @@ impl ChunkValidator { let runtime_adapter = self.runtime_adapter.clone(); let cache = self.main_state_transition_result_cache.clone(); + let signer = signer.clone(); self.validation_spawner.spawn("stateless_validation", move || { // processing_done_tracker must survive until the processing is finished. let _processing_done_tracker_capture: Option = processing_done_tracker; - match validate_chunk_state_witness( + match chunk_validation::validate_chunk_state_witness( state_witness, pre_validation_result, epoch_manager.as_ref(), @@ -232,483 +196,10 @@ impl ChunkValidator { } } -/// Checks that proposed `transactions` are valid for a chunk with `chunk_header`. -/// Uses `storage_config` to possibly record reads or use recorded storage. -pub(crate) fn validate_prepared_transactions( - chain: &Chain, - runtime_adapter: &dyn RuntimeAdapter, - chunk_header: &ShardChunkHeader, - storage_config: RuntimeStorageConfig, - transactions: &[SignedTransaction], - last_chunk_transactions: &[SignedTransaction], -) -> Result { - let parent_block = chain.chain_store().get_block(chunk_header.prev_block_hash())?; - let last_chunk_transactions_size = borsh::to_vec(last_chunk_transactions)?.len(); - runtime_adapter.prepare_transactions( - storage_config, - PrepareTransactionsChunkContext { - shard_id: chunk_header.shard_id(), - gas_limit: chunk_header.gas_limit(), - last_chunk_transactions_size, - }, - (&parent_block).into(), - &mut TransactionGroupIteratorWrapper::new(transactions), - &mut chain.transaction_validity_check(parent_block.header().clone()), - None, - ) -} - -/// Pre-validates the chunk's receipts and transactions against the chain. -/// We do this before handing off the computationally intensive part to a -/// validation thread. -pub(crate) fn pre_validate_chunk_state_witness( - state_witness: &ChunkStateWitness, - chain: &Chain, - epoch_manager: &dyn EpochManagerAdapter, - runtime_adapter: &dyn RuntimeAdapter, -) -> Result { - let store = chain.chain_store(); - let shard_id = state_witness.chunk_header.shard_id(); - - // First, go back through the blockchain history to locate the last new chunk - // and last last new chunk for the shard. - - // Blocks from the last new chunk (exclusive) to the parent block (inclusive). - let mut blocks_after_last_chunk = Vec::new(); - // Blocks from the last last new chunk (exclusive) to the last new chunk (inclusive). - let mut blocks_after_last_last_chunk = Vec::new(); - - { - let mut block_hash = *state_witness.chunk_header.prev_block_hash(); - let mut prev_chunks_seen = 0; - loop { - let block = store.get_block(&block_hash)?; - let chunks = block.chunks(); - let Some(chunk) = chunks.get(shard_id as usize) else { - return Err(Error::InvalidChunkStateWitness(format!( - "Shard {} does not exist in block {:?}", - shard_id, block_hash - ))); - }; - let is_new_chunk = chunk.is_new_chunk(block.header().height()); - let is_genesis = block.header().is_genesis(); - block_hash = *block.header().prev_hash(); - if is_new_chunk { - prev_chunks_seen += 1; - } - if prev_chunks_seen == 0 { - blocks_after_last_chunk.push(block); - } else if prev_chunks_seen == 1 { - blocks_after_last_last_chunk.push(block); - } - if prev_chunks_seen == 2 || is_genesis { - break; - } - } - } - - let receipts_to_apply = validate_source_receipt_proofs( - &state_witness.source_receipt_proofs, - &blocks_after_last_last_chunk, - shard_id, - )?; - let applied_receipts_hash = hash(&borsh::to_vec(receipts_to_apply.as_slice()).unwrap()); - if applied_receipts_hash != state_witness.applied_receipts_hash { - return Err(Error::InvalidChunkStateWitness(format!( - "Receipts hash {:?} does not match expected receipts hash {:?}", - applied_receipts_hash, state_witness.applied_receipts_hash - ))); - } - let (tx_root_from_state_witness, _) = merklize(&state_witness.transactions); - let last_chunk_block = blocks_after_last_last_chunk.first().ok_or_else(|| { - Error::Other("blocks_after_last_last_chunk is empty, this should be impossible!".into()) - })?; - let last_new_chunk_tx_root = - last_chunk_block.chunks().get(shard_id as usize).unwrap().tx_root(); - if last_new_chunk_tx_root != tx_root_from_state_witness { - return Err(Error::InvalidChunkStateWitness(format!( - "Transaction root {:?} does not match expected transaction root {:?}", - tx_root_from_state_witness, last_new_chunk_tx_root - ))); - } - - // Verify that all proposed transactions are valid. - let new_transactions = &state_witness.new_transactions; - if !new_transactions.is_empty() { - let transactions_validation_storage_config = RuntimeStorageConfig { - state_root: state_witness.chunk_header.prev_state_root(), - use_flat_storage: true, - source: StorageDataSource::Recorded(PartialStorage { - nodes: state_witness.new_transactions_validation_state.clone(), - }), - state_patch: Default::default(), - }; - - match validate_prepared_transactions( - chain, - runtime_adapter, - &state_witness.chunk_header, - transactions_validation_storage_config, - &new_transactions, - &state_witness.transactions, - ) { - Ok(result) => { - if result.transactions.len() != new_transactions.len() { - return Err(Error::InvalidChunkStateWitness(format!( - "New transactions validation failed. {} transactions out of {} proposed transactions were valid.", - result.transactions.len(), - new_transactions.len(), - ))); - } - } - Err(error) => { - return Err(Error::InvalidChunkStateWitness(format!( - "New transactions validation failed: {}", - error, - ))); - } - }; - } - - let main_transition_params = if last_chunk_block.header().is_genesis() { - let epoch_id = last_chunk_block.header().epoch_id(); - let congestion_info = last_chunk_block - .shards_congestion_info() - .get(&shard_id) - .map(|info| info.congestion_info); - let genesis_protocol_version = epoch_manager.get_epoch_protocol_version(&epoch_id)?; - let chunk_extra = - chain.genesis_chunk_extra(shard_id, genesis_protocol_version, congestion_info)?; - MainTransition::Genesis { chunk_extra, block_hash: *last_chunk_block.hash(), shard_id } - } else { - MainTransition::NewChunk(NewChunkData { - chunk_header: last_chunk_block.chunks().get(shard_id as usize).unwrap().clone(), - transactions: state_witness.transactions.clone(), - receipts: receipts_to_apply, - resharding_state_roots: None, - block: Chain::get_apply_chunk_block_context( - epoch_manager, - last_chunk_block, - &store.get_block_header(last_chunk_block.header().prev_hash())?, - true, - )?, - is_first_block_with_chunk_of_version: false, - storage_context: StorageContext { - storage_data_source: StorageDataSource::Recorded(PartialStorage { - nodes: state_witness.main_state_transition.base_state.clone(), - }), - state_patch: Default::default(), - }, - }) - }; - - Ok(PreValidationOutput { - main_transition_params, - implicit_transition_params: blocks_after_last_chunk - .into_iter() - .rev() - .map(|block| -> Result<_, Error> { - Ok(Chain::get_apply_chunk_block_context( - epoch_manager, - &block, - &store.get_block_header(block.header().prev_hash())?, - false, - )?) - }) - .collect::>()?, - }) -} - -/// Validate that receipt proofs contain the receipts that should be applied during the -/// transition proven by ChunkStateWitness. The receipts are extracted from the proofs -/// and arranged in the order in which they should be applied during the transition. -/// TODO(resharding): Handle resharding properly. If the receipts were sent from before -/// a resharding boundary, we should first validate the proof using the pre-resharding -/// target_shard_id and then extract the receipts that are targeted at this half of a split shard. -fn validate_source_receipt_proofs( - source_receipt_proofs: &HashMap, - receipt_source_blocks: &[Block], - target_chunk_shard_id: ShardId, -) -> Result, Error> { - if receipt_source_blocks.iter().any(|block| block.header().is_genesis()) { - if receipt_source_blocks.len() != 1 { - return Err(Error::Other( - "Invalid chain state: receipt_source_blocks should not have any blocks alongside genesis".to_owned() - )); - } - if !source_receipt_proofs.is_empty() { - return Err(Error::InvalidChunkStateWitness(format!( - "genesis source_receipt_proofs should be empty, actual len is {}", - source_receipt_proofs.len() - ))); - } - return Ok(vec![]); - } - - let mut receipts_to_apply = Vec::new(); - let mut expected_proofs_len = 0; - - // Iterate over blocks between last_chunk_block (inclusive) and last_last_chunk_block (exclusive), - // from the newest blocks to the oldest. - for block in receipt_source_blocks { - // Collect all receipts coming from this block. - let mut block_receipt_proofs = Vec::new(); - - for chunk in block.chunks().iter() { - if !chunk.is_new_chunk(block.header().height()) { - continue; - } - - // Collect receipts coming from this chunk and validate that they are correct. - let Some(receipt_proof) = source_receipt_proofs.get(&chunk.chunk_hash()) else { - return Err(Error::InvalidChunkStateWitness(format!( - "Missing source receipt proof for chunk {:?}", - chunk.chunk_hash() - ))); - }; - validate_receipt_proof(receipt_proof, chunk, target_chunk_shard_id)?; - - expected_proofs_len += 1; - block_receipt_proofs.push(receipt_proof); - } - - // Arrange the receipts in the order in which they should be applied. - shuffle_receipt_proofs(&mut block_receipt_proofs, block.hash()); - for proof in block_receipt_proofs { - receipts_to_apply.extend(proof.0.iter().cloned()); - } - } - - // Check that there are no extraneous proofs in source_receipt_proofs. - if source_receipt_proofs.len() != expected_proofs_len { - return Err(Error::InvalidChunkStateWitness(format!( - "source_receipt_proofs contains too many proofs. Expected {} proofs, found {}", - expected_proofs_len, - source_receipt_proofs.len() - ))); - } - Ok(receipts_to_apply) -} - -fn validate_receipt_proof( - receipt_proof: &ReceiptProof, - from_chunk: &ShardChunkHeader, - target_chunk_shard_id: ShardId, -) -> Result<(), Error> { - // Validate that from_shard_id is correct. The receipts must match the outgoing receipt root - // for this shard, so it's impossible to fake it. - if receipt_proof.1.from_shard_id != from_chunk.shard_id() { - return Err(Error::InvalidChunkStateWitness(format!( - "Receipt proof for chunk {:?} is from shard {}, expected shard {}", - from_chunk.chunk_hash(), - receipt_proof.1.from_shard_id, - from_chunk.shard_id(), - ))); - } - // Validate that to_shard_id is correct. to_shard_id is also encoded in the merkle tree, - // so it's impossible to fake it. - if receipt_proof.1.to_shard_id != target_chunk_shard_id { - return Err(Error::InvalidChunkStateWitness(format!( - "Receipt proof for chunk {:?} is for shard {}, expected shard {}", - from_chunk.chunk_hash(), - receipt_proof.1.to_shard_id, - target_chunk_shard_id - ))); - } - // Verify that (receipts, to_shard_id) belongs to the merkle tree of outgoing receipts in from_chunk. - if !receipt_proof.verify_against_receipt_root(from_chunk.prev_outgoing_receipts_root()) { - return Err(Error::InvalidChunkStateWitness(format!( - "Receipt proof for chunk {:?} has invalid merkle path, doesn't match outgoing receipts root", - from_chunk.chunk_hash() - ))); - } - Ok(()) -} - -#[allow(clippy::large_enum_variant)] -enum MainTransition { - Genesis { chunk_extra: ChunkExtra, block_hash: CryptoHash, shard_id: ShardId }, - NewChunk(NewChunkData), -} - -impl MainTransition { - fn block_hash(&self) -> CryptoHash { - match self { - Self::Genesis { block_hash, .. } => *block_hash, - Self::NewChunk(data) => data.block.block_hash, - } - } - - fn shard_id(&self) -> ShardId { - match self { - Self::Genesis { shard_id, .. } => *shard_id, - Self::NewChunk(data) => data.chunk_header.shard_id(), - } - } -} - -pub(crate) struct PreValidationOutput { - main_transition_params: MainTransition, - implicit_transition_params: Vec, -} - -pub(crate) fn validate_chunk_state_witness( - state_witness: ChunkStateWitness, - pre_validation_output: PreValidationOutput, - epoch_manager: &dyn EpochManagerAdapter, - runtime_adapter: &dyn RuntimeAdapter, - main_state_transition_cache: &MainStateTransitionCache, -) -> Result<(), Error> { - let _timer = metrics::CHUNK_STATE_WITNESS_VALIDATION_TIME - .with_label_values(&[&state_witness.chunk_header.shard_id().to_string()]) - .start_timer(); - let span = tracing::debug_span!(target: "client", "validate_chunk_state_witness").entered(); - let block_hash = pre_validation_output.main_transition_params.block_hash(); - let epoch_id = epoch_manager.get_epoch_id(&block_hash)?; - let shard_uid = epoch_manager - .shard_id_to_uid(pre_validation_output.main_transition_params.shard_id(), &epoch_id)?; - let protocol_version = epoch_manager.get_epoch_protocol_version(&epoch_id)?; - let cache_result = { - let mut shard_cache = main_state_transition_cache.lock().unwrap(); - shard_cache.get_mut(&shard_uid).and_then(|cache| cache.get(&block_hash).cloned()) - }; - let (mut chunk_extra, outgoing_receipts) = - match (pre_validation_output.main_transition_params, cache_result) { - (MainTransition::Genesis { chunk_extra, .. }, _) => (chunk_extra, vec![]), - (MainTransition::NewChunk(new_chunk_data), None) => { - let chunk_header = new_chunk_data.chunk_header.clone(); - let NewChunkResult { apply_result: mut main_apply_result, .. } = apply_new_chunk( - ApplyChunkReason::ValidateChunkStateWitness, - &span, - new_chunk_data, - ShardContext { - shard_uid, - cares_about_shard_this_epoch: true, - will_shard_layout_change: false, - should_apply_chunk: true, - need_to_reshard: false, - }, - runtime_adapter, - epoch_manager, - )?; - let outgoing_receipts = std::mem::take(&mut main_apply_result.outgoing_receipts); - let chunk_extra = - apply_result_to_chunk_extra(protocol_version, main_apply_result, &chunk_header); - - (chunk_extra, outgoing_receipts) - } - (_, Some(result)) => (result.chunk_extra, result.outgoing_receipts), - }; - if chunk_extra.state_root() != &state_witness.main_state_transition.post_state_root { - // This is an early check, it's not for correctness, only for better - // error reporting in case of an invalid state witness due to a bug. - // Only the final state root check against the chunk header is required. - return Err(Error::InvalidChunkStateWitness(format!( - "Post state root {:?} for main transition does not match expected post state root {:?}", - chunk_extra.state_root(), - state_witness.main_state_transition.post_state_root, - ))); - } - - // Compute receipt hashes here to avoid copying receipts - let outgoing_receipts_hashes = { - let shard_layout = epoch_manager - .get_shard_layout_from_prev_block(state_witness.chunk_header.prev_block_hash())?; - Chain::build_receipts_hashes(&outgoing_receipts, &shard_layout) - }; - // Save main state transition result to cache. - { - let mut shard_cache = main_state_transition_cache.lock().unwrap(); - let cache = shard_cache - .entry(shard_uid) - .or_insert_with(|| LruCache::new(NUM_WITNESS_RESULT_CACHE_ENTRIES)); - cache.put( - block_hash, - ChunkStateWitnessValidationResult { - chunk_extra: chunk_extra.clone(), - outgoing_receipts: outgoing_receipts, - }, - ); - } - - for (block, transition) in pre_validation_output - .implicit_transition_params - .into_iter() - .zip(state_witness.implicit_transitions.into_iter()) - { - let block_hash = block.block_hash; - let old_chunk_data = OldChunkData { - prev_chunk_extra: chunk_extra.clone(), - resharding_state_roots: None, - block, - storage_context: StorageContext { - storage_data_source: StorageDataSource::Recorded(PartialStorage { - nodes: transition.base_state, - }), - state_patch: Default::default(), - }, - }; - let OldChunkResult { apply_result, .. } = apply_old_chunk( - ApplyChunkReason::ValidateChunkStateWitness, - &span, - old_chunk_data, - ShardContext { - // Consider other shard uid in case of resharding. - shard_uid, - cares_about_shard_this_epoch: true, - will_shard_layout_change: false, - should_apply_chunk: false, - need_to_reshard: false, - }, - runtime_adapter, - epoch_manager, - )?; - *chunk_extra.state_root_mut() = apply_result.new_root; - if chunk_extra.state_root() != &transition.post_state_root { - // This is an early check, it's not for correctness, only for better - // error reporting in case of an invalid state witness due to a bug. - // Only the final state root check against the chunk header is required. - return Err(Error::InvalidChunkStateWitness(format!( - "Post state root {:?} for implicit transition at block {:?}, does not match expected state root {:?}", - chunk_extra.state_root(), block_hash, transition.post_state_root - ))); - } - } - - // Finally, verify that the newly proposed chunk matches everything we have computed. - let (outgoing_receipts_root, _) = merklize(&outgoing_receipts_hashes); - validate_chunk_with_chunk_extra_and_receipts_root( - &chunk_extra, - &state_witness.chunk_header, - &outgoing_receipts_root, - protocol_version, - )?; - - Ok(()) -} - -fn apply_result_to_chunk_extra( - protocol_version: ProtocolVersion, - apply_result: ApplyChunkResult, - chunk: &ShardChunkHeader, -) -> ChunkExtra { - let (outcome_root, _) = ApplyChunkResult::compute_outcomes_proof(&apply_result.outcomes); - ChunkExtra::new( - protocol_version, - &apply_result.new_root, - outcome_root, - apply_result.validator_proposals, - apply_result.total_gas_burnt, - chunk.gas_limit(), - apply_result.total_balance_burnt, - apply_result.congestion_info, - ) -} - pub(crate) fn send_chunk_endorsement_to_block_producers( chunk_header: &ShardChunkHeader, epoch_manager: &dyn EpochManagerAdapter, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, network_sender: &Sender, chunk_endorsement_tracker: &ChunkEndorsementTracker, ) { @@ -753,11 +244,11 @@ impl Client { /// you can use the `processing_done_tracker` argument (but it's optional, it's safe to pass None there). pub fn process_chunk_state_witness( &mut self, - encoded_witness: EncodedChunkStateWitness, + witness: ChunkStateWitness, + raw_witness_size: ChunkStateWitnessSize, processing_done_tracker: Option, + signer: Option>, ) -> Result<(), Error> { - let (witness, raw_witness_size) = self.decode_state_witness(&encoded_witness)?; - tracing::debug!( target: "client", chunk_hash=?witness.chunk_header.chunk_hash(), @@ -765,10 +256,18 @@ impl Client { "process_chunk_state_witness", ); + // Chunk producers should not receive state witness from themselves. + log_assert!( + signer.is_some(), + "Received a chunk state witness but this is not a validator node. Witness={:?}", + witness + ); + let signer = signer.unwrap(); + // Send the acknowledgement for the state witness back to the chunk producer. // This is currently used for network roundtrip time measurement, so we do not need to // wait for validation to finish. - self.send_state_witness_ack(&witness); + self.send_state_witness_ack(&witness, &signer); if self.config.save_latest_witnesses { self.chain.chain_store.save_latest_chunk_state_witness(&witness)?; @@ -779,6 +278,7 @@ impl Client { witness, &block, processing_done_tracker, + &signer, ), Err(Error::DBNotFoundErr(_)) => { // Previous block isn't available at the moment, add this witness to the orphan pool. @@ -789,7 +289,19 @@ impl Client { } } - fn send_state_witness_ack(&self, witness: &ChunkStateWitness) { + fn send_state_witness_ack(&self, witness: &ChunkStateWitness, signer: &Arc) { + // In production PartialWitnessActor does not forward a state witness to the chunk producer that + // produced the witness. However some tests bypass PartialWitnessActor, thus when a chunk producer + // receives its own state witness, we log a warning instead of panicking. + // TODO: Make sure all tests run with "test_features" and panic for non-test builds. + if signer.validator_id() == &witness.chunk_producer { + tracing::warn!( + "Validator {:?} received state witness from itself. Witness={:?}", + signer.validator_id(), + witness + ); + return; + } self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( NetworkRequests::ChunkStateWitnessAck( witness.chunk_producer.clone(), @@ -803,6 +315,7 @@ impl Client { witness: ChunkStateWitness, prev_block: &Block, processing_done_tracker: Option, + signer: &Arc, ) -> Result<(), Error> { if witness.chunk_header.prev_block_hash() != prev_block.hash() { return Err(Error::Other(format!( @@ -812,23 +325,11 @@ impl Client { ))); } - self.chunk_validator.start_validating_chunk(witness, &self.chain, processing_done_tracker) - } - - fn decode_state_witness( - &self, - encoded_witness: &EncodedChunkStateWitness, - ) -> Result<(ChunkStateWitness, ChunkStateWitnessSize), Error> { - let decode_start = std::time::Instant::now(); - let (witness, raw_witness_size) = encoded_witness.decode()?; - let decode_elapsed_seconds = decode_start.elapsed().as_secs_f64(); - let witness_shard = witness.chunk_header.shard_id(); - - // Record metrics after validating the witness - metrics::CHUNK_STATE_WITNESS_DECODE_TIME - .with_label_values(&[&witness_shard.to_string()]) - .observe(decode_elapsed_seconds); - - Ok((witness, raw_witness_size)) + self.chunk_validator.start_validating_chunk( + witness, + &self.chain, + processing_done_tracker, + signer, + ) } } diff --git a/chain/client/src/stateless_validation/chunk_validator/orphan_witness_handling.rs b/chain/client/src/stateless_validation/chunk_validator/orphan_witness_handling.rs index 05f9cb83f91..4cb816f7331 100644 --- a/chain/client/src/stateless_validation/chunk_validator/orphan_witness_handling.rs +++ b/chain/client/src/stateless_validation/chunk_validator/orphan_witness_handling.rs @@ -8,9 +8,12 @@ use crate::Client; use near_chain::Block; use near_chain_primitives::Error; +use near_primitives::hash::CryptoHash; use near_primitives::stateless_validation::ChunkStateWitness; -use near_primitives::types::{BlockHeight, EpochId}; +use near_primitives::types::BlockHeight; +use near_primitives::validator_signer::ValidatorSigner; use std::ops::Range; +use std::sync::Arc; /// We keep only orphan witnesses that are within this distance of /// the current chain head. This helps to reduce the size of @@ -68,28 +71,13 @@ impl Client { return Ok(HandleOrphanWitnessOutcome::TooBig(witness_size)); } - // Try to find the EpochId to which this witness will belong based on its height. - // It's not always possible to determine the exact epoch_id because the exact - // starting height of the next epoch isn't known until it actually starts, - // so things can get unclear around epoch boundaries. - // Let's collect the epoch_ids in which the witness might possibly be. - let possible_epochs = - self.epoch_manager.possible_epochs_of_height_around_tip(&chain_head, witness_height)?; - - if !possible_epochs.contains(&witness.epoch_id) { - return Ok(HandleOrphanWitnessOutcome::UnsupportedEpochId(witness.epoch_id)); - } - // Orphan witness is OK, save it to the pool tracing::debug!(target: "client", "Saving an orphaned ChunkStateWitness to orphan pool"); self.chunk_validator.orphan_witness_pool.add_orphan_state_witness(witness, witness_size); Ok(HandleOrphanWitnessOutcome::SavedToPool) } - /// Once a new block arrives, we can process the orphaned chunk state witnesses that were waiting - /// for this block. This function takes the ready witnesses out of the orhan pool and process them. - /// It also removes old witnesses (below final height) from the orphan pool to save memory. - pub fn process_ready_orphan_witnesses_and_clean_old(&mut self, new_block: &Block) { + fn process_ready_orphan_witnesses(&mut self, new_block: &Block, signer: &Arc) { let ready_witnesses = self .chunk_validator .orphan_witness_pool @@ -105,29 +93,44 @@ impl Client { "Processing an orphaned ChunkStateWitness, its previous block has arrived." ); if let Err(err) = - self.process_chunk_state_witness_with_prev_block(witness, new_block, None) + self.process_chunk_state_witness_with_prev_block(witness, new_block, None, signer) { tracing::error!(target: "client", ?err, "Error processing orphan chunk state witness"); } } + } + + /// Once a new block arrives, we can process the orphaned chunk state witnesses that were waiting + /// for this block. This function takes the ready witnesses out of the orhan pool and process them. + /// It also removes old witnesses (below final height) from the orphan pool to save memory. + pub fn process_ready_orphan_witnesses_and_clean_old( + &mut self, + new_block: &Block, + signer: &Option>, + ) { + if let Some(signer) = signer { + self.process_ready_orphan_witnesses(new_block, signer); + } // Remove all orphan witnesses that are below the last final block of the new block. // They won't be used, so we can remove them from the pool to save memory. - let last_final_block = - match self.chain.get_block_header(new_block.header().last_final_block()) { - Ok(block_header) => block_header, - Err(err) => { - // TODO(wacban) this error happens often in integration - // tests when the last final block is genesis / genesis.prev. - tracing::error!( - target: "client", - last_final_block = ?new_block.header().last_final_block(), - ?err, - "Error getting last final block of the new block" - ); - return; - } - }; + let last_final_block = new_block.header().last_final_block(); + // Handle genesis gracefully. + if last_final_block == &CryptoHash::default() { + return; + } + let last_final_block = match self.chain.get_block_header(last_final_block) { + Ok(block_header) => block_header, + Err(err) => { + tracing::error!( + target: "client", + ?last_final_block, + ?err, + "Error getting last final block of the new block" + ); + return; + } + }; self.chunk_validator .orphan_witness_pool .remove_witnesses_below_final_height(last_final_block.height()); @@ -145,5 +148,4 @@ pub enum HandleOrphanWitnessOutcome { SavedToPool, TooBig(usize), TooFarFromHead { head_height: BlockHeight, witness_height: BlockHeight }, - UnsupportedEpochId(EpochId), } diff --git a/chain/client/src/stateless_validation/chunk_validator/orphan_witness_pool.rs b/chain/client/src/stateless_validation/chunk_validator/orphan_witness_pool.rs index 0a68c0b3344..93b70fe0523 100644 --- a/chain/client/src/stateless_validation/chunk_validator/orphan_witness_pool.rs +++ b/chain/client/src/stateless_validation/chunk_validator/orphan_witness_pool.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroUsize; + use lru::LruCache; use near_chain_configs::default_orphan_state_witness_pool_size; use near_primitives::hash::CryptoHash; @@ -31,7 +33,9 @@ impl OrphanStateWitnessPool { to performance problems.", cache_capacity); } - OrphanStateWitnessPool { witness_cache: LruCache::new(cache_capacity) } + OrphanStateWitnessPool { + witness_cache: LruCache::new(NonZeroUsize::new(cache_capacity).unwrap()), + } } /// Add an orphaned chunk state witness to the pool. The witness will be put in a cache and it'll @@ -371,22 +375,6 @@ mod tests { assert_contents(waiting_for_102, vec![witness4]); } - /// An OrphanStateWitnessPool with 0 capacity shouldn't crash, it should just ignore all witnesses - #[test] - fn zero_capacity() { - let mut pool = OrphanStateWitnessPool::new(0); - - pool.add_orphan_state_witness(make_witness(100, 1, block(99), 0), 0); - pool.add_orphan_state_witness(make_witness(100, 1, block(99), 0), 1); - pool.add_orphan_state_witness(make_witness(100, 2, block(99), 0), 0); - pool.add_orphan_state_witness(make_witness(101, 0, block(100), 0), 0); - - let waiting = pool.take_state_witnesses_waiting_for_block(&block(99)); - assert_contents(waiting, vec![]); - - assert_empty(&pool); - } - /// OrphanStateWitnessPool has a Drop implementation which clears the metrics. /// It's hard to test it because metrics are global and it could interfere with other tests, /// but we can at least test that it doesn't crash. That's always something. diff --git a/chain/client/src/stateless_validation/mod.rs b/chain/client/src/stateless_validation/mod.rs index 55798f17716..780a1e4cfc8 100644 --- a/chain/client/src/stateless_validation/mod.rs +++ b/chain/client/src/stateless_validation/mod.rs @@ -1,7 +1,6 @@ pub mod chunk_endorsement_tracker; pub mod chunk_validator; pub mod partial_witness; -pub mod processing_tracker; mod shadow_validate; mod state_witness_producer; pub mod state_witness_tracker; diff --git a/chain/client/src/stateless_validation/partial_witness/encoding.rs b/chain/client/src/stateless_validation/partial_witness/encoding.rs new file mode 100644 index 00000000000..36c5d9711c0 --- /dev/null +++ b/chain/client/src/stateless_validation/partial_witness/encoding.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use near_primitives::checked_feature; +use near_primitives::reed_solomon::{ + reed_solomon_decode, reed_solomon_encode, reed_solomon_part_length, +}; +use near_primitives::stateless_validation::EncodedChunkStateWitness; +use near_vm_runner::logic::ProtocolVersion; +use reed_solomon_erasure::galois_8::ReedSolomon; + +/// Ratio of the number of data parts to total parts in the Reed Solomon encoding. +/// The tradeoff here is having a higher ratio is better for handling missing parts and network errors +/// but increases the size of the encoded state witness and the total network bandwidth requirements. +const RATIO_DATA_PARTS_OLD: f32 = 0.8; +const RATIO_DATA_PARTS: f32 = 0.6; + +/// Type alias around what ReedSolomon represents data part as. +/// This should help with making the code a bit more understandable. +pub type WitnessPart = Option>; + +/// Reed Solomon encoder wrapper for encoding and decoding state witness parts. +pub struct WitnessEncoder { + /// None corresponds to the case when we are the only validator for the chunk + /// since ReedSolomon does not support having exactly 1 total part count and + /// no parity parts. + rs: Option, +} + +impl WitnessEncoder { + pub fn new(total_parts: usize, protocol_version: ProtocolVersion) -> WitnessEncoder { + let rs = if total_parts > 1 { + let data_parts = num_witness_data_parts(total_parts, protocol_version); + Some(ReedSolomon::new(data_parts, total_parts - data_parts).unwrap()) + } else { + None + }; + Self { rs } + } + + pub fn total_parts(&self) -> usize { + match self.rs { + Some(ref rs) => rs.total_shard_count(), + None => 1, + } + } + + pub fn data_parts(&self) -> usize { + match self.rs { + Some(ref rs) => rs.data_shard_count(), + None => 1, + } + } + + pub fn encode(&self, witness: &EncodedChunkStateWitness) -> (Vec, usize) { + match self.rs { + Some(ref rs) => reed_solomon_encode(rs, witness), + None => { + (vec![Some(witness.as_slice().to_vec().into_boxed_slice())], witness.size_bytes()) + } + } + } + + pub fn decode( + &self, + parts: &mut [WitnessPart], + encoded_length: usize, + ) -> Result { + match self.rs { + Some(ref rs) => reed_solomon_decode(rs, parts, encoded_length), + None => { + Ok(EncodedChunkStateWitness::from_boxed_slice(parts[0].as_ref().unwrap().clone())) + } + } + } +} + +/// We keep one encoder for each length of chunk_validators to avoid re-creating the encoder. +pub struct WitnessEncoderCache { + instances: HashMap<(usize, ProtocolVersion), Arc>, +} + +impl WitnessEncoderCache { + pub fn new() -> Self { + Self { instances: HashMap::new() } + } + + pub fn entry( + &mut self, + total_parts: usize, + protocol_version: ProtocolVersion, + ) -> Arc { + self.instances + .entry((total_parts, protocol_version)) + .or_insert_with(|| Arc::new(WitnessEncoder::new(total_parts, protocol_version))) + .clone() + } +} + +pub fn witness_part_length( + encoded_witness_size: usize, + total_parts: usize, + protocol_version: ProtocolVersion, +) -> usize { + reed_solomon_part_length( + encoded_witness_size, + num_witness_data_parts(total_parts, protocol_version), + ) +} + +fn num_witness_data_parts(total_parts: usize, protocol_version: ProtocolVersion) -> usize { + let ratio_data_parts = + if checked_feature!("stable", ChangePartialWitnessDataPartsRequired, protocol_version) { + RATIO_DATA_PARTS + } else { + RATIO_DATA_PARTS_OLD + }; + std::cmp::max((total_parts as f32 * ratio_data_parts) as usize, 1) +} diff --git a/chain/client/src/stateless_validation/partial_witness/mod.rs b/chain/client/src/stateless_validation/partial_witness/mod.rs index 50d93628726..ad4b314edf5 100644 --- a/chain/client/src/stateless_validation/partial_witness/mod.rs +++ b/chain/client/src/stateless_validation/partial_witness/mod.rs @@ -1,2 +1,3 @@ +mod encoding; pub mod partial_witness_actor; mod partial_witness_tracker; diff --git a/chain/client/src/stateless_validation/partial_witness/partial_witness_actor.rs b/chain/client/src/stateless_validation/partial_witness/partial_witness_actor.rs index 8850729c5f6..826a4631afa 100644 --- a/chain/client/src/stateless_validation/partial_witness/partial_witness_actor.rs +++ b/chain/client/src/stateless_validation/partial_witness/partial_witness_actor.rs @@ -3,8 +3,9 @@ use std::sync::Arc; use itertools::Itertools; use near_async::messaging::{Actor, CanSend, Handler, Sender}; use near_async::time::Clock; -use near_async::{MultiSend, MultiSendMessage, MultiSenderFrom}; +use near_async::{MultiSend, MultiSenderFrom}; use near_chain::Error; +use near_chain_configs::MutableValidatorSigner; use near_epoch_manager::EpochManagerAdapter; use near_network::state_witness::{ ChunkStateWitnessAckMessage, PartialEncodedStateWitnessForwardMessage, @@ -13,10 +14,10 @@ use near_network::state_witness::{ use near_network::types::{NetworkRequests, PeerManagerAdapter, PeerManagerMessageRequest}; use near_performance_metrics_macros::perf; use near_primitives::block::Tip; -use near_primitives::reed_solomon::reed_solomon_encode; use near_primitives::sharding::ShardChunkHeader; use near_primitives::stateless_validation::{ ChunkStateWitness, ChunkStateWitnessAck, EncodedChunkStateWitness, PartialEncodedStateWitness, + MAX_COMPRESSED_STATE_WITNESS_SIZE, }; use near_primitives::types::{AccountId, BlockHeightDelta, EpochId}; use near_primitives::validator_signer::ValidatorSigner; @@ -26,13 +27,16 @@ use crate::client_actor::ClientSenderForPartialWitness; use crate::metrics; use crate::stateless_validation::state_witness_tracker::ChunkStateWitnessTracker; -use super::partial_witness_tracker::{PartialEncodedStateWitnessTracker, RsMap}; +use super::encoding::{witness_part_length, WitnessEncoderCache}; +use super::partial_witness_tracker::PartialEncodedStateWitnessTracker; pub struct PartialWitnessActor { /// Adapter to send messages to the network. network_adapter: PeerManagerAdapter, - /// Validator signer to sign the state witness. - my_signer: Arc, + /// Validator signer to sign the state witness. This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + my_signer: MutableValidatorSigner, /// Epoch manager to get the set of chunk validators epoch_manager: Arc, /// Tracks the parts of the state witness sent from chunk producers to chunk validators. @@ -41,7 +45,7 @@ pub struct PartialWitnessActor { state_witness_tracker: ChunkStateWitnessTracker, /// Reed Solomon encoder for encoding state witness parts. /// We keep one wrapper for each length of chunk_validators to avoid re-creating the encoder. - rs_map: RsMap, + encoders: WitnessEncoderCache, /// Currently used to find the chain HEAD when validating partial witnesses, /// but should be removed if we implement retrieving this info from the client store: Store, @@ -61,8 +65,7 @@ pub struct DistributeStateWitnessRequest { pub state_witness: ChunkStateWitness, } -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct PartialWitnessSenderForClient { pub distribute_chunk_state_witness: Sender, } @@ -103,7 +106,7 @@ impl PartialWitnessActor { clock: Clock, network_adapter: PeerManagerAdapter, client_sender: ClientSenderForPartialWitness, - my_signer: Arc, + my_signer: MutableValidatorSigner, epoch_manager: Arc, store: Store, ) -> Self { @@ -115,7 +118,7 @@ impl PartialWitnessActor { epoch_manager, partial_witness_tracker, state_witness_tracker: ChunkStateWitnessTracker::new(clock), - rs_map: RsMap::new(), + encoders: WitnessEncoderCache::new(), store, } } @@ -126,33 +129,22 @@ impl PartialWitnessActor { ) -> Result<(), Error> { let DistributeStateWitnessRequest { epoch_id, chunk_header, state_witness } = msg; - let chunk_validators = self - .epoch_manager - .get_chunk_validator_assignments( - &epoch_id, - chunk_header.shard_id(), - chunk_header.height_created(), - )? - .ordered_chunk_validators(); - tracing::debug!( target: "client", chunk_hash=?chunk_header.chunk_hash(), - ?chunk_validators, "distribute_chunk_state_witness", ); - let witness_bytes = compress_witness(&state_witness)?; + let signer = match self.my_signer.get() { + Some(signer) => signer, + None => { + return Err(Error::NotAValidator(format!("distribute state witness"))); + } + }; - // Record the witness in order to match the incoming acks for measuring round-trip times. - // See process_chunk_state_witness_ack for the handling of the ack messages. - self.state_witness_tracker.record_witness_sent( - &state_witness, - witness_bytes.size_bytes(), - chunk_validators.len(), - ); + let witness_bytes = compress_witness(&state_witness)?; - self.send_state_witness_parts(epoch_id, chunk_header, witness_bytes, chunk_validators)?; + self.send_state_witness_parts(epoch_id, chunk_header, witness_bytes, &signer)?; Ok(()) } @@ -163,21 +155,30 @@ impl PartialWitnessActor { epoch_id: EpochId, chunk_header: ShardChunkHeader, witness_bytes: EncodedChunkStateWitness, - chunk_validators: Vec, - ) -> Vec<(AccountId, PartialEncodedStateWitness)> { + signer: &ValidatorSigner, + ) -> Result, Error> { + let chunk_validators = self + .epoch_manager + .get_chunk_validator_assignments( + &epoch_id, + chunk_header.shard_id(), + chunk_header.height_created(), + )? + .ordered_chunk_validators(); + + tracing::debug!( + target: "client", + chunk_hash=?chunk_header.chunk_hash(), + ?chunk_validators, + "generate_state_witness_parts", + ); + // Break the state witness into parts using Reed Solomon encoding. - let rs = self.rs_map.entry(chunk_validators.len()); - - // For the case when we are the only validator for the chunk, we don't need to do Reed Solomon encoding. - let (parts, encoded_length) = match rs.as_ref() { - Some(rs) => reed_solomon_encode(&rs, witness_bytes), - None => ( - vec![Some(witness_bytes.as_slice().to_vec().into_boxed_slice())], - witness_bytes.size_bytes(), - ), - }; + let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id)?; + let encoder = self.encoders.entry(chunk_validators.len(), protocol_version); + let (parts, encoded_length) = encoder.encode(&witness_bytes); - chunk_validators + Ok(chunk_validators .iter() .zip_eq(parts) .enumerate() @@ -185,16 +186,16 @@ impl PartialWitnessActor { // It's fine to unwrap part here as we just constructed the parts above and we expect // all of them to be present. let partial_witness = PartialEncodedStateWitness::new( - epoch_id.clone(), + epoch_id, chunk_header.clone(), part_ord, part.unwrap().to_vec(), encoded_length, - self.my_signer.as_ref(), + signer, ); (chunk_validator.clone(), partial_witness) }) - .collect_vec() + .collect_vec()) } // Break the state witness into parts and send each part to the corresponding chunk validator owner. @@ -205,35 +206,40 @@ impl PartialWitnessActor { epoch_id: EpochId, chunk_header: ShardChunkHeader, witness_bytes: EncodedChunkStateWitness, - chunk_validators: Vec, + signer: &ValidatorSigner, ) -> Result<(), Error> { + // Capture these values first, as the sources are consumed before calling record_witness_sent. + let chunk_hash = chunk_header.chunk_hash(); + let witness_size_in_bytes = witness_bytes.size_bytes(); + // Record time taken to encode the state witness parts. let shard_id_label = chunk_header.shard_id().to_string(); let encode_timer = metrics::PARTIAL_WITNESS_ENCODE_TIME .with_label_values(&[shard_id_label.as_str()]) .start_timer(); - let validator_witness_tuple = self.generate_state_witness_parts( - epoch_id, - chunk_header, - witness_bytes, - chunk_validators.clone(), - ); + let mut validator_witness_tuple = + self.generate_state_witness_parts(epoch_id, chunk_header, witness_bytes, signer)?; encode_timer.observe_duration(); // Since we can't send network message to ourselves, we need to send the PartialEncodedStateWitnessForward // message for our part. - if let Some((_, partial_witness)) = validator_witness_tuple + if let Some(index) = validator_witness_tuple .iter() - .find(|(validator, _)| validator == self.my_signer.validator_id()) + .position(|(validator, _)| validator == signer.validator_id()) { - self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( - NetworkRequests::PartialEncodedStateWitnessForward( - chunk_validators, - partial_witness.clone(), - ), - )); + // This also removes this validator from the list, since we do not need to send our own witness part to self. + let (_, partial_witness) = validator_witness_tuple.swap_remove(index); + self.forward_state_witness_part(partial_witness, signer)?; } + // Record the witness in order to match the incoming acks for measuring round-trip times. + // See process_chunk_state_witness_ack for the handling of the ack messages. + self.state_witness_tracker.record_witness_sent( + chunk_hash, + witness_size_in_bytes, + validator_witness_tuple.len(), + ); + // Send the parts to the corresponding chunk validator owners. self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( NetworkRequests::PartialEncodedStateWitness(validator_witness_tuple), @@ -241,31 +247,19 @@ impl PartialWitnessActor { Ok(()) } - /// Handles the state witness ack message from the chunk validator. - /// It computes the round-trip time between sending the state witness and receiving - /// the ack message and updates the corresponding metric with it. - /// Currently we do not raise an error for handling of witness-ack messages, - /// as it is used only for tracking some networking metrics. - pub fn handle_chunk_state_witness_ack(&mut self, witness_ack: ChunkStateWitnessAck) { - self.state_witness_tracker.on_witness_ack_received(witness_ack); - } - - /// Function to handle receiving partial_encoded_state_witness message from chunk producer. - pub fn handle_partial_encoded_state_witness( - &mut self, + /// Sends the witness part to the chunk validators, except for the following: + /// 1) The current validator, 2) Chunk producer that originally generated the witness part. + fn forward_state_witness_part( + &self, partial_witness: PartialEncodedStateWitness, + signer: &ValidatorSigner, ) -> Result<(), Error> { - tracing::debug!(target: "client", ?partial_witness, "Receive PartialEncodedStateWitnessMessage"); - - // Validate the partial encoded state witness. - self.validate_partial_encoded_state_witness(&partial_witness)?; - - // Store the partial encoded state witness for self. - self.partial_witness_tracker - .store_partial_encoded_state_witness(partial_witness.clone())?; - - // Forward the part to all the chunk validators. - let chunk_validators = self + let chunk_producer = self.epoch_manager.get_chunk_producer( + partial_witness.epoch_id(), + partial_witness.height_created(), + partial_witness.shard_id(), + )?; + let ordered_chunk_validators = self .epoch_manager .get_chunk_validator_assignments( partial_witness.epoch_id(), @@ -273,10 +267,43 @@ impl PartialWitnessActor { partial_witness.height_created(), )? .ordered_chunk_validators(); - + // Forward witness part to chunk validators except for the following: + // (1) the current validator and (2) validator that produced the chunk and witness. + let target_chunk_validators = ordered_chunk_validators + .into_iter() + .filter(|validator| validator != signer.validator_id() && *validator != chunk_producer) + .collect(); self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( - NetworkRequests::PartialEncodedStateWitnessForward(chunk_validators, partial_witness), + NetworkRequests::PartialEncodedStateWitnessForward( + target_chunk_validators, + partial_witness, + ), )); + Ok(()) + } + + /// Function to handle receiving partial_encoded_state_witness message from chunk producer. + pub fn handle_partial_encoded_state_witness( + &mut self, + partial_witness: PartialEncodedStateWitness, + ) -> Result<(), Error> { + tracing::debug!(target: "client", ?partial_witness, "Receive PartialEncodedStateWitnessMessage"); + + let signer = match self.my_signer.get() { + Some(signer) => signer, + None => { + return Err(Error::NotAValidator(format!("handle partial encoded state witness"))); + } + }; + + // Validate the partial encoded state witness. + if self.validate_partial_encoded_state_witness(&partial_witness, &signer)? { + // Store the partial encoded state witness for self. + self.partial_witness_tracker + .store_partial_encoded_state_witness(partial_witness.clone())?; + // Forward the part to all the chunk validators. + self.forward_state_witness_part(partial_witness, &signer)?; + } Ok(()) } @@ -286,11 +313,20 @@ impl PartialWitnessActor { &mut self, partial_witness: PartialEncodedStateWitness, ) -> Result<(), Error> { - // Validate the partial encoded state witness. - self.validate_partial_encoded_state_witness(&partial_witness)?; + let signer = match self.my_signer.get() { + Some(signer) => signer, + None => { + return Err(Error::NotAValidator(format!( + "handle partial encoded state witness forward" + ))); + } + }; - // Store the partial encoded state witness for self. - self.partial_witness_tracker.store_partial_encoded_state_witness(partial_witness)?; + // Validate the partial encoded state witness. + if self.validate_partial_encoded_state_witness(&partial_witness, &signer)? { + // Store the partial encoded state witness for self. + self.partial_witness_tracker.store_partial_encoded_state_witness(partial_witness)?; + } Ok(()) } @@ -298,14 +334,23 @@ impl PartialWitnessActor { /// Function to validate the partial encoded state witness. We check the following /// - shard_id is valid /// - we are one of the validators for the chunk + /// - height_created is in (last_final_height..chain_head_height + MAX_HEIGHTS_AHEAD] range + /// - epoch_id is within epoch_manager's possible_epochs_of_height_around_tip /// - part_ord is valid and within range of the number of expected parts for this chunk /// - partial_witness signature is valid and from the expected chunk_producer /// TODO(stateless_validation): Include checks from handle_orphan_state_witness in orphan_witness_handling.rs /// These include checks based on epoch_id validity, witness size, height_created, distance from chain head, etc. + /// Returns: + /// - Ok(true) if partial witness is valid and we should process it. + /// - Ok(false) if partial witness is potentially valid, but at this point we + /// should not process it. One example of that is if the witness is too old. + /// - Err if partial witness is invalid which most probably indicates malicious + /// behavior. fn validate_partial_encoded_state_witness( &self, partial_witness: &PartialEncodedStateWitness, - ) -> Result<(), Error> { + signer: &ValidatorSigner, + ) -> Result { if !self .epoch_manager .get_shard_layout(&partial_witness.epoch_id())? @@ -325,7 +370,7 @@ impl PartialWitnessActor { partial_witness.shard_id(), partial_witness.height_created(), )?; - if !chunk_validator_assignments.contains(self.my_signer.validator_id()) { + if !chunk_validator_assignments.contains(signer.validator_id()) { return Err(Error::NotAChunkValidator); } @@ -338,6 +383,22 @@ impl PartialWitnessActor { ))); } + let protocol_version = + self.epoch_manager.get_epoch_protocol_version(&partial_witness.epoch_id())?; + let max_part_len = witness_part_length( + MAX_COMPRESSED_STATE_WITNESS_SIZE.as_u64() as usize, + num_parts, + protocol_version, + ); + if partial_witness.part_size() > max_part_len { + return Err(Error::InvalidPartialChunkStateWitness(format!( + "Part size {} exceed limit of {} (total parts: {})", + partial_witness.part_size(), + max_part_len, + num_parts + ))); + } + // TODO(https://github.com/near/nearcore/issues/11301): replace these direct DB accesses with messages // sent to the client actor. for a draft, see https://github.com/near/nearcore/commit/e186dc7c0b467294034c60758fe555c78a31ef2d let head = self.store.get_ser::(DBCol::BlockMisc, HEAD_KEY)?; @@ -350,39 +411,75 @@ impl PartialWitnessActor { // as well as malicious behavior of a chunk producer. if let Some(final_head) = final_head { if partial_witness.height_created() <= final_head.height { - return Err(Error::InvalidPartialChunkStateWitness(format!( - "Height created of {} in PartialEncodedStateWitness not greater than final head height {}", - partial_witness.height_created(), - final_head.height, - ))); + tracing::debug!( + target: "client", + ?partial_witness, + final_head_height = final_head.height, + "Skipping partial witness because its height created is not greater than final head height", + ); + return Ok(false); } } if let Some(head) = head { if partial_witness.height_created() > head.height + MAX_HEIGHTS_AHEAD { - return Err(Error::InvalidPartialChunkStateWitness(format!( - "Height created of {} in PartialEncodedStateWitness more than {} blocks ahead of head height {}", - partial_witness.height_created(), - MAX_HEIGHTS_AHEAD, - head.height, - ))); + tracing::debug!( + target: "client", + ?partial_witness, + head_height = head.height, + "Skipping partial witness because its height created is more than {} blocks ahead of head height", + MAX_HEIGHTS_AHEAD + ); + return Ok(false); + } + + // Try to find the EpochId to which this witness will belong based on its height. + // It's not always possible to determine the exact epoch_id because the exact + // starting height of the next epoch isn't known until it actually starts, + // so things can get unclear around epoch boundaries. + // Let's collect the epoch_ids in which the witness might possibly be. + let possible_epochs = self + .epoch_manager + .possible_epochs_of_height_around_tip(&head, partial_witness.height_created())?; + if !possible_epochs.contains(&partial_witness.epoch_id()) { + tracing::debug!( + target: "client", + ?partial_witness, + ?possible_epochs, + "Skipping partial witness because its EpochId is is not in the possible list of epochs", + ); + return Ok(false); } } + if !self.epoch_manager.verify_partial_witness_signature(&partial_witness)? { return Err(Error::InvalidPartialChunkStateWitness("Invalid signature".to_string())); } - Ok(()) + Ok(true) + } + + /// Handles the state witness ack message from the chunk validator. + /// It computes the round-trip time between sending the state witness and receiving + /// the ack message and updates the corresponding metric with it. + /// Currently we do not raise an error for handling of witness-ack messages, + /// as it is used only for tracking some networking metrics. + pub fn handle_chunk_state_witness_ack(&mut self, witness_ack: ChunkStateWitnessAck) { + self.state_witness_tracker.on_witness_ack_received(witness_ack); } } fn compress_witness(witness: &ChunkStateWitness) -> Result { let shard_id_label = witness.chunk_header.shard_id().to_string(); - let encode_timer = metrics::CHUNK_STATE_WITNESS_ENCODE_TIME + let encode_timer = near_chain::stateless_validation::metrics::CHUNK_STATE_WITNESS_ENCODE_TIME .with_label_values(&[shard_id_label.as_str()]) .start_timer(); let (witness_bytes, raw_witness_size) = EncodedChunkStateWitness::encode(&witness)?; encode_timer.observe_duration(); - metrics::record_witness_size_metrics(raw_witness_size, witness_bytes.size_bytes(), witness); + near_chain::stateless_validation::metrics::record_witness_size_metrics( + raw_witness_size, + witness_bytes.size_bytes(), + witness, + ); Ok(witness_bytes) } diff --git a/chain/client/src/stateless_validation/partial_witness/partial_witness_tracker.rs b/chain/client/src/stateless_validation/partial_witness/partial_witness_tracker.rs index 7cda6b49e09..488cdf2cfbf 100644 --- a/chain/client/src/stateless_validation/partial_witness/partial_witness_tracker.rs +++ b/chain/client/src/stateless_validation/partial_witness/partial_witness_tracker.rs @@ -1,109 +1,71 @@ -use std::collections::HashMap; +use std::num::NonZeroUsize; use std::sync::Arc; use lru::LruCache; use near_async::messaging::CanSend; -use near_async::time::{Duration, Instant}; +use near_async::time::Instant; use near_chain::chain::ChunkStateWitnessMessage; use near_chain::Error; use near_epoch_manager::EpochManagerAdapter; -use near_primitives::reed_solomon::reed_solomon_decode; +use near_o11y::log_assert_fail; use near_primitives::stateless_validation::{ - ChunkProductionKey, EncodedChunkStateWitness, PartialEncodedStateWitness, + ChunkProductionKey, ChunkStateWitness, ChunkStateWitnessSize, EncodedChunkStateWitness, + PartialEncodedStateWitness, }; -use near_primitives::types::ShardId; -use reed_solomon_erasure::galois_8::ReedSolomon; use time::ext::InstantExt as _; use crate::client_actor::ClientSenderForPartialWitness; use crate::metrics; +use super::encoding::{WitnessEncoder, WitnessEncoderCache, WitnessPart}; + /// Max number of chunks to keep in the witness tracker cache. We reach here only after validation /// of the partial_witness so the LRU cache size need not be too large. -const NUM_CHUNKS_IN_WITNESS_TRACKER_CACHE: usize = 200; - -/// Ratio of the number of data parts to total parts in the Reed Solomon encoding. -/// The tradeoff here is having a higher ratio is better for handling missing parts and network errors -/// but increases the size of the encoded state witness and the total network bandwidth requirements. -const RATIO_DATA_PARTS: f32 = 0.8; - -/// Reed Solomon encoder for encoding state witness parts. -/// We keep one encoder for each length of chunk_validators to avoid re-creating the encoder. -/// This is used by `PartialEncodedStateWitnessTracker` -/// Note that ReedSolomon encoder does not support having exactly 1 total part count and no parity parts. -/// In such cases, we use a dummy encoder with None value. -pub struct RsMap { - rs_map: HashMap>>, -} - -impl RsMap { - pub fn new() -> Self { - let mut rs_map = HashMap::new(); - rs_map.insert(1, Arc::new(None)); - Self { rs_map } - } +/// This effectively limits memory usage to the size of the cache multiplied by +/// MAX_COMPRESSED_STATE_WITNESS_SIZE, currently 40 * 48MiB = 1920MiB. +const WITNESS_PARTS_CACHE_SIZE: usize = 40; - pub fn entry(&mut self, total_parts: usize) -> Arc> { - self.rs_map - .entry(total_parts) - .or_insert_with(|| { - let data_parts = std::cmp::max((total_parts as f32 * RATIO_DATA_PARTS) as usize, 1); - Arc::new(Some(ReedSolomon::new(data_parts, total_parts - data_parts).unwrap())) - }) - .clone() - } -} +/// Number of entries to keep in LRU cache of the processed state witnesses +/// We only store small amount of data (ChunkProductionKey) per entry there, +/// so we don't have to worry much about memory usage here. +const PROCESSED_WITNESSES_CACHE_SIZE: usize = 200; struct CacheEntry { - pub is_decoded: bool, - pub shard_id: ShardId, - pub timer: Instant, - pub duration_to_last_part: Duration, + pub created_at: Instant, pub data_parts_present: usize, - pub data_parts_required: usize, - pub parts: Vec>>, - pub rs: Arc>, + pub parts: Vec, + pub encoder: Arc, pub total_parts_size: usize, } impl CacheEntry { - pub fn new(rs: Arc>) -> Self { - let (data_parts, total_parts) = match rs.as_ref() { - Some(rs) => (rs.data_shard_count(), rs.total_shard_count()), - None => (1, 1), - }; + pub fn new(encoder: Arc) -> Self { Self { - is_decoded: false, - shard_id: 0, // Dummy value - timer: Instant::now(), - duration_to_last_part: Duration::seconds(0), + created_at: Instant::now(), data_parts_present: 0, - data_parts_required: data_parts, - parts: vec![None; total_parts], + parts: vec![None; encoder.total_parts()], total_parts_size: 0, - rs, + encoder, } } + fn data_parts_required(&self) -> usize { + self.encoder.data_parts() + } + // Function to insert a part into the cache entry for the chunk hash. Additionally, it tries to // decode and return the state witness if all parts are present. pub fn insert_in_cache_entry( &mut self, partial_witness: PartialEncodedStateWitness, - ) -> Option { + ) -> Option> { let shard_id = partial_witness.shard_id(); let height_created = partial_witness.height_created(); let (part_ord, part, encoded_length) = partial_witness.decompose(); // Check if the part is already present. if self.parts[part_ord].is_some() { - tracing::warn!( - target: "client", - ?shard_id, - ?height_created, - ?part_ord, - "Received duplicate or redundant partial state witness part." - ); + log_assert_fail!("Received duplicate or redundant partial state witness part. shard_id={shard_id:?}, height_created={height_created:?}, part_ord={part_ord:?}"); return None; } @@ -112,45 +74,14 @@ impl CacheEntry { self.data_parts_present += 1; self.total_parts_size += part.len(); self.parts[part_ord] = Some(part); - self.shard_id = shard_id; - self.duration_to_last_part = Instant::now().signed_duration_since(self.timer); - - // Check if we have already decoded the state witness. - if self.is_decoded { - return None; - } // If we have enough parts, try to decode the state witness. - if self.data_parts_present < self.data_parts_required { + if self.data_parts_present < self.data_parts_required() { return None; } - // For the case when we are the only validator for the chunk, we don't need to do Reed Solomon encoding. - let decode_result = match self.rs.as_ref() { - Some(rs) => reed_solomon_decode(rs, &mut self.parts, encoded_length), - None => Ok(EncodedChunkStateWitness::from_boxed_slice( - self.parts[0].as_ref().unwrap().clone(), - )), - }; - - match decode_result { - Ok(encoded_chunk_state_witness) => { - self.is_decoded = true; - Some(encoded_chunk_state_witness) - } - Err(err) => { - // We ideally never expect the decoding to fail. In case it does, we received a bad part - // from the chunk producer. - tracing::error!( - target: "client", - ?err, - ?shard_id, - ?height_created, - "Failed to reed solomon decode witness parts. Maybe malicious or corrupt data." - ); - None - } - } + let decode_result = self.encoder.decode(&mut self.parts, encoded_length); + Some(decode_result) } } @@ -164,8 +95,12 @@ pub struct PartialEncodedStateWitnessTracker { epoch_manager: Arc, /// Keeps track of state witness parts received from chunk producers. parts_cache: LruCache, + /// Keeps track of the already decoded witnesses. This is needed + /// to protect chunk validator from processing the same witness multiple + /// times. + processed_witnesses: LruCache, /// Reed Solomon encoder for decoding state witness parts. - rs_map: RsMap, + encoders: WitnessEncoderCache, } impl PartialEncodedStateWitnessTracker { @@ -176,8 +111,11 @@ impl PartialEncodedStateWitnessTracker { Self { client_sender, epoch_manager, - parts_cache: LruCache::new(NUM_CHUNKS_IN_WITNESS_TRACKER_CACHE), - rs_map: RsMap::new(), + parts_cache: LruCache::new(NonZeroUsize::new(WITNESS_PARTS_CACHE_SIZE).unwrap()), + processed_witnesses: LruCache::new( + NonZeroUsize::new(PROCESSED_WITNESSES_CACHE_SIZE).unwrap(), + ), + encoders: WitnessEncoderCache::new(), } } @@ -187,20 +125,58 @@ impl PartialEncodedStateWitnessTracker { ) -> Result<(), Error> { tracing::debug!(target: "client", ?partial_witness, "store_partial_encoded_state_witness"); - self.maybe_insert_new_entry_in_parts_cache(&partial_witness)?; - let key = partial_witness.chunk_production_key(); - let entry = self.parts_cache.get_mut(&key).unwrap(); + if self.processed_witnesses.contains(&key) { + tracing::debug!( + target: "client", + ?partial_witness, + "Received redundant part for already processed witness" + ); + return Ok(()); + } - if let Some(encoded_witness) = entry.insert_in_cache_entry(partial_witness) { - tracing::debug!(target: "client", ?key, "Sending encoded witness to client."); + self.maybe_insert_new_entry_in_parts_cache(&partial_witness)?; + let entry = self.parts_cache.get_mut(&key).unwrap(); + if let Some(decode_result) = entry.insert_in_cache_entry(partial_witness) { // Record the time taken from receiving first part to decoding partial witness. - metrics::PARTIAL_WITNESS_DECODE_TIME - .with_label_values(&[entry.shard_id.to_string().as_str()]) - .observe(entry.duration_to_last_part.as_seconds_f64()); + let time_to_last_part = Instant::now().signed_duration_since(entry.created_at); + metrics::PARTIAL_WITNESS_TIME_TO_LAST_PART + .with_label_values(&[key.shard_id.to_string().as_str()]) + .observe(time_to_last_part.as_seconds_f64()); + + self.parts_cache.pop(&key); + self.processed_witnesses.push(key.clone(), ()); + + let encoded_witness = match decode_result { + Ok(encoded_chunk_state_witness) => encoded_chunk_state_witness, + Err(err) => { + // We ideally never expect the decoding to fail. In case it does, we received a bad part + // from the chunk producer. + tracing::error!( + target: "client", + ?err, + shard_id = key.shard_id, + height_created = key.height_created, + "Failed to reed solomon decode witness parts. Maybe malicious or corrupt data." + ); + return Err(Error::InvalidPartialChunkStateWitness(format!( + "Failed to reed solomon decode witness parts: {err}", + ))); + } + }; + + let (witness, raw_witness_size) = self.decode_state_witness(&encoded_witness)?; + if witness.chunk_production_key() != key { + return Err(Error::InvalidPartialChunkStateWitness(format!( + "Decoded witness key {:?} doesn't match partial witness {:?}", + witness.chunk_production_key(), + key, + ))); + } - self.client_sender.send(ChunkStateWitnessMessage(encoded_witness)); + tracing::debug!(target: "client", ?key, "Sending encoded witness to client."); + self.client_sender.send(ChunkStateWitnessMessage { witness, raw_witness_size }); } self.record_total_parts_cache_size_metric(); Ok(()) @@ -230,32 +206,17 @@ impl PartialEncodedStateWitnessTracker { return Ok(()); } let num_parts = self.get_num_parts(&partial_witness)?; - let rs = self.rs_map.entry(num_parts); - let new_entry = CacheEntry::new(rs); - if let Some((evicted_chunk_hash, evicted_entry)) = self.parts_cache.push(key, new_entry) { - // Record the ratio of parts received to parts required for the evicted entry. - // Note that this includes the parts received after decoding the state witness. - let parts_received_ratio = - evicted_entry.data_parts_present as f64 / evicted_entry.data_parts_required as f64; - metrics::PARTIAL_WITNESS_PARTS_RECEIVED_RATIO - .with_label_values(&[evicted_entry.shard_id.to_string().as_str()]) - .observe(parts_received_ratio); - - // Record the time taken from receiving first part to receiving the last part. - metrics::PARTIAL_WITNESS_TOTAL_TIME - .with_label_values(&[evicted_entry.shard_id.to_string().as_str()]) - .observe(evicted_entry.duration_to_last_part.as_seconds_f64()); - - // Check if the evicted entry has been fully decoded and processed. - if !evicted_entry.is_decoded { - tracing::warn!( - target: "client", - ?evicted_chunk_hash, - data_parts_present = ?evicted_entry.data_parts_present, - data_parts_required = ?evicted_entry.data_parts_required, - "Evicted unprocessed partial state witness." - ); - } + let protocol_version = + self.epoch_manager.get_epoch_protocol_version(partial_witness.epoch_id())?; + let new_entry = CacheEntry::new(self.encoders.entry(num_parts, protocol_version)); + if let Some((evicted_key, evicted_entry)) = self.parts_cache.push(key, new_entry) { + tracing::warn!( + target: "client", + ?evicted_key, + data_parts_present = ?evicted_entry.data_parts_present, + data_parts_required = ?evicted_entry.data_parts_required(), + "Evicted unprocessed partial state witness." + ); } Ok(()) } @@ -265,4 +226,21 @@ impl PartialEncodedStateWitnessTracker { self.parts_cache.iter().map(|(_, entry)| entry.total_parts_size).sum(); metrics::PARTIAL_WITNESS_CACHE_SIZE.set(total_size as f64); } + + fn decode_state_witness( + &self, + encoded_witness: &EncodedChunkStateWitness, + ) -> Result<(ChunkStateWitness, ChunkStateWitnessSize), Error> { + let decode_start = std::time::Instant::now(); + let (witness, raw_witness_size) = encoded_witness.decode()?; + let decode_elapsed_seconds = decode_start.elapsed().as_secs_f64(); + let witness_shard = witness.chunk_header.shard_id(); + + // Record metrics after validating the witness + near_chain::stateless_validation::metrics::CHUNK_STATE_WITNESS_DECODE_TIME + .with_label_values(&[&witness_shard.to_string()]) + .observe(decode_elapsed_seconds); + + Ok((witness, raw_witness_size)) + } } diff --git a/chain/client/src/stateless_validation/shadow_validate.rs b/chain/client/src/stateless_validation/shadow_validate.rs index 9c9af834788..d5c240f8a07 100644 --- a/chain/client/src/stateless_validation/shadow_validate.rs +++ b/chain/client/src/stateless_validation/shadow_validate.rs @@ -1,16 +1,10 @@ -use std::time::Instant; - +use near_chain::stateless_validation::chunk_validation::validate_prepared_transactions; use near_chain::types::{RuntimeStorageConfig, StorageDataSource}; use near_chain::{Block, BlockHeader}; use near_chain_primitives::Error; use near_primitives::sharding::{ShardChunk, ShardChunkHeader}; -use near_primitives::stateless_validation::EncodedChunkStateWitness; -use crate::stateless_validation::chunk_validator::{ - pre_validate_chunk_state_witness, validate_chunk_state_witness, validate_prepared_transactions, - MainStateTransitionCache, -}; -use crate::{metrics, Client}; +use crate::Client; impl Client { // Temporary feature to make node produce state witness for every chunk in every processed block @@ -31,7 +25,8 @@ impl Client { if let Err(err) = self.shadow_validate_chunk(prev_block.header(), prev_chunk_header, &chunk) { - metrics::SHADOW_CHUNK_VALIDATION_FAILED_TOTAL.inc(); + near_chain::stateless_validation::metrics::SHADOW_CHUNK_VALIDATION_FAILED_TOTAL + .inc(); tracing::error!( target: "client", ?err, @@ -50,8 +45,6 @@ impl Client { prev_chunk_header: &ShardChunkHeader, chunk: &ShardChunk, ) -> Result<(), Error> { - let shard_id = chunk.shard_id(); - let chunk_hash = chunk.chunk_hash(); let chunk_header = chunk.cloned_header(); let last_chunk = self.chain.get_chunk(&prev_chunk_header.chunk_hash())?; @@ -88,73 +81,12 @@ impl Client { if self.config.save_latest_witnesses { self.chain.chain_store.save_latest_chunk_state_witness(&witness)?; } - let (encoded_witness, raw_witness_size) = { - let shard_id_label = shard_id.to_string(); - let encode_timer = metrics::CHUNK_STATE_WITNESS_ENCODE_TIME - .with_label_values(&[shard_id_label.as_str()]) - .start_timer(); - let (encoded_witness, raw_witness_size) = EncodedChunkStateWitness::encode(&witness)?; - encode_timer.observe_duration(); - metrics::record_witness_size_metrics( - raw_witness_size, - encoded_witness.size_bytes(), - &witness, - ); - let decode_timer = metrics::CHUNK_STATE_WITNESS_DECODE_TIME - .with_label_values(&[shard_id_label.as_str()]) - .start_timer(); - encoded_witness.decode()?; - decode_timer.observe_duration(); - (encoded_witness, raw_witness_size) - }; - let pre_validation_start = Instant::now(); - let pre_validation_result = pre_validate_chunk_state_witness( - &witness, - &self.chain, + self.chain.shadow_validate_state_witness( + witness, self.epoch_manager.as_ref(), self.runtime_adapter.as_ref(), + None, )?; - tracing::debug!( - target: "client", - shard_id, - ?chunk_hash, - witness_size = encoded_witness.size_bytes(), - raw_witness_size, - pre_validation_elapsed = ?pre_validation_start.elapsed(), - "completed shadow chunk pre-validation" - ); - let epoch_manager = self.epoch_manager.clone(); - let runtime_adapter = self.runtime_adapter.clone(); - rayon::spawn(move || { - let validation_start = Instant::now(); - match validate_chunk_state_witness( - witness, - pre_validation_result, - epoch_manager.as_ref(), - runtime_adapter.as_ref(), - &MainStateTransitionCache::default(), - ) { - Ok(()) => { - tracing::debug!( - target: "client", - shard_id, - ?chunk_hash, - validation_elapsed = ?validation_start.elapsed(), - "completed shadow chunk validation" - ); - } - Err(err) => { - metrics::SHADOW_CHUNK_VALIDATION_FAILED_TOTAL.inc(); - tracing::error!( - target: "client", - ?err, - shard_id, - ?chunk_hash, - "shadow chunk validation failed" - ); - } - } - }); Ok(()) } } diff --git a/chain/client/src/stateless_validation/state_witness_producer.rs b/chain/client/src/stateless_validation/state_witness_producer.rs index 6876472123a..f7f7796a2a3 100644 --- a/chain/client/src/stateless_validation/state_witness_producer.rs +++ b/chain/client/src/stateless_validation/state_witness_producer.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::sync::Arc; use near_async::messaging::{CanSend, IntoSender}; use near_chain::{BlockHeader, Chain, ChainStoreAccess}; @@ -13,6 +14,7 @@ use near_primitives::stateless_validation::{ ChunkStateTransition, ChunkStateWitness, StoredChunkStateTransitionData, }; use near_primitives::types::{AccountId, EpochId}; +use near_primitives::validator_signer::ValidatorSigner; use crate::stateless_validation::chunk_validator::send_chunk_endorsement_to_block_producers; use crate::Client; @@ -29,6 +31,7 @@ impl Client { prev_chunk_header: &ShardChunkHeader, chunk: &ShardChunk, transactions_storage_proof: Option, + validator_signer: &Option>, ) -> Result<(), Error> { let protocol_version = self.epoch_manager.get_epoch_protocol_version(epoch_id)?; if !checked_feature!("stable", StatelessValidationV0, protocol_version) { @@ -38,7 +41,8 @@ impl Client { let shard_id = chunk_header.shard_id(); let _span = tracing::debug_span!(target: "client", "send_chunk_state_witness", chunk_hash=?chunk_header.chunk_hash(), ?shard_id).entered(); - let my_signer = self.validator_signer.as_ref().ok_or(Error::NotAValidator)?.clone(); + let my_signer = + validator_signer.as_ref().ok_or(Error::NotAValidator(format!("send state witness")))?; let state_witness = self.create_state_witness( my_signer.validator_id().clone(), prev_block_header, @@ -69,7 +73,7 @@ impl Client { } self.partial_witness_adapter.send(DistributeStateWitnessRequest { - epoch_id: epoch_id.clone(), + epoch_id: *epoch_id, chunk_header, state_witness, }); diff --git a/chain/client/src/stateless_validation/state_witness_tracker.rs b/chain/client/src/stateless_validation/state_witness_tracker.rs index 4e65dc68b0b..05c5529484b 100644 --- a/chain/client/src/stateless_validation/state_witness_tracker.rs +++ b/chain/client/src/stateless_validation/state_witness_tracker.rs @@ -4,9 +4,10 @@ use bytesize::ByteSize; use lru::LruCache; use near_async::time::Clock; use near_primitives::sharding::ChunkHash; -use near_primitives::stateless_validation::{ChunkStateWitness, ChunkStateWitnessAck}; +use near_primitives::stateless_validation::ChunkStateWitnessAck; use s3::creds::time::ext::InstantExt as _; use std::hash::Hash; +use std::num::NonZeroUsize; /// Limit to the number of witnesses tracked. /// @@ -24,8 +25,8 @@ struct ChunkStateWitnessKey { } impl ChunkStateWitnessKey { - pub fn new(witness: &ChunkStateWitness) -> Self { - Self { chunk_hash: witness.chunk_header.chunk_hash() } + pub fn new(chunk_hash: ChunkHash) -> Self { + Self { chunk_hash } } } @@ -51,17 +52,22 @@ pub struct ChunkStateWitnessTracker { impl ChunkStateWitnessTracker { pub fn new(clock: Clock) -> Self { - Self { witnesses: LruCache::new(CHUNK_STATE_WITNESS_MAX_RECORD_COUNT), clock } + Self { + witnesses: LruCache::new( + NonZeroUsize::new(CHUNK_STATE_WITNESS_MAX_RECORD_COUNT).unwrap(), + ), + clock, + } } /// Adds a new witness message to track. pub fn record_witness_sent( &mut self, - witness: &ChunkStateWitness, + chunk_hash: ChunkHash, witness_size_in_bytes: usize, num_validators: usize, ) -> () { - let key = ChunkStateWitnessKey::new(witness); + let key = ChunkStateWitnessKey::new(chunk_hash); tracing::trace!(target: "state_witness_tracker", witness_key=?key, size=witness_size_in_bytes, "Recording state witness sent."); self.witnesses.put( @@ -111,9 +117,9 @@ impl ChunkStateWitnessTracker { #[cfg(test)] fn get_record_for_witness( &mut self, - witness: &ChunkStateWitness, + witness: &near_primitives::stateless_validation::ChunkStateWitness, ) -> Option<&ChunkStateWitnessRecord> { - let key = ChunkStateWitnessKey::new(witness); + let key = ChunkStateWitnessKey::new(witness.chunk_header.chunk_hash()); self.witnesses.get(&key) } } @@ -147,6 +153,7 @@ mod state_witness_tracker_tests { use super::*; use near_async::time::{Duration, FakeClock, Utc}; use near_primitives::hash::hash; + use near_primitives::stateless_validation::ChunkStateWitness; use near_primitives::types::ShardId; const NUM_VALIDATORS: usize = 3; @@ -157,7 +164,7 @@ mod state_witness_tracker_tests { let clock = dummy_clock(); let mut tracker = ChunkStateWitnessTracker::new(clock.clock()); - tracker.record_witness_sent(&witness, 4321, NUM_VALIDATORS); + tracker.record_witness_sent(witness.chunk_header.compute_hash(), 4321, NUM_VALIDATORS); clock.advance(Duration::milliseconds(3444)); // Ack received from all "except for one". @@ -176,7 +183,7 @@ mod state_witness_tracker_tests { let clock = dummy_clock(); let mut tracker = ChunkStateWitnessTracker::new(clock.clock()); - tracker.record_witness_sent(&witness, 4321, NUM_VALIDATORS); + tracker.record_witness_sent(witness.chunk_header.compute_hash(), 4321, NUM_VALIDATORS); clock.advance(Duration::milliseconds(3444)); // Ack received from all. diff --git a/chain/client/src/sync/adapter.rs b/chain/client/src/sync/adapter.rs index 5685910cf95..0eb9ffdd14f 100644 --- a/chain/client/src/sync/adapter.rs +++ b/chain/client/src/sync/adapter.rs @@ -1,8 +1,8 @@ use super::sync_actor::SyncActor; -use actix::{Actor, Message}; -use actix_rt::Arbiter; +use actix::Message; use core::fmt::Debug; use near_async::actix::AddrWithAutoSpanContextExt; +use near_async::actix_wrapper::spawn_actix_actor; use near_async::messaging::{IntoSender, Sender}; use near_network::types::{PeerManagerMessageRequest, StateSyncResponse}; use near_primitives::hash::CryptoHash; @@ -81,14 +81,11 @@ impl SyncAdapter { + Sync, > { Arc::new(|shard_uid, client_sender, network_sender| { - let arbiter = Arbiter::new(); - let arbiter_handle = arbiter.handle(); - let sync_actor = SyncActor::start_in_arbiter(&arbiter_handle, move |_ctx| { - SyncActor::new(shard_uid, client_sender, network_sender) - }); + let (sync_actor_addr, arbiter) = + spawn_actix_actor(SyncActor::new(shard_uid, client_sender, network_sender)); SyncActorHandler { - client_sender: sync_actor.clone().with_auto_span_context().into_sender(), - network_sender: sync_actor.with_auto_span_context().into_sender(), + client_sender: sync_actor_addr.clone().with_auto_span_context().into_sender(), + network_sender: sync_actor_addr.with_auto_span_context().into_sender(), shutdown: Mutex::new(Box::new(move || { arbiter.stop(); })), diff --git a/chain/client/src/sync/block.rs b/chain/client/src/sync/block.rs index fc22b431e58..c787c609f8d 100644 --- a/chain/client/src/sync/block.rs +++ b/chain/client/src/sync/block.rs @@ -444,7 +444,7 @@ mod test { let requested_block_hashes = collect_hashes_from_network_adapter(&network_adapter); assert!(requested_block_hashes.is_empty(), "{:?}", requested_block_hashes); - // Now finish paused processing processing and sanity check that we + // Now finish paused processing and sanity check that we // still are fully synced. env.resume_block_processing(blocks[4 * MAX_BLOCK_REQUESTS - 1].hash()); wait_for_all_blocks_in_processing(&mut env.clients[1].chain); diff --git a/chain/client/src/sync/epoch.rs b/chain/client/src/sync/epoch.rs index eff2e9e47c7..1af495d4d1e 100644 --- a/chain/client/src/sync/epoch.rs +++ b/chain/client/src/sync/epoch.rs @@ -64,7 +64,7 @@ impl EpochSync { network_adapter, peer_to_last_request_time: HashMap::new(), peers_reporting_up_to_date: HashSet::new(), - current_epoch_id: genesis_epoch_id.clone(), + current_epoch_id: genesis_epoch_id, next_epoch_id: genesis_next_epoch_id, next_block_producers: first_epoch_block_producers, requested_epoch_id: genesis_epoch_id, diff --git a/chain/client/src/sync/header.rs b/chain/client/src/sync/header.rs index e82e3d5ef40..84ebf2deb74 100644 --- a/chain/client/src/sync/header.rs +++ b/chain/client/src/sync/header.rs @@ -767,12 +767,9 @@ mod test { let last_block = chain2.get_block(&chain2.head().unwrap().last_block_hash).unwrap(); let this_height = last_block.header().height() + 1; let (epoch_id, next_epoch_id) = if last_block.header().is_genesis() { - (last_block.header().next_epoch_id().clone(), EpochId(*last_block.hash())) + (*last_block.header().next_epoch_id(), EpochId(*last_block.hash())) } else { - ( - last_block.header().epoch_id().clone(), - last_block.header().next_epoch_id().clone(), - ) + (*last_block.header().epoch_id(), *last_block.header().next_epoch_id()) }; let block = Block::produce( PROTOCOL_VERSION, @@ -808,7 +805,8 @@ mod test { signer2.as_ref(), *last_block.header().next_bp_hash(), block_merkle_tree.root(), - clock.now_utc(), + clock.clock(), + None, ); block_merkle_tree.insert(*block.hash()); chain2.process_block_header(block.header(), &mut Vec::new()).unwrap(); // just to validate diff --git a/chain/client/src/sync/state.rs b/chain/client/src/sync/state.rs index af1e1165721..78691476d9c 100644 --- a/chain/client/src/sync/state.rs +++ b/chain/client/src/sync/state.rs @@ -1,6 +1,6 @@ //! State sync is trying to fetch the 'full state' from the peers (which can be multiple GB). //! It happens after HeaderSync and before BlockSync (but only if the node sees that it is 'too much behind'). -//! See https://near.github.io/nearcore/architecture/how/sync.html for more detailed information. +//! See for more detailed information. //! Such state can be downloaded only at special heights (currently - at the beginning of the current and previous //! epochs). //! @@ -55,6 +55,7 @@ use near_store::DBCol; use rand::seq::SliceRandom; use rand::{thread_rng, Rng}; use std::collections::HashMap; +use std::num::NonZeroUsize; use std::ops::Add; use std::sync::atomic::Ordering; use std::sync::mpsc::{channel, Receiver, Sender}; @@ -171,7 +172,9 @@ impl StateSync { let inner = match sync_config { SyncConfig::Peers => StateSyncInner::Peers { last_part_id_requested: Default::default(), - requested_target: lru::LruCache::new(MAX_PENDING_PART as usize), + requested_target: lru::LruCache::new( + NonZeroUsize::new(MAX_PENDING_PART as usize).unwrap(), + ), }, SyncConfig::ExternalStorage(ExternalStorageConfig { location, @@ -247,8 +250,8 @@ impl StateSync { let mut all_done = true; let prev_hash = *chain.get_block_header(&sync_hash)?.prev_hash(); - let prev_epoch_id = chain.get_block_header(&prev_hash)?.epoch_id().clone(); - let epoch_id = chain.get_block_header(&sync_hash)?.epoch_id().clone(); + let prev_epoch_id = *chain.get_block_header(&prev_hash)?.epoch_id(); + let epoch_id = *chain.get_block_header(&sync_hash)?.epoch_id(); let prev_shard_layout = epoch_manager.get_shard_layout(&prev_epoch_id)?; let shard_layout = epoch_manager.get_shard_layout(&epoch_id)?; if prev_shard_layout != shard_layout { @@ -296,8 +299,8 @@ impl StateSync { state_parts_task_scheduler, )?; } - ShardSyncStatus::StateApplyComplete => { - self.sync_shards_apply_complete_status( + ShardSyncStatus::StateApplyInProgress => { + self.sync_shards_apply_status( shard_id, shard_sync_download, sync_hash, @@ -479,7 +482,7 @@ impl StateSync { sync_hash: &CryptoHash, ) -> Result { let mut header = chain.get_block_header(sync_hash)?; - let mut epoch_id = header.epoch_id().clone(); + let mut epoch_id = *header.epoch_id(); let mut hash = *header.hash(); let mut prev_hash = *header.prev_hash(); loop { @@ -490,7 +493,7 @@ impl StateSync { if &epoch_id != header.epoch_id() { return Ok(hash); } - epoch_id = header.epoch_id().clone(); + epoch_id = *header.epoch_id(); hash = *header.hash(); prev_hash = *header.prev_hash(); } @@ -1001,7 +1004,7 @@ impl StateSync { Ok(()) => { *shard_sync_download = ShardSyncDownload { downloads: vec![], - status: ShardSyncStatus::StateApplyComplete, + status: ShardSyncStatus::StateApplyInProgress, } } Err(err) => { @@ -1016,7 +1019,7 @@ impl StateSync { Ok(()) } - fn sync_shards_apply_complete_status( + fn sync_shards_apply_status( &mut self, shard_id: ShardId, shard_sync_download: &mut ShardSyncDownload, @@ -1028,7 +1031,7 @@ impl StateSync { // (these are set via callback from ClientActor - both for sync and catchup). if let Some(result) = self.state_parts_apply_results.remove(&shard_id) { result?; - let epoch_id = chain.get_block_header(&sync_hash)?.epoch_id().clone(); + let epoch_id = *chain.get_block_header(&sync_hash)?.epoch_id(); let shard_uid = chain.epoch_manager.shard_id_to_uid(shard_id, &epoch_id)?; let shard_state_header = chain.get_state_header(shard_id, sync_hash)?; let chunk = shard_state_header.cloned_chunk(); @@ -1544,7 +1547,7 @@ mod test { let prev = chain.get_block(&chain.head().unwrap().last_block_hash).unwrap(); let block = if kv.is_next_block_epoch_start(prev.hash()).unwrap() { TestBlockBuilder::new(Clock::real(), &prev, signer.clone()) - .epoch_id(prev.header().next_epoch_id().clone()) + .epoch_id(*prev.header().next_epoch_id()) .next_epoch_id(EpochId { 0: *prev.hash() }) .next_bp_hash(*prev.header().next_bp_hash()) .build() diff --git a/chain/client/src/sync/sync_actor.rs b/chain/client/src/sync/sync_actor.rs index 3e127a0654d..9e6670fa331 100644 --- a/chain/client/src/sync/sync_actor.rs +++ b/chain/client/src/sync/sync_actor.rs @@ -1,7 +1,6 @@ use super::adapter::{SyncMessage as ClientSyncMessage, SyncShardInfo}; -use near_async::messaging::Sender; +use near_async::messaging::{Actor, Handler, Sender}; use near_network::types::{PeerManagerMessageRequest, StateSyncResponse}; -use near_o11y::{handler_debug_span, WithSpanContext}; use near_performance_metrics_macros::perf; use near_primitives::hash::CryptoHash; use near_store::ShardUId; @@ -71,43 +70,20 @@ impl SyncActor { } } -/// Control the flow of the state sync actor -impl actix::Actor for SyncActor { - type Context = actix::Context; - - fn started(&mut self, _ctx: &mut Self::Context) { - info!(target: "sync", shard_id = ?self.shard_uid.shard_id, "Sync actor started."); - } - - fn stopped(&mut self, _ctx: &mut Self::Context) { - info!(target: "sync", shard_id = ?self.shard_uid.shard_id, "Sync actor stopped."); - } -} +impl Actor for SyncActor {} /// Process messages from client -impl actix::Handler> for SyncActor { - type Result = (); +impl Handler for SyncActor { #[perf] - fn handle( - &mut self, - msg: WithSpanContext, - _ctx: &mut Self::Context, - ) -> Self::Result { - let (_span, msg) = handler_debug_span!(target: "sync", msg); + fn handle(&mut self, msg: ClientSyncMessage) { self.handle_client_sync_message(msg); } } /// Process messages from network -impl actix::Handler> for SyncActor { - type Result = (); +impl Handler for SyncActor { #[perf] - fn handle( - &mut self, - msg: WithSpanContext, - _ctx: &mut Self::Context, - ) -> Self::Result { - let (_span, msg) = handler_debug_span!(target: "sync", msg); + fn handle(&mut self, msg: StateSyncResponse) { self.handle_network_sync_message(msg); } } diff --git a/chain/client/src/sync_jobs_actor.rs b/chain/client/src/sync_jobs_actor.rs index 987cf9fb62c..a31f08a37a2 100644 --- a/chain/client/src/sync_jobs_actor.rs +++ b/chain/client/src/sync_jobs_actor.rs @@ -3,7 +3,7 @@ use near_async::actix_wrapper::ActixWrapper; use near_async::futures::{DelayedActionRunner, DelayedActionRunnerExt}; use near_async::messaging::{self, CanSend, Handler, HandlerWithContext, Sender}; use near_async::time::Duration; -use near_async::{MultiSend, MultiSendMessage, MultiSenderFrom}; +use near_async::{MultiSend, MultiSenderFrom}; use near_chain::chain::{ do_apply_chunks, ApplyStatePartsRequest, ApplyStatePartsResponse, BlockCatchUpRequest, BlockCatchUpResponse, LoadMemtrieRequest, LoadMemtrieResponse, @@ -19,8 +19,7 @@ use near_store::DBCol; // Set the mailbox capacity for the SyncJobsActor from default 16 to 100. const MAILBOX_CAPACITY: usize = 100; -#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] +#[derive(Clone, MultiSend, MultiSenderFrom)] pub struct ClientSenderForSyncJobs { apply_state_parts_response: Sender, block_catch_up_response: Sender, diff --git a/chain/client/src/test_utils/client.rs b/chain/client/src/test_utils/client.rs index 00139e55ffc..f65f31106e1 100644 --- a/chain/client/src/test_utils/client.rs +++ b/chain/client/src/test_utils/client.rs @@ -42,11 +42,16 @@ impl Client { block: MaybeValidated, provenance: Provenance, should_produce_chunk: bool, + allow_errors: bool, ) -> Result, near_chain::Error> { - self.start_process_block(block, provenance, None)?; + let signer = self.validator_signer.get(); + self.start_process_block(block, provenance, None, &signer)?; wait_for_all_blocks_in_processing(&mut self.chain); - let (accepted_blocks, errors) = self.postprocess_ready_blocks(None, should_produce_chunk); - assert!(errors.is_empty(), "unexpected errors when processing blocks: {errors:#?}"); + let (accepted_blocks, errors) = + self.postprocess_ready_blocks(None, should_produce_chunk, &signer); + if !allow_errors { + assert!(errors.is_empty(), "unexpected errors when processing blocks: {errors:#?}"); + } Ok(accepted_blocks) } @@ -55,7 +60,7 @@ impl Client { block: MaybeValidated, provenance: Provenance, ) -> Result, near_chain::Error> { - self.process_block_sync_with_produce_chunk_options(block, provenance, true) + self.process_block_sync_with_produce_chunk_options(block, provenance, true, false) } pub fn process_block_test_no_produce_chunk( @@ -63,14 +68,23 @@ impl Client { block: MaybeValidated, provenance: Provenance, ) -> Result, near_chain::Error> { - self.process_block_sync_with_produce_chunk_options(block, provenance, false) + self.process_block_sync_with_produce_chunk_options(block, provenance, false, false) + } + + pub fn process_block_test_no_produce_chunk_allow_errors( + &mut self, + block: MaybeValidated, + provenance: Provenance, + ) -> Result, near_chain::Error> { + self.process_block_sync_with_produce_chunk_options(block, provenance, false, true) } /// This function finishes processing all blocks that started being processed. pub fn finish_blocks_in_processing(&mut self) -> Vec { + let signer = self.validator_signer.get(); let mut accepted_blocks = vec![]; while wait_for_all_blocks_in_processing(&mut self.chain) { - accepted_blocks.extend(self.postprocess_ready_blocks(None, true).0); + accepted_blocks.extend(self.postprocess_ready_blocks(None, true, &signer).0); } accepted_blocks } @@ -79,7 +93,8 @@ impl Client { /// has started. pub fn finish_block_in_processing(&mut self, hash: &CryptoHash) -> Vec { if let Ok(()) = wait_for_block_in_processing(&mut self.chain, hash) { - let (accepted_blocks, _) = self.postprocess_ready_blocks(None, true); + let signer = self.validator_signer.get(); + let (accepted_blocks, _) = self.postprocess_ready_blocks(None, true, &signer); return accepted_blocks; } vec![] @@ -93,12 +108,13 @@ impl Client { receipts, transactions_storage_proof, } = create_chunk_on_height_for_shard(self, height, shard_id); + let signer = self.validator_signer.get(); let shard_chunk = self .persist_and_distribute_encoded_chunk( encoded_chunk, merkle_paths, receipts, - self.validator_signer.as_ref().unwrap().validator_id().clone(), + signer.as_ref().unwrap().validator_id().clone(), ) .unwrap(); let prev_block = self.chain.get_block(shard_chunk.prev_block()).unwrap(); @@ -114,6 +130,7 @@ impl Client { &prev_chunk_header, &shard_chunk, transactions_storage_proof, + &signer, ) .unwrap(); shard_chunk @@ -127,6 +144,7 @@ fn create_chunk_on_height_for_shard( ) -> ProduceChunkResult { let last_block_hash = client.chain.head().unwrap().last_block_hash; let last_block = client.chain.get_block(&last_block_hash).unwrap(); + let signer = client.validator_signer.get(); client .try_produce_chunk( &last_block, @@ -135,6 +153,7 @@ fn create_chunk_on_height_for_shard( .unwrap(), next_height, shard_id, + signer.as_ref(), ) .unwrap() .unwrap() @@ -160,6 +179,7 @@ pub fn create_chunk( ) -> (ProduceChunkResult, Block) { let last_block = client.chain.get_block_by_height(client.chain.head().unwrap().height).unwrap(); let next_height = last_block.header().height() + 1; + let signer = client.validator_signer.get(); let ProduceChunkResult { mut chunk, encoded_chunk_parts_paths: mut merkle_paths, @@ -172,6 +192,7 @@ pub fn create_chunk( last_block.chunks()[0].clone(), next_height, 0, + signer.as_ref(), ) .unwrap() .unwrap(); @@ -190,7 +211,7 @@ pub fn create_chunk( let parity_parts = total_parts - data_parts; let rs = ReedSolomon::new(data_parts, parity_parts).unwrap(); - let signer = client.validator_signer.as_ref().unwrap().clone(); + let signer = signer.unwrap(); let header = chunk.cloned_header(); let (mut encoded_chunk, mut new_merkle_paths) = EncodedShardChunk::new( *header.prev_block_hash(), @@ -207,8 +228,7 @@ pub fn create_chunk( transactions, decoded_chunk.prev_outgoing_receipts(), header.prev_outgoing_receipts_root(), - // TODO(congestion_control): compute if not available - header.congestion_info().unwrap_or_default(), + header.congestion_info(), &*signer, PROTOCOL_VERSION, ) @@ -228,7 +248,7 @@ pub fn create_chunk( client.chain.chain_store().get_block_merkle_tree(last_block.hash()).unwrap(); let mut block_merkle_tree = PartialMerkleTree::clone(&block_merkle_tree); - let signer = client.validator_signer.as_ref().unwrap().clone(); + let signer = client.validator_signer.get().unwrap(); let endorsement = ChunkEndorsement::new(chunk.cloned_header().chunk_hash(), signer.as_ref()); block_merkle_tree.insert(*last_block.hash()); let block = Block::produce( @@ -239,8 +259,8 @@ pub fn create_chunk( last_block.header().block_ordinal() + 1, vec![chunk.cloned_header()], vec![vec![Some(Box::new(endorsement.signature))]], - last_block.header().epoch_id().clone(), - last_block.header().next_epoch_id().clone(), + *last_block.header().epoch_id(), + *last_block.header().next_epoch_id(), None, vec![], Ratio::new(0, 1), @@ -249,10 +269,11 @@ pub fn create_chunk( None, vec![], vec![], - &*client.validator_signer.as_ref().unwrap().clone(), + &*client.validator_signer.get().unwrap(), *last_block.header().next_bp_hash(), block_merkle_tree.root(), - client.clock.now_utc(), + client.clock.clone(), + None, ); ( ProduceChunkResult { @@ -286,6 +307,7 @@ pub fn run_catchup( let _ = System::new(); let state_parts_future_spawner = ActixArbiterHandleFutureSpawner(Arbiter::new().handle()); loop { + let signer = client.validator_signer.get(); client.run_catchup( highest_height_peers, &noop().into_sender(), @@ -294,6 +316,7 @@ pub fn run_catchup( &resharding, None, &state_parts_future_spawner, + &signer, )?; let mut catchup_done = true; for msg in block_messages.write().unwrap().drain(..) { diff --git a/chain/client/src/test_utils/setup.rs b/chain/client/src/test_utils/setup.rs index 05b933177cb..bee38ff2911 100644 --- a/chain/client/src/test_utils/setup.rs +++ b/chain/client/src/test_utils/setup.rs @@ -55,7 +55,7 @@ use near_primitives::hash::{hash, CryptoHash}; use near_primitives::network::PeerId; use near_primitives::test_utils::create_test_signer; use near_primitives::types::{AccountId, BlockHeightDelta, EpochId, NumBlocks, NumSeats}; -use near_primitives::validator_signer::ValidatorSigner; +use near_primitives::validator_signer::{EmptyValidatorSigner, ValidatorSigner}; use near_primitives::version::PROTOCOL_VERSION; use near_store::test_utils::create_test_store; use near_telemetry::TelemetryActor; @@ -90,7 +90,6 @@ pub fn setup( network_adapter: PeerManagerAdapter, transaction_validity_period: NumBlocks, genesis_time: Utc, - // ctx: &Context, chunk_distribution_config: Option, ) -> ( Addr, @@ -116,7 +115,10 @@ pub fn setup( protocol_version: PROTOCOL_VERSION, }; - let signer = Arc::new(create_test_signer(account_id.as_str())); + let signer = MutableConfigValue::new( + Some(Arc::new(create_test_signer(account_id.as_str()))), + "validator_signer", + ); let telemetry = ActixWrapper::new(TelemetryActor::default()).start(); let config = { let mut base = ClientConfig::test( @@ -137,7 +139,7 @@ pub fn setup( let view_client_addr = ViewClientActorInner::spawn_actix_actor( clock.clone(), - Some(signer.validator_id().clone()), + signer.clone(), chain_genesis.clone(), epoch_manager.clone(), shard_tracker.clone(), @@ -176,7 +178,7 @@ pub fn setup( state_sync_adapter, network_adapter.clone(), shards_manager_adapter_for_client.as_sender(), - Some(signer), + signer, telemetry.with_auto_span_context().into_sender(), None, None, @@ -186,13 +188,13 @@ pub fn setup( enable_doomslug, Some(TEST_SEED), ); - + let validator_signer = Some(Arc::new(EmptyValidatorSigner::new(account_id))); let (shards_manager_addr, _) = start_shards_manager( epoch_manager, shard_tracker, network_adapter.into_sender(), client_actor.clone().with_auto_span_context().into_sender(), - Some(account_id), + MutableConfigValue::new(validator_signer, "validator_signer"), store, config.chunk_request_retry_period, ); @@ -264,11 +266,14 @@ pub fn setup_only_view( }, None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); - let signer = Arc::new(create_test_signer(account_id.as_str())); + let signer = MutableConfigValue::new( + Some(Arc::new(create_test_signer(account_id.as_str()))), + "validator_signer", + ); ActixWrapper::new(TelemetryActor::default()).start(); let config = ClientConfig::test( skip_sync_wait, @@ -285,7 +290,7 @@ pub fn setup_only_view( ViewClientActorInner::spawn_actix_actor( clock, - Some(signer.validator_id().clone()), + signer, chain_genesis, epoch_manager, shard_tracker, @@ -657,7 +662,7 @@ fn process_peer_manager_message_default( } NetworkRequests::AnnounceAccount(announce_account) => { let mut aa = announced_accounts.write().unwrap(); - let key = (announce_account.account_id.clone(), announce_account.epoch_id.clone()); + let key = (announce_account.account_id.clone(), announce_account.epoch_id); if aa.get(&key).is_none() { aa.insert(key); for actor_handles in connectors { @@ -985,7 +990,7 @@ pub fn setup_client_with_runtime( save_trie_changes: bool, snapshot_callbacks: Option, partial_witness_adapter: PartialWitnessSenderForClient, - validator_signer: Arc, + validator_signer: Arc, ) -> Client { let mut config = ClientConfig::test( true, @@ -1013,7 +1018,7 @@ pub fn setup_client_with_runtime( runtime, network_adapter, shards_manager_adapter.into_sender(), - Some(validator_signer), + MutableConfigValue::new(Some(validator_signer), "validator_signer"), enable_doomslug, rng_seed, snapshot_callbacks, @@ -1057,14 +1062,15 @@ pub fn setup_synchronous_shards_manager( }, // irrelevant None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); let chain_head = chain.head().unwrap(); let chain_header_head = chain.header_head().unwrap(); + let validator_signer = account_id.map(|id| Arc::new(EmptyValidatorSigner::new(id))); let shards_manager = ShardsManagerActor::new( clock, - account_id, + MutableConfigValue::new(validator_signer, "validator_signer"), epoch_manager, shard_tracker, network_adapter.request_sender, diff --git a/chain/client/src/test_utils/test_env.rs b/chain/client/src/test_utils/test_env.rs index fcb3fafdf1a..fdff540c003 100644 --- a/chain/client/src/test_utils/test_env.rs +++ b/chain/client/src/test_utils/test_env.rs @@ -1,10 +1,10 @@ -use crate::stateless_validation::processing_tracker::{ - ProcessingDoneTracker, ProcessingDoneWaiter, -}; use crate::{Client, DistributeStateWitnessRequest}; use near_async::messaging::{CanSend, IntoMultiSender}; use near_async::time::Clock; use near_async::time::{Duration, Instant}; +use near_chain::stateless_validation::processing_tracker::{ + ProcessingDoneTracker, ProcessingDoneWaiter, +}; use near_chain::test_utils::ValidatorSchedule; use near_chain::types::Tip; use near_chain::{ChainGenesis, ChainStoreAccess, Provenance}; @@ -12,7 +12,7 @@ use near_chain_configs::GenesisConfig; use near_chain_primitives::error::QueryError; use near_chunks::client::ShardsManagerResponse; use near_chunks::test_utils::{MockClientAdapterForShardsManager, SynchronousShardsManagerAdapter}; -use near_crypto::{InMemorySigner, KeyType, Signer}; +use near_crypto::{InMemorySigner, KeyType}; use near_network::client::ProcessTxResponse; use near_network::shards_manager::ShardsManagerRequestFromNetwork; use near_network::test_utils::MockPeerManagerAdapter; @@ -27,7 +27,7 @@ use near_primitives::epoch_manager::RngSeed; use near_primitives::errors::InvalidTxError; use near_primitives::hash::CryptoHash; use near_primitives::sharding::{ChunkHash, PartialEncodedChunk}; -use near_primitives::stateless_validation::{ChunkEndorsement, EncodedChunkStateWitness}; +use near_primitives::stateless_validation::{ChunkEndorsement, ChunkStateWitness}; use near_primitives::test_utils::create_test_signer; use near_primitives::transaction::{Action, FunctionCallAction, SignedTransaction}; use near_primitives::types::{AccountId, Balance, BlockHeight, EpochId, NumSeats, ShardId}; @@ -297,7 +297,8 @@ impl TestEnv { while let Some(msg) = self.client_adapters[id].pop() { match msg { ShardsManagerResponse::ChunkCompleted { partial_chunk, shard_chunk } => { - self.clients[id].on_chunk_completed(partial_chunk, shard_chunk, None); + let signer = self.clients[id].validator_signer.get(); + self.clients[id].on_chunk_completed(partial_chunk, shard_chunk, None, &signer); } ShardsManagerResponse::InvalidChunk(encoded_chunk) => { self.clients[id].on_invalid_chunk(encoded_chunk); @@ -307,7 +308,6 @@ impl TestEnv { chunk_producer, } => { self.clients[id] - .chunk_inclusion_tracker .mark_chunk_header_ready_for_inclusion(chunk_header, chunk_producer); } } @@ -329,9 +329,8 @@ impl TestEnv { } fn found_differing_post_state_root_due_to_state_transitions( - encoded_witness: &EncodedChunkStateWitness, + witness: &ChunkStateWitness, ) -> bool { - let witness = encoded_witness.decode().unwrap().0; let mut post_state_roots = HashSet::from([witness.main_state_transition.post_state_root]); post_state_roots.extend(witness.implicit_transitions.iter().map(|t| t.post_state_root)); post_state_roots.len() >= 2 @@ -360,8 +359,8 @@ impl TestEnv { while let Some(request) = partial_witness_adapter.pop_distribution_request() { let DistributeStateWitnessRequest { epoch_id, chunk_header, state_witness } = request; - let (encoded_witness, _) = - EncodedChunkStateWitness::encode(&state_witness).unwrap(); + + let raw_witness_size = borsh::to_vec(&state_witness).unwrap().len(); let chunk_validators = self.clients[client_idx] .epoch_manager .get_chunk_validator_assignments( @@ -376,9 +375,12 @@ impl TestEnv { let processing_done_tracker = ProcessingDoneTracker::new(); witness_processing_done_waiters.push(processing_done_tracker.make_waiter()); - let processing_result = self.client(&account_id).process_chunk_state_witness( - encoded_witness.clone(), + let client = self.client(&account_id); + let processing_result = client.process_chunk_state_witness( + state_witness.clone(), + raw_witness_size, Some(processing_done_tracker), + client.validator_signer.get(), ); if !allow_errors { processing_result.unwrap(); @@ -387,9 +389,7 @@ impl TestEnv { // Update output. output.found_differing_post_state_root_due_to_state_transitions |= - Self::found_differing_post_state_root_due_to_state_transitions( - &encoded_witness, - ); + Self::found_differing_post_state_root_due_to_state_transitions(&state_witness); } } @@ -472,8 +472,8 @@ impl TestEnv { let tx = SignedTransaction::send_money( 1, account_id.clone(), - account_id.clone(), - &signer, + account_id, + &signer.into(), 100, self.clients[id].chain.head().unwrap().last_block_hash, ); @@ -481,7 +481,7 @@ impl TestEnv { } /// This function used to be able to upgrade to a specific protocol version - /// but due to https://github.com/near/nearcore/issues/8590 that + /// but due to that /// functionality does not work currently. Hence it is renamed to upgrade /// to the latest version. pub fn upgrade_protocol_to_latest_version(&mut self) { @@ -611,7 +611,7 @@ impl TestEnv { /// memory caches. /// Though, it seems that it is not necessary for current use cases. pub fn restart(&mut self, idx: usize) { - let account_id = self.get_client_id(idx).clone(); + let account_id = self.get_client_id(idx); let rng_seed = match self.seeds.get(&account_id) { Some(seed) => *seed, None => TEST_SEED, @@ -633,14 +633,15 @@ impl TestEnv { self.save_trie_changes, None, self.clients[idx].partial_witness_adapter.clone(), - self.clients[idx].validator_signer.clone().unwrap(), + self.clients[idx].validator_signer.get().unwrap(), ) } /// Returns an [`AccountId`] used by a client at given index. More /// specifically, returns validator id of the client’s validator signer. - pub fn get_client_id(&self, idx: usize) -> &AccountId { - self.clients[idx].validator_signer.as_ref().unwrap().validator_id() + pub fn get_client_id(&self, idx: usize) -> AccountId { + let validator_signer = self.clients[idx].validator_signer.get(); + validator_signer.unwrap().validator_id().clone() } /// Returns the index of client with the given [`AccoountId`]. @@ -695,7 +696,7 @@ impl TestEnv { tip.height + 1, signer.account_id.clone(), receiver, - signer, + &signer.clone().into(), actions, tip.last_block_hash, 0, @@ -734,7 +735,7 @@ impl TestEnv { relayer_nonce, relayer, sender, - &relayer_signer, + &relayer_signer.into(), vec![Action::Delegate(Box::new(signed_delegate_action))], tip.last_block_hash, 0, @@ -784,6 +785,37 @@ impl TestEnv { let tx = self.tx_from_actions(actions, &signer, signer.account_id.clone()); self.execute_tx(tx).unwrap() } + + /// Print a short summary of all the blocks from genesis to head. + pub fn print_summary(&self) { + let client = &self.clients[0]; + + let genesis_height = client.chain.genesis().height(); + let head_height = client.chain.head().unwrap().height; + + tracing::info!(target: "test", genesis_height, head_height, "printing summary"); + for height in genesis_height..head_height + 1 { + self.print_block_summary(height); + } + } + + pub fn print_block_summary(&self, height: u64) { + let client = &self.clients[0]; + let block = client.chain.get_block_by_height(height); + let Ok(block) = block else { + tracing::info!(target: "test", "Block {}: missing", height); + return; + }; + let prev_hash = block.header().prev_hash(); + let epoch_id = client.epoch_manager.get_epoch_id_from_prev_block(prev_hash).unwrap(); + let protocol_version = client.epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap(); + let latest_protocol_version = block.header().latest_protocol_version(); + + let block_hash = block.hash(); + let chunk_mask = block.header().chunk_mask(); + + tracing::info!(target: "test", height, ?block_hash, ?chunk_mask, protocol_version, latest_protocol_version, "block"); + } } impl Drop for TestEnv { diff --git a/chain/client/src/test_utils/test_loop.rs b/chain/client/src/test_utils/test_loop.rs index 3962fbc37ec..0dd8b8c7767 100644 --- a/chain/client/src/test_utils/test_loop.rs +++ b/chain/client/src/test_utils/test_loop.rs @@ -1,214 +1,19 @@ -pub mod client_actor; -pub mod partial_witness_actor; -pub mod sync_actor; -pub mod sync_jobs_actor; +use std::sync::{Arc, Mutex}; -use crate::client_actor::{ClientActorInner, ClientSenderForPartialWitnessMessage}; -use near_async::messaging::{CanSend, Handler, SendAsync}; -use near_async::test_loop::delay_sender::DelaySender; -use near_async::test_loop::event_handler::{LoopEventHandler, TryIntoOrSelf}; - -use near_async::time::Duration; - -use crate::Client; -use near_network::client::{ - BlockApproval, BlockResponse, ChunkEndorsementMessage, ClientSenderForNetwork, - ClientSenderForNetworkMessage, ProcessTxRequest, -}; -use near_network::state_witness::{ - ChunkStateWitnessAckMessage, PartialEncodedStateWitnessForwardMessage, - PartialEncodedStateWitnessMessage, PartialWitnessSenderForNetwork, - PartialWitnessSenderForNetworkMessage, -}; -use near_network::test_loop::SupportsRoutingLookup; -use near_network::types::{NetworkRequests, PeerManagerMessageRequest}; +use near_async::messaging::{IntoSender, LateBoundSender, Sender}; +use near_async::test_loop::data::TestLoopData; +use near_async::test_loop::pending_events_sender::PendingEventsSender; +use near_network::types::PeerManagerMessageRequest; use near_primitives::hash::CryptoHash; -use near_primitives::network::PeerId; use near_primitives::types::{AccountId, Balance, ShardId}; use near_primitives::views::{ FinalExecutionOutcomeView, QueryRequest, QueryResponse, QueryResponseKind, }; +use near_store::ShardUId; -pub fn print_basic_client_info_before_each_event( - idx: Option, -) -> LoopEventHandler -where - Data: AsRef, -{ - let idx_prefix = idx.map(|idx| format!("[Client #{}] ", idx)).unwrap_or_default(); - - LoopEventHandler::new(move |msg, data: &mut Data| { - let client = &data.as_ref().client; - tracing::info!("{}sync_status: {:?}", idx_prefix, client.sync_status); - let head = client.chain.head().unwrap(); - tracing::info!("{}Chain HEAD: {:?}", idx_prefix, head); - - if let Some(signer) = client.validator_signer.as_ref() { - let account_id = signer.validator_id(); - - let mut tracked_shards = Vec::new(); - let mut next_tracked_shards = Vec::new(); - let epoch_manager = client.epoch_manager.as_ref(); - for shard_id in &epoch_manager.shard_ids(&head.epoch_id).unwrap() { - let tracks_shard = client - .epoch_manager - .cares_about_shard_from_prev_block(&head.prev_block_hash, account_id, *shard_id) - .unwrap(); - if tracks_shard { - tracked_shards.push(*shard_id); - } - let next_tracks_shard = client - .epoch_manager - .cares_about_shard_next_epoch_from_prev_block( - &head.prev_block_hash, - account_id, - *shard_id, - ) - .unwrap(); - if next_tracks_shard { - next_tracked_shards.push(*shard_id); - } - } - tracing::info!( - "{}Validator assigned shards: this epoch = {:?}; next epoch = {:?}", - idx_prefix, - tracked_shards, - next_tracked_shards - ); - - tracing::info!("{}Tx pool: {}", idx_prefix, client.sharded_tx_pool.debug_status()); - } - Err(msg) - }) -} - -/// Handles outgoing network messages, and turns them into incoming client messages. -pub fn route_network_messages_to_client< - Data: SupportsRoutingLookup, - Event: TryIntoOrSelf - + From - + From - + From, ->( - sender: DelaySender<(usize, Event)>, - network_delay: Duration, -) -> LoopEventHandler { - // let mut route_back_lookup: HashMap = HashMap::new(); - // let mut next_hash: u64 = 0; - LoopEventHandler::new(move |event: (usize, Event), data: &mut Data| { - let (idx, event) = event; - let message = event.try_into_or_self().map_err(|event| (idx, event.into()))?; - let PeerManagerMessageRequest::NetworkRequests(request) = message else { - return Err((idx, message.into())); - }; - - let client_senders = (0..data.num_accounts()) - .map(|idx| { - sender - .with_additional_delay(network_delay) - .for_index(idx) - .into_wrapped_multi_sender::() - }) - .collect::>(); - - let state_witness_senders = (0..data.num_accounts()) - .map(|idx| { - sender - .with_additional_delay(network_delay) - .for_index(idx) - .into_wrapped_multi_sender::() - }) - .collect::>(); - - match request { - NetworkRequests::Block { block } => { - for other_idx in 0..data.num_accounts() { - if other_idx != idx { - drop(client_senders[other_idx].send_async(BlockResponse { - block: block.clone(), - peer_id: PeerId::random(), - was_requested: false, - })); - } - } - } - NetworkRequests::Approval { approval_message } => { - let other_idx = data.index_for_account(&approval_message.target); - if other_idx != idx { - drop( - client_senders[other_idx] - .send_async(BlockApproval(approval_message.approval, PeerId::random())), - ); - } else { - tracing::warn!("Dropping message to self"); - } - } - NetworkRequests::ForwardTx(account, transaction) => { - let other_idx = data.index_for_account(&account); - if other_idx != idx { - drop(client_senders[other_idx].send_async(ProcessTxRequest { - transaction, - is_forwarded: true, - check_only: false, - })) - } else { - tracing::warn!("Dropping message to self"); - } - } - NetworkRequests::ChunkEndorsement(target, endorsement) => { - let other_idx = data.index_for_account(&target); - if other_idx != idx { - drop( - client_senders[other_idx].send_async(ChunkEndorsementMessage(endorsement)), - ); - } else { - tracing::warn!("Dropping message to self"); - } - } - NetworkRequests::ChunkStateWitnessAck(target, witness_ack) => { - let other_idx = data.index_for_account(&target); - if other_idx != idx { - state_witness_senders[other_idx].send(ChunkStateWitnessAckMessage(witness_ack)); - } else { - tracing::warn!("Dropping state-witness-ack message to self"); - } - } - NetworkRequests::PartialEncodedStateWitness(validator_witness_tuple) => { - for (target, partial_witness) in validator_witness_tuple.into_iter() { - let other_idx = data.index_for_account(&target); - if other_idx != idx { - state_witness_senders[other_idx] - .send(PartialEncodedStateWitnessMessage(partial_witness)); - } else { - tracing::warn!("Dropping state-witness message to self"); - } - } - } - NetworkRequests::PartialEncodedStateWitnessForward( - chunk_validators, - partial_witness, - ) => { - for target in chunk_validators { - let other_idx = data.index_for_account(&target); - if other_idx != idx { - state_witness_senders[other_idx].send( - PartialEncodedStateWitnessForwardMessage(partial_witness.clone()), - ); - } else { - tracing::warn!("Dropping state-witness-forward message to self"); - } - } - } - NetworkRequests::SnapshotHostInfo { .. } => { - // TODO: what to do about this? - } - // TODO: Support more network message types as we expand the test. - _ => return Err((idx, PeerManagerMessageRequest::NetworkRequests(request).into())), - } - - Ok(()) - }) -} +use crate::sync::adapter::SyncActorHandler; +use crate::sync::sync_actor::SyncActor; +use crate::{Client, SyncMessage}; // TODO: This would be a good starting point for turning this into a test util. pub trait ClientQueries { @@ -220,7 +25,10 @@ pub trait ClientQueries { fn tracked_shards_for_each_client(&self) -> Vec>; } -impl + AsRef> ClientQueries for Vec { +impl ClientQueries for Vec +where + Data: AsRef, +{ fn client_index_tracking_account(&self, account_id: &AccountId) -> usize { let client: &Client = self[0].as_ref(); let head = client.chain.head().unwrap(); @@ -229,13 +37,11 @@ impl + AsRef> ClientQueries for Vec { for i in 0..self.len() { let client: &Client = self[i].as_ref(); + let validator_signer = client.validator_signer.get().unwrap(); + let account_id = validator_signer.validator_id(); let tracks_shard = client .epoch_manager - .cares_about_shard_from_prev_block( - &head.prev_block_hash, - &self[i].as_ref(), - shard_id, - ) + .cares_about_shard_from_prev_block(&head.prev_block_hash, account_id, shard_id) .unwrap(); if tracks_shard { return i; @@ -311,15 +117,13 @@ impl + AsRef> ClientQueries for Vec { let mut ret = Vec::new(); for i in 0..self.len() { let client: &Client = self[i].as_ref(); + let validator_signer = client.validator_signer.get().unwrap(); + let account_id = validator_signer.validator_id(); let mut tracked_shards = Vec::new(); for shard_id in &all_shard_ids { let tracks_shard = client .epoch_manager - .cares_about_shard_from_prev_block( - &head.prev_block_hash, - &self[i].as_ref(), - *shard_id, - ) + .cares_about_shard_from_prev_block(&head.prev_block_hash, account_id, *shard_id) .unwrap(); if tracks_shard { tracked_shards.push(*shard_id); @@ -331,9 +135,30 @@ impl + AsRef> ClientQueries for Vec { } } -pub fn forward_messages_from_partial_witness_actor_to_client( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, client_actor: &mut ClientActorInner| match msg { - ClientSenderForPartialWitnessMessage::_chunk_state_witness(msg) => client_actor.handle(msg), +pub fn test_loop_sync_actor_maker( + index: usize, + sender: PendingEventsSender, +) -> Arc< + dyn Fn(ShardUId, Sender, Sender) -> SyncActorHandler + + Send + + Sync, +> { + // This is a closure that will be called by SyncAdapter to create SyncActor. + // Since we don't have too much control over when the closure is called, we need to use the CallbackEvent + // to register the SyncActor in the TestLoopData. + // TestLoop and TestLoopData can not cross the closure boundary and be moved while the PendingEventsSender can. + Arc::new(move |shard_uid, client_sender, network_sender| { + let sync_actor = SyncActor::new(shard_uid, client_sender, network_sender); + let sync_actor_adapter = LateBoundSender::new(); + let sync_actor_adapter_clone = sync_actor_adapter.clone(); + let callback = move |data: &mut TestLoopData| { + data.register_actor_for_index(index, sync_actor, Some(sync_actor_adapter)); + }; + sender.send(format!("Register SyncActor {:?}", shard_uid), Box::new(callback)); + SyncActorHandler { + client_sender: sync_actor_adapter_clone.as_sender(), + network_sender: sync_actor_adapter_clone.as_sender(), + shutdown: Mutex::new(Box::new(move || {})), + } }) } diff --git a/chain/client/src/test_utils/test_loop/client_actor.rs b/chain/client/src/test_utils/test_loop/client_actor.rs deleted file mode 100644 index a156cd70d47..00000000000 --- a/chain/client/src/test_utils/test_loop/client_actor.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::client_actor::{ClientActorInner, ClientSenderForClientMessage}; -use crate::sync_jobs_actor::ClientSenderForSyncJobsMessage; -use crate::SyncMessage; -use near_async::messaging::Handler; -use near_async::test_loop::event_handler::LoopEventHandler; -use near_chunks::client::ShardsManagerResponse; -use near_network::client::ClientSenderForNetworkMessage; - -pub fn forward_client_messages_from_network_to_client_actor( -) -> LoopEventHandler { - LoopEventHandler::new(|msg, client_actor: &mut ClientActorInner| { - match msg { - ClientSenderForNetworkMessage::_state_response(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_block_approval(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_transaction(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_block(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_block_headers(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_challenge(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_network_info(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - ClientSenderForNetworkMessage::_chunk_endorsement(msg) => { - (msg.callback)(Ok(client_actor.handle(msg.message))); - } - _ => { - return Err(msg); - } - } - Ok(()) - }) -} - -pub fn forward_client_messages_from_client_to_client_actor( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, client_actor: &mut ClientActorInner| match msg { - ClientSenderForClientMessage::_apply_chunks_done(msg) => client_actor.handle(msg), - }) -} - -pub fn forward_client_messages_from_sync_jobs_to_client_actor( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, client_actor: &mut ClientActorInner| match msg { - ClientSenderForSyncJobsMessage::_apply_state_parts_response(msg) => { - client_actor.handle(msg) - } - ClientSenderForSyncJobsMessage::_block_catch_up_response(msg) => client_actor.handle(msg), - ClientSenderForSyncJobsMessage::_resharding_response(msg) => client_actor.handle(msg), - ClientSenderForSyncJobsMessage::_load_memtrie_response(msg) => client_actor.handle(msg), - }) -} - -pub fn forward_client_messages_from_shards_manager( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, client_actor: &mut ClientActorInner| { - client_actor.handle(msg); - }) -} - -pub fn forward_client_messages_from_sync_adapter() -> LoopEventHandler -{ - LoopEventHandler::new_simple(|msg, client_actor: &mut ClientActorInner| { - client_actor.handle(msg); - }) -} diff --git a/chain/client/src/test_utils/test_loop/partial_witness_actor.rs b/chain/client/src/test_utils/test_loop/partial_witness_actor.rs deleted file mode 100644 index 65fa20742ac..00000000000 --- a/chain/client/src/test_utils/test_loop/partial_witness_actor.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{PartialWitnessActor, PartialWitnessSenderForClientMessage}; -use near_async::messaging::Handler; -use near_async::test_loop::event_handler::LoopEventHandler; -use near_network::state_witness::PartialWitnessSenderForNetworkMessage; - -pub fn forward_messages_from_network_to_partial_witness_actor( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, partial_witness_actor: &mut PartialWitnessActor| match msg { - PartialWitnessSenderForNetworkMessage::_chunk_state_witness_ack(msg) => { - partial_witness_actor.handle(msg); - } - PartialWitnessSenderForNetworkMessage::_partial_encoded_state_witness(msg) => { - partial_witness_actor.handle(msg); - } - PartialWitnessSenderForNetworkMessage::_partial_encoded_state_witness_forward(msg) => { - partial_witness_actor.handle(msg); - } - }) -} - -pub fn forward_messages_from_client_to_partial_witness_actor( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|msg, partial_witness_actor: &mut PartialWitnessActor| match msg { - PartialWitnessSenderForClientMessage::_distribute_chunk_state_witness(msg) => { - partial_witness_actor.handle(msg); - } - }) -} diff --git a/chain/client/src/test_utils/test_loop/sync_actor.rs b/chain/client/src/test_utils/test_loop/sync_actor.rs deleted file mode 100644 index 66a43e8726f..00000000000 --- a/chain/client/src/test_utils/test_loop/sync_actor.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::sync::adapter::SyncActorHandler; -use crate::sync::sync_actor::SyncActor; -use crate::SyncMessage; -use near_async::messaging::{IntoSender, Sender}; -use near_async::test_loop::delay_sender::DelaySender; -use near_async::test_loop::event_handler::LoopEventHandler; -use near_network::state_sync::StateSyncResponse; -use near_network::types::PeerManagerMessageRequest; -use near_primitives::shard_layout::ShardUId; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -pub type TestSyncActors = Arc>>; - -pub fn test_loop_sync_actor_maker( - sender: DelaySender, - sync_actors: TestSyncActors, -) -> Arc< - dyn Fn(ShardUId, Sender, Sender) -> SyncActorHandler - + Send - + Sync, -> -where - E: From<(ShardUId, SyncMessage)> + From<(ShardUId, StateSyncResponse)> + 'static, -{ - Arc::new(move |shard_uid, client_sender, network_sender| { - let sender_for_client: Sender<(ShardUId, SyncMessage)> = sender.clone().into_sender(); - let sender_for_network: Sender<(ShardUId, StateSyncResponse)> = - sender.clone().into_sender(); - let sender_for_client: Sender = - Sender::from_fn(move |msg| sender_for_client.send((shard_uid, msg))); - let sender_for_network: Sender = - Sender::from_fn(move |msg| sender_for_network.send((shard_uid, msg))); - let sync_actor = SyncActor::new(shard_uid, client_sender, network_sender); - assert!( - sync_actors.lock().unwrap().insert(shard_uid, sync_actor).is_none(), - "Sync actor for shard {} already created!", - shard_uid - ); - - let sync_actors = sync_actors.clone(); - SyncActorHandler { - client_sender: sender_for_client, - network_sender: sender_for_network, - shutdown: Mutex::new(Box::new(move || { - sync_actors.lock().unwrap().remove(&shard_uid); - })), - } - }) -} - -pub fn forward_sync_actor_messages_from_client( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|(shard_uid, msg), sync_actors: &mut TestSyncActors| { - sync_actors - .lock() - .unwrap() - .get_mut(&shard_uid) - .expect("No such ShardUId for sync actor") - .handle_client_sync_message(msg); - }) -} - -pub fn forward_sync_actor_messages_from_network( -) -> LoopEventHandler { - LoopEventHandler::new_simple(|(shard_uid, msg), sync_actors: &mut TestSyncActors| { - sync_actors - .lock() - .unwrap() - .get_mut(&shard_uid) - .expect("No such ShardUId for sync actor") - .handle_network_sync_message(msg); - }) -} diff --git a/chain/client/src/test_utils/test_loop/sync_jobs_actor.rs b/chain/client/src/test_utils/test_loop/sync_jobs_actor.rs deleted file mode 100644 index f98d4e22b23..00000000000 --- a/chain/client/src/test_utils/test_loop/sync_jobs_actor.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::client_actor::SyncJobsSenderForClientMessage; -use crate::sync_jobs_actor::SyncJobsActor; -use near_async::messaging::Handler; -use near_async::test_loop::event_handler::LoopEventHandler; -use near_async::test_loop::futures::TestLoopDelayedActionRunner; - -pub fn forward_messages_from_client_to_sync_jobs_actor( - mut ctx: TestLoopDelayedActionRunner, -) -> LoopEventHandler { - LoopEventHandler::new_simple(move |msg, sync_jobs_actor: &mut SyncJobsActor| match msg { - SyncJobsSenderForClientMessage::_apply_state_parts(msg) => { - sync_jobs_actor.handle(msg); - } - SyncJobsSenderForClientMessage::_block_catch_up(msg) => { - sync_jobs_actor.handle(msg); - } - SyncJobsSenderForClientMessage::_resharding(msg) => { - sync_jobs_actor.handle_resharding_request(msg, &mut ctx); - } - SyncJobsSenderForClientMessage::_load_memtrie(msg) => { - sync_jobs_actor.handle(msg); - } - }) -} diff --git a/chain/client/src/tests/bug_repros.rs b/chain/client/src/tests/bug_repros.rs index caac7a6c876..c9a59f87a2d 100644 --- a/chain/client/src/tests/bug_repros.rs +++ b/chain/client/src/tests/bug_repros.rs @@ -122,7 +122,8 @@ fn repro_1183() { from.clone(), KeyType::ED25519, from.as_ref(), - ), + ) + .into(), 1, *block.header().prev_hash(), ), diff --git a/chain/client/src/tests/catching_up.rs b/chain/client/src/tests/catching_up.rs index 3befb5c0118..ff727cc523f 100644 --- a/chain/client/src/tests/catching_up.rs +++ b/chain/client/src/tests/catching_up.rs @@ -75,7 +75,12 @@ fn send_tx( connector.do_send( ProcessTxRequest { transaction: SignedTransaction::send_money( - nonce, from, to, &signer, amount, block_hash, + nonce, + from, + to, + &signer.into(), + amount, + block_hash, ), is_forwarded: false, check_only: false, diff --git a/chain/client/src/tests/cross_shard_tx.rs b/chain/client/src/tests/cross_shard_tx.rs index 423b66c2096..809be505b1c 100644 --- a/chain/client/src/tests/cross_shard_tx.rs +++ b/chain/client/src/tests/cross_shard_tx.rs @@ -115,7 +115,7 @@ fn send_tx( nonce, from.clone(), to.clone(), - &signer, + &signer.into(), amount, block_hash, ), diff --git a/chain/client/src/tests/doomslug.rs b/chain/client/src/tests/doomslug.rs index f49b155c9c3..0a400199cbe 100644 --- a/chain/client/src/tests/doomslug.rs +++ b/chain/client/src/tests/doomslug.rs @@ -31,7 +31,8 @@ fn test_processing_skips_on_forks() { env.process_block(1, b2, Provenance::NONE); let validator_signer = InMemoryValidatorSigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); - let approval = Approval::new(CryptoHash::default(), 1, 3, &validator_signer); - env.clients[1].collect_block_approval(&approval, ApprovalType::SelfApproval); + let approval = Approval::new(CryptoHash::default(), 1, 3, &validator_signer.into()); + let client_signer = env.clients[1].validator_signer.get(); + env.clients[1].collect_block_approval(&approval, ApprovalType::SelfApproval, &client_signer); assert!(!env.clients[1].doomslug.approval_status_at_height(&3).approvals.is_empty()); } diff --git a/chain/client/src/tests/process_blocks.rs b/chain/client/src/tests/process_blocks.rs index f9f454f81c8..6d06874e7ac 100644 --- a/chain/client/src/tests/process_blocks.rs +++ b/chain/client/src/tests/process_blocks.rs @@ -33,6 +33,7 @@ fn test_not_process_height_twice() { duplicate_block.mut_header().get_mut().inner_rest.prev_validator_proposals = proposals; duplicate_block.mut_header().resign(&validator_signer); let dup_block_hash = *duplicate_block.hash(); + let signer = env.clients[0].validator_signer.get(); // we should have dropped the block before we even tried to process it, so the result should be ok env.clients[0] .receive_block_impl( @@ -40,6 +41,7 @@ fn test_not_process_height_twice() { PeerId::new(PublicKey::empty(KeyType::ED25519)), false, None, + &signer, ) .unwrap(); // check that the second block is not being processed @@ -65,6 +67,9 @@ fn test_bad_shard_id() { // modify chunk 0 to have shard_id 1 let chunk = chunks.get(0).unwrap(); let outgoing_receipts_root = chunks.get(1).unwrap().prev_outgoing_receipts_root(); + let congestion_info = ProtocolFeature::CongestionControl + .enabled(PROTOCOL_VERSION) + .then_some(CongestionInfo::default()); let mut modified_chunk = ShardChunkHeaderV3::new( PROTOCOL_VERSION, *chunk.prev_block_hash(), @@ -80,7 +85,7 @@ fn test_bad_shard_id() { outgoing_receipts_root, chunk.tx_root(), chunk.prev_validator_proposals().collect(), - CongestionInfo::default(), + congestion_info, &validator_signer, ); modified_chunk.height_included = 2; @@ -109,9 +114,16 @@ fn test_bad_block_content_vrf() { let block = env.clients[0].produce_block(2).unwrap().unwrap(); let mut bad_block = block.clone(); bad_block.set_vrf_value(Value([0u8; 32])); + let signer = env.clients[0].validator_signer.get(); let err = env.clients[0] - .receive_block_impl(bad_block, PeerId::new(PublicKey::empty(KeyType::ED25519)), false, None) + .receive_block_impl( + bad_block, + PeerId::new(PublicKey::empty(KeyType::ED25519)), + false, + None, + &signer, + ) .unwrap_err(); assert_matches!(err, near_chain::Error::InvalidSignature); @@ -128,9 +140,16 @@ fn test_bad_block_signature() { let block = env.clients[0].produce_block(2).unwrap().unwrap(); let mut bad_block = block.clone(); bad_block.mut_header().get_mut().signature = Signature::default(); + let signer = env.clients[0].validator_signer.get(); let err = env.clients[0] - .receive_block_impl(bad_block, PeerId::new(PublicKey::empty(KeyType::ED25519)), false, None) + .receive_block_impl( + bad_block, + PeerId::new(PublicKey::empty(KeyType::ED25519)), + false, + None, + &signer, + ) .unwrap_err(); assert_matches!(err, near_chain::Error::InvalidSignature); @@ -209,7 +228,7 @@ fn test_bad_congestion_info_impl(mode: BadCongestionInfoMode) { chunk.prev_outgoing_receipts_root(), chunk.tx_root(), chunk.prev_validator_proposals().collect(), - congestion_info, + Some(congestion_info), &validator_signer, ); modified_chunk_header.height_included = 2; diff --git a/chain/client/src/tests/query_client.rs b/chain/client/src/tests/query_client.rs index ed4cdae3bf2..dc660ab0788 100644 --- a/chain/client/src/tests/query_client.rs +++ b/chain/client/src/tests/query_client.rs @@ -99,7 +99,8 @@ fn query_status_not_crash() { &signer, block.header.next_bp_hash, block_merkle_tree.root(), - Clock::real().now_utc(), + Clock::real(), + None, ); next_block.mut_header().get_mut().inner_lite.timestamp = (next_block.header().timestamp() + Duration::seconds(60)).unix_timestamp_nanos() @@ -167,7 +168,7 @@ fn test_execution_outcome_for_chunk() { 1, "test".parse().unwrap(), "near".parse().unwrap(), - &signer, + &signer.into(), 10, block_hash, ); diff --git a/chain/client/src/view_client_actor.rs b/chain/client/src/view_client_actor.rs index 16d75b5ffd2..288c0af8494 100644 --- a/chain/client/src/view_client_actor.rs +++ b/chain/client/src/view_client_actor.rs @@ -13,7 +13,7 @@ use near_chain::types::{RuntimeAdapter, Tip}; use near_chain::{ get_epoch_block_producers_view, Chain, ChainGenesis, ChainStoreAccess, DoomslugThresholdMode, }; -use near_chain_configs::{ClientConfig, ProtocolConfigView}; +use near_chain_configs::{ClientConfig, MutableValidatorSigner, ProtocolConfigView}; use near_chain_primitives::error::EpochErrorResultToChainError; use near_client_primitives::types::{ Error, GetBlock, GetBlockError, GetBlockProof, GetBlockProofError, GetBlockProofResponse, @@ -51,6 +51,7 @@ use near_primitives::types::{ AccountId, BlockHeight, BlockId, BlockReference, EpochReference, Finality, MaybeBlockId, ShardId, SyncCheckpoint, TransactionOrReceiptId, ValidatorInfoIdentifier, }; +use near_primitives::validator_signer::ValidatorSigner; use near_primitives::views::validator_stake_view::ValidatorStakeView; use near_primitives::views::{ BlockView, ChunkView, EpochValidatorInfo, ExecutionOutcomeWithIdView, ExecutionStatusView, @@ -64,6 +65,7 @@ use near_store::{DBCol, COLD_HEAD_KEY, FINAL_HEAD_KEY, HEAD_KEY}; use std::cmp::Ordering; use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; use std::hash::Hash; +use std::num::NonZeroUsize; use std::sync::{Arc, Mutex, RwLock}; use tracing::{error, info, warn}; @@ -80,12 +82,6 @@ pub struct ViewClientRequestManager { pub tx_status_requests: lru::LruCache, /// Transaction status response pub tx_status_response: lru::LruCache, - /// Query requests that need to be forwarded to other shards - pub query_requests: lru::LruCache, - /// Query responses from other nodes (can be errors) - pub query_responses: lru::LruCache>, - /// Receipt outcome requests - pub receipt_outcome_requests: lru::LruCache, } pub type ViewClientActor = SyncActixWrapper; @@ -95,8 +91,10 @@ pub struct ViewClientActorInner { clock: Clock, pub adv: crate::adversarial::Controls, - /// Validator account (if present). - validator_account_id: Option, + /// Validator account (if present). This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + validator: MutableValidatorSigner, chain: Chain, epoch_manager: Arc, shard_tracker: ShardTracker, @@ -110,11 +108,8 @@ pub struct ViewClientActorInner { impl ViewClientRequestManager { pub fn new() -> Self { Self { - tx_status_requests: lru::LruCache::new(QUERY_REQUEST_LIMIT), - tx_status_response: lru::LruCache::new(QUERY_REQUEST_LIMIT), - query_requests: lru::LruCache::new(QUERY_REQUEST_LIMIT), - query_responses: lru::LruCache::new(QUERY_REQUEST_LIMIT), - receipt_outcome_requests: lru::LruCache::new(QUERY_REQUEST_LIMIT), + tx_status_requests: lru::LruCache::new(NonZeroUsize::new(QUERY_REQUEST_LIMIT).unwrap()), + tx_status_response: lru::LruCache::new(NonZeroUsize::new(QUERY_REQUEST_LIMIT).unwrap()), } } } @@ -125,7 +120,7 @@ impl ViewClientActorInner { pub fn spawn_actix_actor( clock: Clock, - validator_account_id: Option, + validator: MutableValidatorSigner, chain_genesis: ChainGenesis, epoch_manager: Arc, shard_tracker: ShardTracker, @@ -150,7 +145,7 @@ impl ViewClientActorInner { let view_client_actor = ViewClientActorInner { clock: clock.clone(), adv: adv.clone(), - validator_account_id: validator_account_id.clone(), + validator: validator.clone(), chain, epoch_manager: epoch_manager.clone(), shard_tracker: shard_tracker.clone(), @@ -513,6 +508,7 @@ impl ViewClientActorInner { tx_hash: CryptoHash, signer_account_id: AccountId, fetch_receipt: bool, + validator_signer: &Option>, ) -> Result { { // TODO(telezhnaya): take into account `fetch_receipt()` @@ -537,7 +533,7 @@ impl ViewClientActorInner { .map_err(|err| TxStatusError::InternalError(err.to_string()))?; // Check if we are tracking this shard. if self.shard_tracker.care_about_shard( - self.validator_account_id.as_ref(), + validator_signer.as_ref().map(|v| v.validator_id()), &head.prev_block_hash, target_shard_id, true, @@ -786,7 +782,8 @@ impl Handler for ViewClientActorInner { tracing::debug!(target: "client", ?msg); let _timer = metrics::VIEW_CLIENT_MESSAGE_TIME.with_label_values(&["TxStatus"]).start_timer(); - self.get_tx_status(msg.tx_hash, msg.signer_account_id, msg.fetch_receipt) + let validator_signer = self.validator.get(); + self.get_tx_status(msg.tx_hash, msg.signer_account_id, msg.fetch_receipt, &validator_signer) } } @@ -823,7 +820,7 @@ impl Handler for ViewClientActorInner { if block_header.epoch_id() != next_block_header.epoch_id() && block_header.next_epoch_id() == next_block_header.epoch_id() { - ValidatorInfoIdentifier::EpochId(block_header.epoch_id().clone()) + ValidatorInfoIdentifier::EpochId(*block_header.epoch_id()) } else { return Err(GetValidatorInfoError::ValidatorInfoUnavailable); } @@ -972,8 +969,8 @@ impl Handler for ViewClientActorInner { .with_label_values(&["GetNextLightClientBlock"]) .start_timer(); let last_block_header = self.chain.get_block_header(&msg.last_block_hash)?; - let last_epoch_id = last_block_header.epoch_id().clone(); - let last_next_epoch_id = last_block_header.next_epoch_id().clone(); + let last_epoch_id = *last_block_header.epoch_id(); + let last_next_epoch_id = *last_block_header.next_epoch_id(); let last_height = last_block_header.height(); let head = self.chain.head()?; @@ -1027,7 +1024,7 @@ impl Handler for ViewClientActorInner { Ok(outcome) => { let mut outcome_proof = outcome; let epoch_id = - self.chain.get_block(&outcome_proof.block_hash)?.header().epoch_id().clone(); + *self.chain.get_block(&outcome_proof.block_hash)?.header().epoch_id(); let target_shard_id = self .epoch_manager .account_id_to_shard_id(&account_id, &epoch_id) @@ -1069,7 +1066,7 @@ impl Handler for ViewClientActorInner { .account_id_to_shard_id(&account_id, &head.epoch_id) .into_chain_error()?; if self.shard_tracker.care_about_shard( - self.validator_account_id.as_ref(), + self.validator.get().map(|v| v.validator_id().clone()).as_ref(), &head.last_block_hash, target_shard_id, true, @@ -1204,8 +1201,10 @@ impl Handler for ViewClientActorInner { let _timer = metrics::VIEW_CLIENT_MESSAGE_TIME.with_label_values(&["TxStatusRequest"]).start_timer(); let TxStatusRequest { tx_hash, signer_account_id } = msg; - if let Ok(Some(result)) = - self.get_tx_status(tx_hash, signer_account_id, false).map(|s| s.execution_outcome) + let validator_signer = self.validator.get(); + if let Ok(Some(result)) = self + .get_tx_status(tx_hash, signer_account_id, false, &validator_signer) + .map(|s| s.execution_outcome) { Some(Box::new(result.into_outcome())) } else { diff --git a/chain/epoch-manager/src/adapter.rs b/chain/epoch-manager/src/adapter.rs index f55b94f2313..0553132cc26 100644 --- a/chain/epoch-manager/src/adapter.rs +++ b/chain/epoch-manager/src/adapter.rs @@ -8,8 +8,7 @@ use near_primitives::block::Tip; use near_primitives::block_header::{Approval, ApprovalInner, BlockHeader}; use near_primitives::epoch_manager::block_info::BlockInfo; use near_primitives::epoch_manager::epoch_info::EpochInfo; -use near_primitives::epoch_manager::EpochConfig; -use near_primitives::epoch_manager::ShardConfig; +use near_primitives::epoch_manager::{EpochConfig, ShardConfig}; use near_primitives::errors::EpochError; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::{account_id_to_shard_id, ShardLayout, ShardLayoutError}; @@ -30,10 +29,15 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::sync::Arc; -/// A trait that abstracts the interface of the EpochManager. -/// The two implementations are EpochManagerHandle and KeyValueEpochManager. -/// Strongly prefer the former whenever possible. The latter is for legacy -/// tests. +/// A trait that abstracts the interface of the EpochManager. The two +/// implementations are EpochManagerHandle and KeyValueEpochManager. Strongly +/// prefer the former whenever possible. The latter is for legacy tests. +/// +/// TODO - Most of the methods here take the epoch id as an argument but often +/// the protocol version would be sufficient. Rename those methods by adding +/// "_from_epoch_id" suffix and add the more precise methods using only the +/// protocol version. This may simplify the usage of the EpochManagerAdapter in +/// a few places where it's cumbersome to get the epoch id. pub trait EpochManagerAdapter: Send + Sync { /// Check if epoch exists. fn epoch_exists(&self, epoch_id: &EpochId) -> bool; @@ -190,6 +194,12 @@ pub trait EpochManagerAdapter: Send + Sync { epoch_id: &EpochId, ) -> Result, EpochError>; + /// Returns all validators for a given epoch. + fn get_epoch_all_validators( + &self, + epoch_id: &EpochId, + ) -> Result, EpochError>; + /// Block producers for given height for the main block. Return EpochError if outside of known boundaries. fn get_block_producer( &self, @@ -416,6 +426,13 @@ pub trait EpochManagerAdapter: Send + Sync { partial_witness: &PartialEncodedStateWitness, ) -> Result; + fn cares_about_shard_in_epoch( + &self, + epoch_id: EpochId, + account_id: &AccountId, + shard_id: ShardId, + ) -> Result; + fn cares_about_shard_from_prev_block( &self, parent_hash: &CryptoHash, @@ -808,7 +825,7 @@ impl EpochManagerAdapter for EpochManagerHandle { > { let epoch_manager = self.read(); let last_block_info = epoch_manager.get_block_info(prev_epoch_last_block_hash)?; - let prev_epoch_id = last_block_info.epoch_id().clone(); + let prev_epoch_id = *last_block_info.epoch_id(); Ok(( epoch_manager.get_block_info(last_block_info.epoch_first_block())?, epoch_manager.get_block_info(last_block_info.prev_hash())?, @@ -900,7 +917,7 @@ impl EpochManagerAdapter for EpochManagerHandle { data, signature, ) { - Err(Error::NotAValidator) => { + Err(Error::NotAValidator(_)) => { let (fisherman, is_slashed) = self.get_fisherman_by_account_id(epoch_id, last_known_block_hash, account_id)?; if is_slashed { @@ -1048,7 +1065,7 @@ impl EpochManagerAdapter for EpochManagerHandle { chunk_header.height_created(), )?; if !chunk_validator_assignments.contains(&endorsement.account_id) { - return Err(Error::NotAValidator); + return Err(Error::NotAValidator(format!("verify chunk endorsement"))); } let validator = epoch_manager.get_validator_by_account_id(&epoch_id, &endorsement.account_id)?; @@ -1125,6 +1142,25 @@ impl EpochManagerAdapter for EpochManagerHandle { #[cfg(feature = "new_epoch_sync")] fn force_update_aggregator(&self, epoch_id: &EpochId, hash: &CryptoHash) { let mut epoch_manager = self.write(); - epoch_manager.epoch_info_aggregator = EpochInfoAggregator::new(epoch_id.clone(), *hash); + epoch_manager.epoch_info_aggregator = EpochInfoAggregator::new(*epoch_id, *hash); + } + + /// Returns the set of chunk validators for a given epoch + fn get_epoch_all_validators( + &self, + epoch_id: &EpochId, + ) -> Result, EpochError> { + let epoch_manager = self.read(); + Ok(epoch_manager.get_epoch_info(epoch_id)?.validators_iter().collect::>()) + } + + fn cares_about_shard_in_epoch( + &self, + epoch_id: EpochId, + account_id: &AccountId, + shard_id: ShardId, + ) -> Result { + let epoch_manager = self.read(); + epoch_manager.cares_about_shard_in_epoch(epoch_id, account_id, shard_id) } } diff --git a/chain/epoch-manager/src/lib.rs b/chain/epoch-manager/src/lib.rs index b69356d6a60..1337df96a6b 100644 --- a/chain/epoch-manager/src/lib.rs +++ b/chain/epoch-manager/src/lib.rs @@ -1,5 +1,4 @@ use crate::metrics::{PROTOCOL_VERSION_NEXT, PROTOCOL_VERSION_VOTES}; -use crate::proposals::proposals_to_epoch_info; use crate::types::EpochInfoAggregator; use near_cache::SyncLruCache; use near_chain_configs::GenesisConfig; @@ -17,8 +16,8 @@ use near_primitives::shard_layout::ShardLayout; use near_primitives::stateless_validation::ChunkValidatorAssignments; use near_primitives::types::validator_stake::ValidatorStake; use near_primitives::types::{ - AccountId, ApprovalStake, Balance, BlockChunkValidatorStats, BlockHeight, ChunkValidatorStats, - EpochId, EpochInfoProvider, NumSeats, ShardId, ValidatorId, ValidatorInfoIdentifier, + AccountId, ApprovalStake, Balance, BlockChunkValidatorStats, BlockHeight, ChunkStats, EpochId, + EpochInfoProvider, NumSeats, ShardId, ValidatorId, ValidatorInfoIdentifier, ValidatorKickoutReason, ValidatorStats, }; use near_primitives::version::{ProtocolVersion, UPGRADABILITY_FIX_PROTOCOL_VERSION}; @@ -35,6 +34,7 @@ use tracing::{debug, warn}; use types::BlockHeaderInfo; pub use crate::adapter::EpochManagerAdapter; +pub use crate::proposals::proposals_to_epoch_info; pub use crate::reward_calculator::RewardCalculator; pub use crate::reward_calculator::NUM_SECONDS_IN_A_YEAR; pub use crate::types::RngSeed; @@ -450,6 +450,8 @@ impl EpochManager { exempted_validators } + /// Computes the set of validators to reward with stats and validators to kick out with reason. + /// /// # Parameters /// epoch_info /// block_validator_tracker @@ -459,7 +461,7 @@ impl EpochManager { /// prev_validator_kickout: previously kicked out /// /// # Returns - /// (set of validators to kickout, set of validators to reward with stats) + /// (set of validators to reward with stats, set of validators to kickout) /// /// - Slashed validators are ignored (they are handled separately) /// - The total stake of validators that will be kicked out will not exceed @@ -468,17 +470,18 @@ impl EpochManager { /// - A validator is kicked out if he produced too few blocks or chunks /// - If all validators are either previously kicked out or to be kicked out, we choose one not to /// kick out - fn compute_kickout_info( + fn compute_validators_to_reward_and_kickout( config: &EpochConfig, epoch_info: &EpochInfo, block_validator_tracker: &HashMap, - chunk_validator_tracker: &HashMap>, + chunk_stats_tracker: &HashMap>, slashed: &HashMap, prev_validator_kickout: &HashMap, - ) -> (HashMap, HashMap) + ) -> (HashMap, HashMap) { let block_producer_kickout_threshold = config.block_producer_kickout_threshold; let chunk_producer_kickout_threshold = config.chunk_producer_kickout_threshold; + let chunk_validator_only_kickout_threshold = config.chunk_validator_only_kickout_threshold; let mut validator_block_chunk_stats = HashMap::new(); let mut total_stake: Balance = 0; let mut maximum_block_prod = 0; @@ -493,11 +496,15 @@ impl EpochManager { .get(&(i as u64)) .unwrap_or(&ValidatorStats { expected: 0, produced: 0 }) .clone(); - let mut chunk_stats = ChunkValidatorStats::default(); - for (_, tracker) in chunk_validator_tracker.iter() { + let mut chunk_stats = ChunkStats::default(); + for (_, tracker) in chunk_stats_tracker.iter() { if let Some(stat) = tracker.get(&(i as u64)) { *chunk_stats.expected_mut() += stat.expected(); *chunk_stats.produced_mut() += stat.produced(); + chunk_stats.endorsement_stats_mut().produced += + stat.endorsement_stats().produced; + chunk_stats.endorsement_stats_mut().expected += + stat.endorsement_stats().expected; } } total_stake += v.stake(); @@ -528,9 +535,7 @@ impl EpochManager { all_kicked_out = false; continue; } - if stats.block_stats.produced * 100 - < u64::from(block_producer_kickout_threshold) * stats.block_stats.expected - { + if stats.block_stats.less_than(block_producer_kickout_threshold) { validator_kickout.insert( account_id.clone(), ValidatorKickoutReason::NotEnoughBlocks { @@ -539,9 +544,7 @@ impl EpochManager { }, ); } - if stats.chunk_stats.produced() * 100 - < u64::from(chunk_producer_kickout_threshold) * stats.chunk_stats.expected() - { + if stats.chunk_stats.production_stats().less_than(chunk_producer_kickout_threshold) { validator_kickout.entry(account_id.clone()).or_insert_with(|| { ValidatorKickoutReason::NotEnoughChunks { produced: stats.chunk_stats.produced(), @@ -549,6 +552,21 @@ impl EpochManager { } }); } + let chunk_validator_only = + stats.block_stats.expected == 0 && stats.chunk_stats.expected() == 0; + if chunk_validator_only + && stats + .chunk_stats + .endorsement_stats() + .less_than(chunk_validator_only_kickout_threshold) + { + validator_kickout.entry(account_id.clone()).or_insert_with(|| { + ValidatorKickoutReason::NotEnoughChunkEndorsements { + produced: stats.chunk_stats.endorsement_stats().produced, + expected: stats.chunk_stats.endorsement_stats().expected, + } + }); + } let is_already_kicked_out = prev_validator_kickout.contains_key(account_id); if !validator_kickout.contains_key(account_id) { if !is_already_kicked_out { @@ -562,7 +580,7 @@ impl EpochManager { validator_kickout.remove(&validator); } } - (validator_kickout, validator_block_chunk_stats) + (validator_block_chunk_stats, validator_kickout) } fn collect_blocks_info( @@ -581,7 +599,6 @@ impl EpochManager { version_tracker, .. } = self.get_epoch_info_aggregator_upto_last(last_block_hash)?; - let mut proposals = vec![]; let mut validator_kickout = HashMap::new(); @@ -660,7 +677,7 @@ impl EpochManager { let config = self.config.for_protocol_version(epoch_info.protocol_version()); // Compute kick outs for validators who are offline. - let (kickout, validator_block_chunk_stats) = Self::compute_kickout_info( + let (validator_block_chunk_stats, kickout) = Self::compute_validators_to_reward_and_kickout( &config, &epoch_info, &block_validator_tracker, @@ -721,6 +738,7 @@ impl EpochManager { reason, ValidatorKickoutReason::NotEnoughBlocks { .. } | ValidatorKickoutReason::NotEnoughChunks { .. } + | ValidatorKickoutReason::NotEnoughChunkEndorsements { .. } ) { validator_block_chunk_stats.remove(account_id); } @@ -815,7 +833,7 @@ impl EpochManager { is_epoch_start = true; } else { // Same epoch as parent, copy epoch_id and epoch_start_height. - *block_info.epoch_id_mut() = prev_block_info.epoch_id().clone(); + *block_info.epoch_id_mut() = *prev_block_info.epoch_id(); *block_info.epoch_first_block_mut() = *prev_block_info.epoch_first_block(); } let epoch_info = self.get_epoch_info(block_info.epoch_id())?; @@ -903,7 +921,7 @@ impl EpochManager { last_known_block_hash: &CryptoHash, ) -> Result, EpochError> { // TODO(3674): Revisit this when we enable slashing - self.epoch_validators_ordered.get_or_try_put(epoch_id.clone(), |epoch_id| { + self.epoch_validators_ordered.get_or_try_put(*epoch_id, |epoch_id| { let block_info = self.get_block_info(last_known_block_hash)?; let epoch_info = self.get_epoch_info(epoch_id)?; let result = epoch_info @@ -926,7 +944,7 @@ impl EpochManager { epoch_id: &EpochId, last_known_block_hash: &CryptoHash, ) -> Result, EpochError> { - self.epoch_validators_ordered_unique.get_or_try_put(epoch_id.clone(), |epoch_id| { + self.epoch_validators_ordered_unique.get_or_try_put(*epoch_id, |epoch_id| { let settlement = self.get_all_block_producers_settlement(epoch_id, last_known_block_hash)?; let mut validators: HashSet = HashSet::default(); @@ -947,7 +965,7 @@ impl EpochManager { &self, epoch_id: &EpochId, ) -> Result, EpochError> { - self.epoch_chunk_producers_unique.get_or_try_put(epoch_id.clone(), |epoch_id| { + self.epoch_chunk_producers_unique.get_or_try_put(*epoch_id, |epoch_id| { let mut producers: HashSet = HashSet::default(); // Collect unique chunk producers. @@ -969,7 +987,7 @@ impl EpochManager { shard_id: ShardId, height: BlockHeight, ) -> Result, EpochError> { - let cache_key = (epoch_id.clone(), shard_id, height); + let cache_key = (*epoch_id, shard_id, height); if let Some(chunk_validators) = self.chunk_validators_cache.get(&cache_key) { return Ok(chunk_validators); } @@ -983,7 +1001,7 @@ impl EpochManager { (epoch_info.get_validator(validator_id).take_account_id(), assignment_weight) }) .collect(); - let cache_key = (epoch_id.clone(), shard_id as ShardId, height); + let cache_key = (*epoch_id, shard_id as ShardId, height); self.chunk_validators_cache .put(cache_key, Arc::new(ChunkValidatorAssignments::new(chunk_validators))); } @@ -1082,7 +1100,7 @@ impl EpochManager { let epoch_info = self.get_epoch_info(epoch_id)?; epoch_info .get_validator_by_account(account_id) - .ok_or_else(|| EpochError::NotAValidator(account_id.clone(), epoch_id.clone())) + .ok_or_else(|| EpochError::NotAValidator(account_id.clone(), *epoch_id)) } /// Returns fisherman for given account id for given epoch. @@ -1094,11 +1112,11 @@ impl EpochManager { let epoch_info = self.get_epoch_info(epoch_id)?; epoch_info .get_fisherman_by_account(account_id) - .ok_or_else(|| EpochError::NotAValidator(account_id.clone(), epoch_id.clone())) + .ok_or_else(|| EpochError::NotAValidator(account_id.clone(), *epoch_id)) } pub fn get_epoch_id(&self, block_hash: &CryptoHash) -> Result { - Ok(self.get_block_info(block_hash)?.epoch_id().clone()) + Ok(*self.get_block_info(block_hash)?.epoch_id()) } pub fn get_next_epoch_id(&self, block_hash: &CryptoHash) -> Result { @@ -1120,6 +1138,25 @@ impl EpochManager { self.get_epoch_info(&epoch_id) } + pub fn cares_about_shard_in_epoch( + &self, + epoch_id: EpochId, + account_id: &AccountId, + shard_id: ShardId, + ) -> Result { + let epoch_info = self.get_epoch_info(&epoch_id)?; + let chunk_producers_settlement = epoch_info.chunk_producers_settlement(); + let chunk_producers = chunk_producers_settlement + .get(shard_id as usize) + .ok_or_else(|| EpochError::ShardingError(format!("invalid shard id {shard_id}")))?; + for validator_id in chunk_producers.iter() { + if epoch_info.validator_account_id(*validator_id) == account_id { + return Ok(true); + } + } + Ok(false) + } + pub fn cares_about_shard_from_prev_block( &self, parent_hash: &CryptoHash, @@ -1146,11 +1183,7 @@ impl EpochManager { .get_children_shards_ids(shard_id) .expect("all shard layouts expect the first one must have a split map"); for next_shard_id in split_shards { - if self.cares_about_shard_in_epoch( - next_epoch_id.clone(), - account_id, - next_shard_id, - )? { + if self.cares_about_shard_in_epoch(next_epoch_id, account_id, next_shard_id)? { return Ok(true); } } @@ -1327,7 +1360,7 @@ impl EpochManager { epoch_identifier: ValidatorInfoIdentifier, ) -> Result { let epoch_id = match epoch_identifier { - ValidatorInfoIdentifier::EpochId(ref id) => id.clone(), + ValidatorInfoIdentifier::EpochId(ref id) => *id, ValidatorInfoIdentifier::BlockHash(ref b) => self.get_epoch_id(b)?, }; let cur_epoch_info = self.get_epoch_info(&epoch_id)?; @@ -1359,7 +1392,7 @@ impl EpochManager { .get(info.account_id()) .unwrap_or(&BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats { + chunk_stats: ChunkStats { production: ValidatorStats { produced: 0, expected: 0 }, endorsement: ValidatorStats { produced: 0, expected: 0 }, }, @@ -1417,9 +1450,9 @@ impl EpochManager { .unwrap_or(&ValidatorStats { produced: 0, expected: 0 }) .clone(); - let mut chunks_stats_by_shard: HashMap = + let mut chunks_stats_by_shard: HashMap = HashMap::new(); - let mut chunk_stats = ChunkValidatorStats::default(); + let mut chunk_stats = ChunkStats::default(); for (shard, tracker) in aggregator.shard_tracker.iter() { if let Some(stats) = tracker.get(&(validator_id as u64)) { let produced = stats.produced(); @@ -1596,7 +1629,7 @@ impl EpochManager { (Ok(index1), Ok(index2)) => Ok(index1.cmp(&index2)), (Ok(_), Err(_)) => self.get_epoch_info(other_epoch_id).map(|_| Ordering::Less), (Err(_), Ok(_)) => self.get_epoch_info(epoch_id).map(|_| Ordering::Greater), - (Err(_), Err(_)) => Err(EpochError::EpochOutOfBounds(epoch_id.clone())), // other_epoch_id may be out of bounds as well + (Err(_), Err(_)) => Err(EpochError::EpochOutOfBounds(*epoch_id)), // other_epoch_id may be out of bounds as well } } @@ -1616,25 +1649,6 @@ impl EpochManager { /// Private utilities for EpochManager. impl EpochManager { - fn cares_about_shard_in_epoch( - &self, - epoch_id: EpochId, - account_id: &AccountId, - shard_id: ShardId, - ) -> Result { - let epoch_info = self.get_epoch_info(&epoch_id)?; - let chunk_producers_settlement = epoch_info.chunk_producers_settlement(); - let chunk_producers = chunk_producers_settlement - .get(shard_id as usize) - .ok_or_else(|| EpochError::ShardingError(format!("invalid shard id {shard_id}")))?; - for validator_id in chunk_producers.iter() { - if epoch_info.validator_account_id(*validator_id) == account_id { - return Ok(true); - } - } - Ok(false) - } - #[inline] pub(crate) fn block_producer_from_info( epoch_info: &EpochInfo, @@ -1708,9 +1722,16 @@ impl EpochManager { Ok(ShardConfig::new(epoch_config)) } + pub fn get_config_for_protocol_version( + &self, + protocol_version: ProtocolVersion, + ) -> Result { + Ok(self.config.for_protocol_version(protocol_version)) + } + pub fn get_epoch_config(&self, epoch_id: &EpochId) -> Result { let protocol_version = self.get_epoch_info(epoch_id)?.protocol_version(); - Ok(self.config.for_protocol_version(protocol_version)) + self.get_config_for_protocol_version(protocol_version) } pub fn get_shard_layout(&self, epoch_id: &EpochId) -> Result { @@ -1728,10 +1749,10 @@ impl EpochManager { } pub fn get_epoch_info(&self, epoch_id: &EpochId) -> Result, EpochError> { - self.epochs_info.get_or_try_put(epoch_id.clone(), |epoch_id| { + self.epochs_info.get_or_try_put(*epoch_id, |epoch_id| { self.store .get_ser(DBCol::EpochInfo, epoch_id.as_ref())? - .ok_or_else(|| EpochError::EpochOutOfBounds(epoch_id.clone())) + .ok_or_else(|| EpochError::EpochOutOfBounds(*epoch_id)) }) } @@ -1750,7 +1771,7 @@ impl EpochManager { epoch_info: Arc, ) -> Result<(), EpochError> { store_update.set_ser(DBCol::EpochInfo, epoch_id.as_ref(), &epoch_info)?; - self.epochs_info.put(epoch_id.clone(), epoch_info); + self.epochs_info.put(*epoch_id, epoch_info); Ok(()) } @@ -1758,7 +1779,7 @@ impl EpochManager { // We don't use cache here since this query happens rarely and only for rpc. self.store .get_ser(DBCol::EpochValidatorInfo, epoch_id.as_ref())? - .ok_or_else(|| EpochError::EpochOutOfBounds(epoch_id.clone())) + .ok_or_else(|| EpochError::EpochOutOfBounds(*epoch_id)) } // Note(#6572): beware, after calling `save_epoch_validator_info`, @@ -1817,15 +1838,15 @@ impl EpochManager { store_update .set_ser(DBCol::EpochStart, epoch_id.as_ref(), &epoch_start) .map_err(EpochError::from)?; - self.epoch_id_to_start.put(epoch_id.clone(), epoch_start); + self.epoch_id_to_start.put(*epoch_id, epoch_start); Ok(()) } fn get_epoch_start_from_epoch_id(&self, epoch_id: &EpochId) -> Result { - self.epoch_id_to_start.get_or_try_put(epoch_id.clone(), |epoch_id| { + self.epoch_id_to_start.get_or_try_put(*epoch_id, |epoch_id| { self.store .get_ser(DBCol::EpochStart, epoch_id.as_ref())? - .ok_or_else(|| EpochError::EpochOutOfBounds(epoch_id.clone())) + .ok_or_else(|| EpochError::EpochOutOfBounds(*epoch_id)) }) } @@ -1929,10 +1950,10 @@ impl EpochManager { ); } - let epoch_id = self.get_block_info(block_hash)?.epoch_id().clone(); + let epoch_id = *self.get_block_info(block_hash)?.epoch_id(); let epoch_info = self.get_epoch_info(&epoch_id)?; - let mut aggregator = EpochInfoAggregator::new(epoch_id.clone(), *block_hash); + let mut aggregator = EpochInfoAggregator::new(epoch_id, *block_hash); let mut cur_hash = *block_hash; Ok(Some(loop { #[cfg(test)] @@ -1960,7 +1981,7 @@ impl EpochManager { let prev_hash = *block_info.prev_hash(); let prev_info = self.get_block_info(&prev_hash)?; let prev_height = prev_info.height(); - let prev_epoch = prev_info.epoch_id().clone(); + let prev_epoch = *prev_info.epoch_id(); let block_info = self.get_block_info(&cur_hash)?; aggregator.update_tail(&block_info, &epoch_info, prev_height); @@ -2007,11 +2028,11 @@ impl EpochManager { // if the genesis height is nonzero. It's easier to handle it manually. if tip.prev_block_hash == CryptoHash::default() { if tip.height == height { - return Ok(vec![tip.epoch_id.clone()]); + return Ok(vec![tip.epoch_id]); } if height > tip.height { - return Ok(vec![tip.next_epoch_id.clone()]); + return Ok(vec![tip.next_epoch_id]); } return Ok(vec![]); @@ -2030,7 +2051,7 @@ impl EpochManager { // All blocks with height lower than the estimated end are guaranteed to reside in the current epoch. // The situation is clear here. if (current_epoch_start..current_epoch_estimated_end).contains(&height) { - return Ok(vec![tip.epoch_id.clone()]); + return Ok(vec![tip.epoch_id]); } // If the height is higher than the current epoch's estimated end, then it's @@ -2039,7 +2060,7 @@ impl EpochManager { // past its estimated end, so the height might end up being in the current epoch, // even though its height is higher than the estimated end. if height >= current_epoch_estimated_end { - return Ok(vec![tip.epoch_id.clone(), tip.next_epoch_id.clone()]); + return Ok(vec![tip.epoch_id, tip.next_epoch_id]); } // Finally try the previous epoch. @@ -2055,7 +2076,7 @@ impl EpochManager { if tip.epoch_id == EpochId(CryptoHash::default()) { let genesis_block_info = prev_epoch_last_block_info; if height == genesis_block_info.height() { - return Ok(vec![genesis_block_info.epoch_id().clone()]); + return Ok(vec![*genesis_block_info.epoch_id()]); } else { return Ok(vec![]); } @@ -2064,7 +2085,7 @@ impl EpochManager { if (prev_epoch_first_block_info.height()..=prev_epoch_last_block_info.height()) .contains(&height) { - return Ok(vec![prev_epoch_last_block_info.epoch_id().clone()]); + return Ok(vec![*prev_epoch_last_block_info.epoch_id()]); } // The height doesn't belong to any of the epochs around the tip, return an empty Vec. diff --git a/chain/epoch-manager/src/proposals.rs b/chain/epoch-manager/src/proposals.rs index d9f08469da8..f09731f93be 100644 --- a/chain/epoch-manager/src/proposals.rs +++ b/chain/epoch-manager/src/proposals.rs @@ -56,7 +56,7 @@ pub fn proposals_to_epoch_info( AliasValidatorSelectionAlgorithm, prev_prev_epoch_protocol_version ) { - return crate::validator_selection::proposals_to_epoch_info( + crate::validator_selection::proposals_to_epoch_info( epoch_config, rng_seed, prev_epoch_info, @@ -66,9 +66,9 @@ pub fn proposals_to_epoch_info( minted_amount, protocol_version, use_stable_shard_assignment, - ); + ) } else { - return old_validator_selection::proposals_to_epoch_info( + old_validator_selection::proposals_to_epoch_info( epoch_config, rng_seed, prev_epoch_info, @@ -77,7 +77,7 @@ pub fn proposals_to_epoch_info( validator_reward, minted_amount, protocol_version, - ); + ) } } diff --git a/chain/epoch-manager/src/reward_calculator.rs b/chain/epoch-manager/src/reward_calculator.rs index 76665a2eea2..df2729c5c0a 100644 --- a/chain/epoch-manager/src/reward_calculator.rs +++ b/chain/epoch-manager/src/reward_calculator.rs @@ -208,7 +208,7 @@ impl RewardCalculator { #[cfg(test)] mod tests { use super::*; - use near_primitives::types::{BlockChunkValidatorStats, ChunkValidatorStats, ValidatorStats}; + use near_primitives::types::{BlockChunkValidatorStats, ChunkStats, ValidatorStats}; use near_primitives::version::PROTOCOL_VERSION; use num_rational::Ratio; use std::collections::HashMap; @@ -231,14 +231,14 @@ mod tests { "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::default(), + chunk_stats: ChunkStats::default(), }, ), ( "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 1 }, - chunk_stats: ChunkValidatorStats::new_with_production(0, 1), + chunk_stats: ChunkStats::new_with_production(0, 1), }, ), ]); @@ -282,21 +282,21 @@ mod tests { "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 945, expected: 1000 }, - chunk_stats: ChunkValidatorStats::new_with_production(945, 1000), + chunk_stats: ChunkStats::new_with_production(945, 1000), }, ), ( "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 999, expected: 1000 }, - chunk_stats: ChunkValidatorStats::new_with_production(999, 1000), + chunk_stats: ChunkStats::new_with_production(999, 1000), }, ), ( "test3".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 850, expected: 1000 }, - chunk_stats: ChunkValidatorStats::new_with_production(850, 1000), + chunk_stats: ChunkStats::new_with_production(850, 1000), }, ), ]); @@ -347,7 +347,7 @@ mod tests { "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 945, expected: 1000 }, - chunk_stats: ChunkValidatorStats::new_with_production(945, 1000), + chunk_stats: ChunkStats::new_with_production(945, 1000), }, ), // chunk only producer @@ -355,7 +355,7 @@ mod tests { "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::new_with_production(999, 1000), + chunk_stats: ChunkStats::new_with_production(999, 1000), }, ), // block only producer (not implemented right now, just for testing) @@ -363,7 +363,7 @@ mod tests { "test3".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 945, expected: 1000 }, - chunk_stats: ChunkValidatorStats::default(), + chunk_stats: ChunkStats::default(), }, ), // a validator that expected blocks and chunks are both 0 (this could occur with very @@ -372,7 +372,7 @@ mod tests { "test4".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::default(), + chunk_stats: ChunkStats::default(), }, ), ]); @@ -428,7 +428,7 @@ mod tests { "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 945, expected: 1000 }, - chunk_stats: ChunkValidatorStats { + chunk_stats: ChunkStats { production: ValidatorStats { produced: 944, expected: 1000 }, endorsement: ValidatorStats { produced: 946, expected: 1000 }, }, @@ -439,7 +439,7 @@ mod tests { "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats { + chunk_stats: ChunkStats { production: ValidatorStats { produced: 998, expected: 1000 }, endorsement: ValidatorStats { produced: 1000, expected: 1000 }, }, @@ -450,7 +450,7 @@ mod tests { "test3".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 940, expected: 1000 }, - chunk_stats: ChunkValidatorStats::new_with_endorsement(950, 1000), + chunk_stats: ChunkStats::new_with_endorsement(950, 1000), }, ), // Endorsements only @@ -458,7 +458,7 @@ mod tests { "test4".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::new_with_endorsement(1000, 1000), + chunk_stats: ChunkStats::new_with_endorsement(1000, 1000), }, ), ]); @@ -514,7 +514,7 @@ mod tests { "test".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 43200, expected: 43200 }, - chunk_stats: ChunkValidatorStats { + chunk_stats: ChunkStats { production: ValidatorStats { produced: 345600, expected: 345600 }, endorsement: ValidatorStats { produced: 345600, expected: 345600 }, }, diff --git a/chain/epoch-manager/src/shard_tracker.rs b/chain/epoch-manager/src/shard_tracker.rs index 9d90cb6d649..e9677c5d55c 100644 --- a/chain/epoch-manager/src/shard_tracker.rs +++ b/chain/epoch-manager/src/shard_tracker.rs @@ -10,9 +10,13 @@ use near_primitives::types::{AccountId, EpochId, ShardId}; #[derive(Clone)] pub enum TrackedConfig { + /// Tracks shards that contain one of the given account. Accounts(Vec), + /// Tracks shards that are assigned to given validator account. + ShadowValidator(AccountId), + /// Tracks all shards. AllShards, - // Rotates between sets of shards to track. + /// Rotates between sets of shards to track. Schedule(Vec>), } @@ -26,6 +30,8 @@ impl TrackedConfig { TrackedConfig::AllShards } else if !config.tracked_shard_schedule.is_empty() { TrackedConfig::Schedule(config.tracked_shard_schedule.clone()) + } else if let Some(account_id) = config.tracked_shadow_validator.as_ref() { + TrackedConfig::ShadowValidator(account_id.clone()) } else { TrackedConfig::Accounts(config.tracked_accounts.clone()) } @@ -70,7 +76,7 @@ impl ShardTracker { match &self.tracked_config { TrackedConfig::Accounts(tracked_accounts) => { let shard_layout = self.epoch_manager.get_shard_layout(epoch_id)?; - let tracking_mask = self.tracking_shards_cache.get_or_put(epoch_id.clone(), |_| { + let tracking_mask = self.tracking_shards_cache.get_or_put(*epoch_id, |_| { let mut tracking_mask: Vec<_> = shard_layout.shard_ids().map(|_| false).collect(); for account_id in tracked_accounts { @@ -90,6 +96,9 @@ impl ShardTracker { let subset = &schedule[index as usize]; Ok(subset.contains(&shard_id)) } + TrackedConfig::ShadowValidator(account_id) => { + self.epoch_manager.cares_about_shard_in_epoch(*epoch_id, account_id, shard_id) + } } } @@ -229,6 +238,8 @@ mod tests { avg_hidden_validator_seats_per_shard: vec![], block_producer_kickout_threshold: 90, chunk_producer_kickout_threshold: 60, + chunk_validator_only_kickout_threshold: 60, + target_validator_mandates_per_shard: 1, fishermen_threshold: 0, online_max_threshold: Ratio::from_integer(1), online_min_threshold: Ratio::new(90, 100), diff --git a/chain/epoch-manager/src/test_utils.rs b/chain/epoch-manager/src/test_utils.rs index 5fa5e0e4344..517bf8e049c 100644 --- a/chain/epoch-manager/src/test_utils.rs +++ b/chain/epoch-manager/src/test_utils.rs @@ -12,7 +12,7 @@ use near_crypto::{KeyType, SecretKey}; use near_primitives::challenge::SlashedValidator; use near_primitives::epoch_manager::block_info::BlockInfoV2; use near_primitives::epoch_manager::epoch_info::EpochInfo; -use near_primitives::epoch_manager::{AllEpochConfig, EpochConfig, ValidatorWeight}; +use near_primitives::epoch_manager::{AllEpochConfig, EpochConfig, ValidatorSelectionConfig}; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::types::validator_stake::ValidatorStake; use near_primitives::types::{ @@ -48,12 +48,6 @@ pub fn epoch_info( accounts: Vec<(AccountId, Balance)>, block_producers_settlement: Vec, chunk_producers_settlement: Vec>, - hidden_validators_settlement: Vec, - fishermen: Vec<(AccountId, Balance)>, - stake_change: BTreeMap, - validator_kickout: Vec<(AccountId, ValidatorKickoutReason)>, - validator_reward: HashMap, - minted_amount: Balance, ) -> EpochInfo { let num_seats = block_producers_settlement.len() as u64; epoch_info_with_num_seats( @@ -61,12 +55,10 @@ pub fn epoch_info( accounts, block_producers_settlement, chunk_producers_settlement, - hidden_validators_settlement, - fishermen, - stake_change, - validator_kickout, - validator_reward, - minted_amount, + Default::default(), + Default::default(), + Default::default(), + 0, num_seats, ) } @@ -76,8 +68,6 @@ pub fn epoch_info_with_num_seats( mut accounts: Vec<(AccountId, Balance)>, block_producers_settlement: Vec, chunk_producers_settlement: Vec>, - _hidden_validators_settlement: Vec, - _fishermen: Vec<(AccountId, Balance)>, stake_change: BTreeMap, validator_kickout: Vec<(AccountId, ValidatorKickoutReason)>, validator_reward: HashMap, @@ -134,10 +124,10 @@ pub fn epoch_config_with_production_config( epoch_length: BlockHeightDelta, num_shards: NumShards, num_block_producer_seats: NumSeats, - num_hidden_validator_seats: NumSeats, + num_chunk_producer_seats: NumSeats, block_producer_kickout_threshold: u8, chunk_producer_kickout_threshold: u8, - fishermen_threshold: Balance, + chunk_validator_only_kickout_threshold: u8, use_production_config: bool, ) -> AllEpochConfig { let epoch_config = EpochConfig { @@ -147,17 +137,20 @@ pub fn epoch_config_with_production_config( num_shards, num_block_producer_seats, ), - avg_hidden_validator_seats_per_shard: (0..num_shards) - .map(|_| num_hidden_validator_seats) - .collect(), + avg_hidden_validator_seats_per_shard: vec![], block_producer_kickout_threshold, chunk_producer_kickout_threshold, - fishermen_threshold, + chunk_validator_only_kickout_threshold, + target_validator_mandates_per_shard: 68, + fishermen_threshold: 0, online_min_threshold: Ratio::new(90, 100), online_max_threshold: Ratio::new(99, 100), protocol_upgrade_stake_threshold: Ratio::new(80, 100), minimum_stake_divisor: 1, - validator_selection_config: Default::default(), + validator_selection_config: ValidatorSelectionConfig { + num_chunk_producer_seats, + ..Default::default() + }, shard_layout: ShardLayout::v0(num_shards, 0), validator_max_kickout_stake_perc: 100, }; @@ -168,19 +161,18 @@ pub fn epoch_config( epoch_length: BlockHeightDelta, num_shards: NumShards, num_block_producer_seats: NumSeats, - num_hidden_validator_seats: NumSeats, block_producer_kickout_threshold: u8, chunk_producer_kickout_threshold: u8, - fishermen_threshold: Balance, + chunk_validator_only_kickout_threshold: u8, ) -> AllEpochConfig { epoch_config_with_production_config( epoch_length, num_shards, num_block_producer_seats, - num_hidden_validator_seats, + 100, block_producer_kickout_threshold, chunk_producer_kickout_threshold, - fishermen_threshold, + chunk_validator_only_kickout_threshold, false, ) } @@ -213,10 +205,9 @@ pub fn setup_epoch_manager( epoch_length: BlockHeightDelta, num_shards: NumShards, num_block_producer_seats: NumSeats, - num_hidden_validator_seats: NumSeats, block_producer_kickout_threshold: u8, chunk_producer_kickout_threshold: u8, - fishermen_threshold: Balance, + chunk_validator_only_kickout_threshold: u8, reward_calculator: RewardCalculator, ) -> EpochManager { let store = create_test_store(); @@ -224,10 +215,9 @@ pub fn setup_epoch_manager( epoch_length, num_shards, num_block_producer_seats, - num_hidden_validator_seats, block_producer_kickout_threshold, chunk_producer_kickout_threshold, - fishermen_threshold, + chunk_validator_only_kickout_threshold, ); EpochManager::new( store, @@ -247,7 +237,6 @@ pub fn setup_default_epoch_manager( epoch_length: BlockHeightDelta, num_shards: NumShards, num_block_producer_seats: NumSeats, - num_hidden_validator_seats: NumSeats, block_producer_kickout_threshold: u8, chunk_producer_kickout_threshold: u8, ) -> EpochManager { @@ -256,10 +245,9 @@ pub fn setup_default_epoch_manager( epoch_length, num_shards, num_block_producer_seats, - num_hidden_validator_seats, block_producer_kickout_threshold, chunk_producer_kickout_threshold, - 1, + 0, default_reward_calculator(), ) } @@ -293,7 +281,7 @@ pub fn setup_epoch_manager_with_block_and_chunk_producers( validators.push((chunk_only_producer.clone(), stake)); total_stake += stake; } - let config = epoch_config(epoch_length, num_shards, num_block_producers, 0, 0, 0, 0); + let config = epoch_config(epoch_length, num_shards, num_block_producers, 0, 0, 0); let epoch_manager = EpochManager::new( store, config, diff --git a/chain/epoch-manager/src/tests/mod.rs b/chain/epoch-manager/src/tests/mod.rs index 2be045d7b69..26bb366a5a2 100644 --- a/chain/epoch-manager/src/tests/mod.rs +++ b/chain/epoch-manager/src/tests/mod.rs @@ -9,6 +9,7 @@ use crate::test_utils::{ record_with_block_info, reward, setup_default_epoch_manager, setup_epoch_manager, stake, DEFAULT_TOTAL_SUPPLY, }; +use itertools::Itertools; use near_o11y::testonly::init_test_logger; use near_primitives::account::id::AccountIdRef; use near_primitives::block::Tip; @@ -19,9 +20,11 @@ use near_primitives::hash::hash; use near_primitives::shard_layout::ShardLayout; use near_primitives::sharding::{ShardChunkHeader, ShardChunkHeaderV3}; use near_primitives::stateless_validation::PartialEncodedStateWitness; -use near_primitives::types::ValidatorKickoutReason::{NotEnoughBlocks, NotEnoughChunks}; +use near_primitives::types::ValidatorKickoutReason::{ + NotEnoughBlocks, NotEnoughChunkEndorsements, NotEnoughChunks, +}; use near_primitives::validator_signer::ValidatorSigner; -use near_primitives::version::ProtocolFeature::SimpleNightshade; +use near_primitives::version::ProtocolFeature::{self, SimpleNightshade, StatelessValidationV0}; use near_primitives::version::PROTOCOL_VERSION; use near_store::test_utils::create_test_store; use num_rational::Ratio; @@ -37,7 +40,7 @@ impl EpochManager { let epoch_info = self.get_epoch_info(epoch_id)?; let validator_id = *epoch_info .get_validator_id(account_id) - .ok_or_else(|| EpochError::NotAValidator(account_id.clone(), epoch_id.clone()))?; + .ok_or_else(|| EpochError::NotAValidator(account_id.clone(), *epoch_id))?; let aggregator = self.get_epoch_info_aggregator_upto_last(last_known_block_hash)?; Ok(aggregator .block_tracker @@ -51,7 +54,7 @@ impl EpochManager { fn test_stake_validator() { let amount_staked = 1_000_000; let validators = vec![("test1".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_default_epoch_manager(validators.clone(), 1, 1, 2, 2, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators.clone(), 1, 1, 2, 90, 60); let h = hash_range(4); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); @@ -61,8 +64,6 @@ fn test_stake_validator() { vec![("test1".parse().unwrap(), amount_staked)], vec![0, 0], vec![vec![0, 0]], - vec![], - vec![], change_stake(vec![("test1".parse().unwrap(), amount_staked)]), vec![], reward(vec![("near".parse().unwrap(), 0)]), @@ -102,8 +103,6 @@ fn test_stake_validator() { vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)], vec![0, 1], vec![vec![0, 1]], - vec![], - vec![], change_stake(vec![ ("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked), @@ -136,20 +135,10 @@ fn test_stake_validator() { #[test] fn test_validator_change_of_stake() { let amount_staked = 1_000_000; - let fishermen_threshold = 100; let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_epoch_manager( - validators, - 2, - 1, - 2, - 0, - 90, - 60, - fishermen_threshold, - default_reward_calculator(), - ); + let mut epoch_manager = + setup_epoch_manager(validators, 2, 1, 2, 90, 60, 0, default_reward_calculator()); let h = hash_range(4); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); @@ -196,7 +185,7 @@ fn test_fork_finalization() { ]; let epoch_length = 20; let mut epoch_manager = - setup_default_epoch_manager(validators.clone(), epoch_length, 1, 3, 0, 90, 60); + setup_default_epoch_manager(validators.clone(), epoch_length, 1, 3, 90, 60); let h = hash_range((5 * epoch_length - 1) as usize); // Have an alternate set of hashes to use on the other branch to avoid collisions. @@ -282,7 +271,7 @@ fn test_fork_finalization() { ); // Check that if we have a different epoch manager and apply only second branch we get the same results. - let mut epoch_manager2 = setup_default_epoch_manager(validators, epoch_length, 1, 3, 0, 90, 60); + let mut epoch_manager2 = setup_default_epoch_manager(validators, epoch_length, 1, 3, 90, 60); record_block(&mut epoch_manager2, CryptoHash::default(), h[0], 0, vec![]); build_branch(&mut epoch_manager2, h[0], &h2, &["test1", "test3"]); assert_eq!(epoch_manager.get_epoch_info(&epoch2_2), epoch_manager2.get_epoch_info(&epoch2_2)); @@ -300,7 +289,6 @@ fn test_one_validator_kickout() { 2, 1, 1, - 0, 90, 60, ); @@ -328,7 +316,7 @@ fn test_validator_kickout() { let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; let epoch_length = 10; - let mut epoch_manager = setup_default_epoch_manager(validators, epoch_length, 1, 2, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, epoch_length, 1, 2, 90, 60); let h = hash_range((3 * epoch_length) as usize); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); @@ -354,13 +342,7 @@ fn test_validator_kickout() { h.iter().filter_map(|x| epoch_manager.get_epoch_info(&EpochId(*x)).ok()).collect(); check_kickout( &epoch_infos[1], - &[( - "test2", - ValidatorKickoutReason::NotEnoughBlocks { - produced: 0, - expected: test2_expected_blocks, - }, - )], + &[("test2", NotEnoughBlocks { produced: 0, expected: test2_expected_blocks })], ); let epoch_info = &epoch_infos[2]; check_validators(epoch_info, &[("test1", amount_staked)]); @@ -380,7 +362,7 @@ fn test_validator_kickout() { #[test] fn test_validator_unstake() { let store = create_test_store(); - let config = epoch_config(2, 1, 2, 0, 90, 60, 0); + let config = epoch_config(2, 1, 2, 90, 60, 0); let amount_staked = 1_000_000; let validators = vec![ stake("test1".parse().unwrap(), amount_staked), @@ -445,7 +427,7 @@ fn test_validator_unstake() { #[test] fn test_slashing() { let store = create_test_store(); - let config = epoch_config(2, 1, 2, 0, 90, 60, 0); + let config = epoch_config(2, 1, 2, 90, 60, 0); let amount_staked = 1_000_000; let validators = vec![ stake("test1".parse().unwrap(), amount_staked), @@ -512,7 +494,7 @@ fn test_slashing() { #[test] fn test_double_sign_slashing1() { let store = create_test_store(); - let config = epoch_config(2, 1, 2, 0, 90, 60, 0); + let config = epoch_config(2, 1, 2, 90, 60, 0); let amount_staked = 1_000_000; let validators = vec![ stake("test1".parse().unwrap(), amount_staked), @@ -590,7 +572,7 @@ fn test_double_sign_slashing2() { let amount_staked = 1_000_000; let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 90, 60); let h = hash_range(10); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); @@ -634,7 +616,7 @@ fn test_all_validators_unstake() { ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 90, 60); let h = hash_range(5); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); // all validators are trying to unstake. @@ -681,17 +663,8 @@ fn test_validator_reward_one_validator() { online_max_threshold: Ratio::new(99, 100), num_seconds_per_year: 50, }; - let mut epoch_manager = setup_epoch_manager( - validators, - epoch_length, - 1, - 1, - 0, - 90, - 60, - 100, - reward_calculator.clone(), - ); + let mut epoch_manager = + setup_epoch_manager(validators, epoch_length, 1, 1, 90, 60, 0, reward_calculator.clone()); let rng_seed = [0; 32]; let h = hash_range(5); @@ -727,7 +700,7 @@ fn test_validator_reward_one_validator() { "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 1, expected: 1 }, - chunk_stats: ChunkValidatorStats::new_with_production(1, 1), + chunk_stats: ChunkStats::new_with_production(1, 1), }, ); let mut validator_stakes = HashMap::new(); @@ -773,17 +746,8 @@ fn test_validator_reward_weight_by_stake() { online_max_threshold: Ratio::new(99, 100), num_seconds_per_year: 50, }; - let mut epoch_manager = setup_epoch_manager( - validators, - epoch_length, - 1, - 2, - 0, - 90, - 60, - 100, - reward_calculator.clone(), - ); + let mut epoch_manager = + setup_epoch_manager(validators, epoch_length, 1, 2, 90, 60, 0, reward_calculator.clone()); let h = hash_range(5); record_with_block_info( &mut epoch_manager, @@ -811,14 +775,14 @@ fn test_validator_reward_weight_by_stake() { "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 1, expected: 1 }, - chunk_stats: ChunkValidatorStats::new_with_production(1, 1), + chunk_stats: ChunkStats::new_with_production(1, 1), }, ); validator_online_ratio.insert( "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 1, expected: 1 }, - chunk_stats: ChunkValidatorStats::new_with_production(1, 1), + chunk_stats: ChunkStats::new_with_production(1, 1), }, ); let mut validators_stakes = HashMap::new(); @@ -885,7 +849,6 @@ fn test_reward_multiple_shards() { epoch_length, num_shards, 2, - 0, 90, 60, 0, @@ -934,7 +897,7 @@ fn test_reward_multiple_shards() { "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 1, expected: 1 }, - chunk_stats: ChunkValidatorStats::new_with_production(1, 1), + chunk_stats: ChunkStats::new_with_production(1, 1), }, ); let mut validators_stakes = HashMap::new(); @@ -964,10 +927,7 @@ fn test_reward_multiple_shards() { ); check_kickout( epoch_info, - &[( - "test1", - ValidatorKickoutReason::NotEnoughChunks { produced: 0, expected: expected_chunks }, - )], + &[("test1", NotEnoughChunks { produced: 0, expected: expected_chunks })], ); check_reward( epoch_info, @@ -981,7 +941,7 @@ fn test_unstake_and_then_change_stake() { let amount_staked = 1_000_000; let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 90, 60); let h = hash_range(8); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); // test1 unstakes in epoch 1, and should be kicked out in epoch 3 (validators stored at h2). @@ -1014,29 +974,35 @@ fn test_unstake_and_then_change_stake() { ); } -/// When a block producer fails to produce a block, check that other chunk producers who produce -/// chunks for that block are not kicked out because of it. +/// When a block producer fails to produce a block, check that other chunk +/// producers and validators who produce chunks for that block are not kicked +/// out because of it. #[test] fn test_expected_chunks() { let stake_amount = 1_000_000; - let validators = vec![ + let validators: Vec<(AccountId, u128)> = vec![ ("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), + ("test4".parse().unwrap(), stake_amount), ]; let epoch_length = 20; + let num_shards = 3; let total_supply = stake_amount * validators.len() as u128; - let mut epoch_manager = setup_epoch_manager( - validators, - epoch_length, - 3, - 3, - 0, - 90, - 60, - 0, + + let epoch_config = + epoch_config_with_production_config(epoch_length, num_shards, 3, 3, 90, 60, 60, false); + let mut epoch_manager = EpochManager::new( + create_test_store(), + epoch_config, + PROTOCOL_VERSION, default_reward_calculator(), - ); + validators + .iter() + .map(|(account_id, balance)| stake(account_id.clone(), *balance)) + .collect(), + ) + .unwrap(); let rng_seed = [0; 32]; let hashes = hash_range((2 * epoch_length) as usize); record_block(&mut epoch_manager, Default::default(), hashes[0], 0, vec![]); @@ -1051,26 +1017,28 @@ fn test_expected_chunks() { // test1 does not produce blocks during first epoch if block_producer == 0 && epoch_id == initial_epoch_id { expected += 1; - } else { - epoch_manager - .record_block_info( - block_info( - *curr_block, - height, - height, - prev_block, - prev_block, - epoch_id.0, - vec![true, true, true], - total_supply, - ), - rng_seed, - ) - .unwrap() - .commit() - .unwrap(); - prev_block = *curr_block; + continue; } + + epoch_manager + .record_block_info( + block_info( + *curr_block, + height, + height, + prev_block, + prev_block, + epoch_id.0, + vec![true, true, true], + total_supply, + ), + rng_seed, + ) + .unwrap() + .commit() + .unwrap(); + prev_block = *curr_block; + if epoch_id != initial_epoch_id { break; } @@ -1082,12 +1050,9 @@ fn test_expected_chunks() { .unwrap(); assert_eq!( epoch_info.validator_kickout(), - &[( - "test1".parse::().unwrap(), - ValidatorKickoutReason::NotEnoughBlocks { produced: 0, expected } - )] - .into_iter() - .collect::>() + &[("test1".parse::().unwrap(), NotEnoughBlocks { produced: 0, expected })] + .into_iter() + .collect::>() ); } @@ -1101,17 +1066,8 @@ fn test_expected_chunks_prev_block_not_produced() { ]; let epoch_length = 50; let total_supply = stake_amount * validators.len() as u128; - let mut epoch_manager = setup_epoch_manager( - validators, - epoch_length, - 1, - 3, - 0, - 90, - 90, - 0, - default_reward_calculator(), - ); + let mut epoch_manager = + setup_epoch_manager(validators, epoch_length, 1, 3, 90, 90, 0, default_reward_calculator()); let rng_seed = [0; 32]; let hashes = hash_range((2 * epoch_length) as usize); record_block(&mut epoch_manager, Default::default(), hashes[0], 0, vec![]); @@ -1163,12 +1119,9 @@ fn test_expected_chunks_prev_block_not_produced() { .unwrap(); assert_eq!( epoch_info.validator_kickout(), - &[( - "test1".parse().unwrap(), - ValidatorKickoutReason::NotEnoughBlocks { produced: 0, expected } - )] - .into_iter() - .collect::>() + &[("test1".parse().unwrap(), NotEnoughBlocks { produced: 0, expected })] + .into_iter() + .collect::>() ); } @@ -1208,8 +1161,7 @@ fn test_rewards_with_kickouts() { online_max_threshold: Ratio::new(99, 100), num_seconds_per_year: NUM_SECONDS_IN_A_YEAR, }; - let mut em = - setup_epoch_manager(validators, epoch_length, 1, 3, 0, 10, 10, 0, reward_calculator); + let mut em = setup_epoch_manager(validators, epoch_length, 1, 3, 10, 10, 0, reward_calculator); let mut height: BlockHeight = 0; let genesis_hash = hash(height.to_le_bytes().as_ref()); @@ -1315,17 +1267,8 @@ fn test_epoch_info_aggregator() { let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let epoch_length = 5; - let mut em = setup_epoch_manager( - validators, - epoch_length, - 1, - 2, - 0, - 10, - 10, - 0, - default_reward_calculator(), - ); + let mut em = + setup_epoch_manager(validators, epoch_length, 1, 2, 10, 10, 0, default_reward_calculator()); let h = hash_range(6); record_block(&mut em, Default::default(), h[0], 0, vec![]); record_block_with_final_block_hash(&mut em, h[0], h[1], h[0], 1, vec![]); @@ -1360,17 +1303,8 @@ fn test_epoch_info_aggregator_data_loss() { let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let epoch_length = 5; - let mut em = setup_epoch_manager( - validators, - epoch_length, - 1, - 2, - 0, - 10, - 10, - 0, - default_reward_calculator(), - ); + let mut em = + setup_epoch_manager(validators, epoch_length, 1, 2, 10, 10, 0, default_reward_calculator()); let h = hash_range(6); record_block(&mut em, Default::default(), h[0], 0, vec![]); record_block(&mut em, h[0], h[1], 1, vec![stake("test1".parse().unwrap(), stake_amount - 10)]); @@ -1404,17 +1338,8 @@ fn test_epoch_info_aggregator_reorg_past_final_block() { let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let epoch_length = 6; - let mut em = setup_epoch_manager( - validators, - epoch_length, - 1, - 2, - 0, - 10, - 10, - 0, - default_reward_calculator(), - ); + let mut em = + setup_epoch_manager(validators, epoch_length, 1, 2, 10, 10, 0, default_reward_calculator()); let h = hash_range(6); record_block(&mut em, Default::default(), h[0], 0, vec![]); record_block_with_final_block_hash(&mut em, h[0], h[1], h[0], 1, vec![]); @@ -1444,17 +1369,8 @@ fn test_epoch_info_aggregator_reorg_beginning_of_epoch() { let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let epoch_length = 4; - let mut em = setup_epoch_manager( - validators, - epoch_length, - 1, - 2, - 0, - 10, - 10, - 0, - default_reward_calculator(), - ); + let mut em = + setup_epoch_manager(validators, epoch_length, 1, 2, 10, 10, 0, default_reward_calculator()); let h = hash_range(10); record_block(&mut em, Default::default(), h[0], 0, vec![]); for i in 1..5 { @@ -1506,17 +1422,8 @@ fn test_num_missing_blocks() { let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let epoch_length = 2; - let mut em = setup_epoch_manager( - validators, - epoch_length, - 1, - 2, - 0, - 10, - 10, - 0, - default_reward_calculator(), - ); + let mut em = + setup_epoch_manager(validators, epoch_length, 1, 2, 10, 10, 0, default_reward_calculator()); let h = hash_range(8); record_block(&mut em, Default::default(), h[0], 0, vec![]); record_block(&mut em, h[0], h[1], 1, vec![]); @@ -1553,26 +1460,17 @@ fn test_num_missing_blocks() { ); } -/// Test when blocks are all produced, validators can be kicked out because of not producing -/// enough chunks +/// Test when blocks are all produced, not producing chunks leads to chunk +/// producer kickout. #[test] -fn test_chunk_validator_kickout() { +fn test_chunk_producer_kickout() { let stake_amount = 1_000_000; let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let epoch_length = 10; let total_supply = stake_amount * validators.len() as u128; - let mut em = setup_epoch_manager( - validators, - epoch_length, - 4, - 2, - 0, - 90, - 70, - 0, - default_reward_calculator(), - ); + let mut em = + setup_epoch_manager(validators, epoch_length, 4, 2, 90, 70, 0, default_reward_calculator()); let rng_seed = [0; 32]; let hashes = hash_range((epoch_length + 2) as usize); record_block(&mut em, Default::default(), hashes[0], 0, vec![]); @@ -1581,62 +1479,119 @@ fn test_chunk_validator_kickout() { let height = height as u64; let epoch_id = em.get_epoch_id_from_prev_block(prev_block).unwrap(); let epoch_info = em.get_epoch_info(&epoch_id).unwrap().clone(); - if height < epoch_length { - let chunk_mask = (0..4) - .map(|shard_id| { - let chunk_producer = EpochManager::chunk_producer_from_info( - &epoch_info, - height, - shard_id as u64, - ) - .unwrap(); - // test1 skips chunks - if chunk_producer == 0 { - expected += 1; - false - } else { - true - } - }) - .collect(); - em.record_block_info( - block_info( - *curr_block, - height, - height - 1, - *prev_block, - *prev_block, - epoch_id.0, - chunk_mask, - total_supply, - ), - rng_seed, - ) - .unwrap(); + let chunk_mask = (0..4) + .map(|shard_id| { + if height >= epoch_length { + return true; + } + + let chunk_producer = + EpochManager::chunk_producer_from_info(&epoch_info, height, shard_id as u64) + .unwrap(); + // test1 skips chunks + if chunk_producer == 0 { + expected += 1; + false + } else { + true + } + }) + .collect(); + + em.record_block_info( + block_info( + *curr_block, + height, + height - 1, + *prev_block, + *prev_block, + epoch_id.0, + chunk_mask, + total_supply, + ), + rng_seed, + ) + .unwrap(); + } + + let last_epoch_info = hashes.iter().filter_map(|x| em.get_epoch_info(&EpochId(*x)).ok()).last(); + assert_eq!( + last_epoch_info.unwrap().validator_kickout(), + &[("test1".parse().unwrap(), NotEnoughChunks { produced: 0, expected })] + .into_iter() + .collect::>(), + ); +} + +/// Test when all blocks are produced and all chunks are skipped, chunk +/// validator is not kicked out. +#[test] +fn test_chunk_validator_kickout() { + if !StatelessValidationV0.enabled(PROTOCOL_VERSION) { + return; + } + let stake_amount = 1_000_000; + let validators: Vec<(AccountId, Balance)> = + (0..3).map(|i| (format!("test{i}").parse().unwrap(), stake_amount + 100 - i)).collect(); + let epoch_length = 10; + let total_supply = stake_amount * validators.len() as u128; + let num_shards = 2; + let epoch_config = + epoch_config_with_production_config(epoch_length, num_shards, 2, 2, 90, 40, 75, false); + let mut em = EpochManager::new( + create_test_store(), + epoch_config, + PROTOCOL_VERSION, + default_reward_calculator(), + validators + .iter() + .map(|(account_id, balance)| stake(account_id.clone(), *balance)) + .collect(), + ) + .unwrap(); + let rng_seed = [0; 32]; + let hashes = hash_range((epoch_length + 2) as usize); + record_block(&mut em, Default::default(), hashes[0], 0, vec![]); + for (prev_block, (height, curr_block)) in hashes.iter().zip(hashes.iter().enumerate().skip(1)) { + let height = height as u64; + let epoch_id = em.get_epoch_id_from_prev_block(prev_block).unwrap(); + let chunk_mask = if height < epoch_length { + (0..num_shards).map(|i| (height + i) % 2 == 0).collect() } else { - em.record_block_info( - block_info( - *curr_block, - height, - height - 1, - *prev_block, - *prev_block, - epoch_id.0, - vec![true, true, true, true], - total_supply, - ), - rng_seed, - ) - .unwrap(); - } + vec![true; num_shards as usize] + }; + em.record_block_info( + block_info( + *curr_block, + height, + height - 1, + *prev_block, + *prev_block, + epoch_id.0, + chunk_mask, + total_supply, + ), + rng_seed, + ) + .unwrap(); } let last_epoch_info = hashes.iter().filter_map(|x| em.get_epoch_info(&EpochId(*x)).ok()).last(); + let total_expected_chunks = num_shards * (epoch_length - 1); + // Every second chunk is skipped. + let total_produced_chunks = total_expected_chunks / 2; + + // Chunk producers skip only every second chunk and pass the threshold. + // Chunk validator validates all chunks, so its performance is determined + // by the chunk production ratio, which is not enough. assert_eq!( last_epoch_info.unwrap().validator_kickout(), &[( - "test1".parse().unwrap(), - ValidatorKickoutReason::NotEnoughChunks { produced: 0, expected } + "test2".parse().unwrap(), + NotEnoughChunkEndorsements { + produced: total_produced_chunks, + expected: total_expected_chunks + } )] .into_iter() .collect::>(), @@ -1648,7 +1603,7 @@ fn test_compare_epoch_id() { let amount_staked = 1_000_000; let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 90, 60); let h = hash_range(8); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); // test1 unstakes in epoch 1, and should be kicked out in epoch 3 (validators stored at h2). @@ -1683,17 +1638,8 @@ fn test_fishermen() { ("test4".parse().unwrap(), fishermen_threshold / 2), ]; let epoch_length = 4; - let em = setup_epoch_manager( - validators, - epoch_length, - 1, - 4, - 0, - 90, - 70, - fishermen_threshold, - default_reward_calculator(), - ); + let em = + setup_epoch_manager(validators, epoch_length, 1, 4, 90, 70, 0, default_reward_calculator()); let epoch_info = em.get_epoch_info(&EpochId::default()).unwrap(); check_validators(&epoch_info, &[("test1", stake_amount), ("test2", stake_amount)]); check_fishermen(&epoch_info, &[]); @@ -1718,17 +1664,7 @@ fn test_fishermen_unstake() { ("test2".parse().unwrap(), fishermen_threshold), ("test3".parse().unwrap(), fishermen_threshold), ]; - let mut em = setup_epoch_manager( - validators, - 2, - 1, - 1, - 0, - 90, - 70, - fishermen_threshold, - default_reward_calculator(), - ); + let mut em = setup_epoch_manager(validators, 2, 1, 1, 90, 70, 0, default_reward_calculator()); let h = hash_range(5); record_block(&mut em, CryptoHash::default(), h[0], 0, vec![]); // fishermen unstake @@ -1759,7 +1695,7 @@ fn test_validator_consistency() { let stake_amount = 1_000; let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 1, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 1, 90, 60); let h = hash_range(5); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); let epoch_id = epoch_manager.get_epoch_id(&h[0]).unwrap(); @@ -1786,7 +1722,7 @@ fn test_finalize_epoch_large_epoch_length() { let validators = vec![("test1".parse().unwrap(), stake_amount), ("test2".parse().unwrap(), stake_amount)]; let mut epoch_manager = - setup_default_epoch_manager(validators, (BLOCK_CACHE_SIZE + 1) as u64, 1, 2, 0, 90, 60); + setup_default_epoch_manager(validators, (BLOCK_CACHE_SIZE + 1) as u64, 1, 2, 90, 60); let h = hash_range(BLOCK_CACHE_SIZE + 2); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); for i in 1..=(BLOCK_CACHE_SIZE + 1) { @@ -1820,7 +1756,7 @@ fn test_kickout_set() { ("test3".parse().unwrap(), 10), ]; // have two seats to that 500 would be the threshold - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 2, 90, 60); let h = hash_range(5); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block( @@ -1867,7 +1803,7 @@ fn test_epoch_height_increase() { ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 90, 60); let h = hash_range(5); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block(&mut epoch_manager, h[0], h[2], 2, vec![stake("test1".parse().unwrap(), 223)]); @@ -1887,7 +1823,7 @@ fn test_unstake_slash() { ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 90, 60); let h = hash_range(9); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block(&mut epoch_manager, h[0], h[1], 1, vec![stake("test1".parse().unwrap(), 0)]); @@ -1937,7 +1873,7 @@ fn test_no_unstake_slash() { ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 90, 60); let h = hash_range(9); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block_with_slashes( @@ -1987,7 +1923,7 @@ fn test_slash_non_validator() { ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 90, 60); let h = hash_range(9); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block(&mut epoch_manager, h[0], h[1], 1, vec![stake("test1".parse().unwrap(), 0)]); @@ -2040,7 +1976,7 @@ fn test_slash_restake() { ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 1, 1, 3, 90, 60); let h = hash_range(9); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block_with_slashes( @@ -2081,7 +2017,7 @@ fn test_all_kickout_edge_case() { ("test3".parse().unwrap(), stake_amount), ]; const EPOCH_LENGTH: u64 = 10; - let mut epoch_manager = setup_default_epoch_manager(validators, EPOCH_LENGTH, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, EPOCH_LENGTH, 1, 3, 90, 60); let hashes = hash_range((8 * EPOCH_LENGTH + 1) as usize); record_block(&mut epoch_manager, CryptoHash::default(), hashes[0], 0, vec![]); @@ -2173,7 +2109,7 @@ fn set_block_info_protocol_version(info: &mut BlockInfo, protocol_version: Proto #[test] fn test_protocol_version_switch() { let store = create_test_store(); - let config = epoch_config(2, 1, 2, 0, 90, 60, 0); + let config = epoch_config(2, 1, 2, 90, 60, 0); let amount_staked = 1_000_000; let validators = vec![ stake("test1".parse().unwrap(), amount_staked), @@ -2199,7 +2135,7 @@ fn test_protocol_version_switch() { #[test] fn test_protocol_version_switch_with_shard_layout_change() { let store = create_test_store(); - let config = epoch_config_with_production_config(2, 1, 2, 0, 90, 60, 0, true); + let config = epoch_config_with_production_config(2, 1, 2, 100, 90, 60, 0, true); let amount_staked = 1_000_000; let validators = vec![ stake("test1".parse().unwrap(), amount_staked), @@ -2269,6 +2205,8 @@ fn test_protocol_version_switch_with_many_seats() { avg_hidden_validator_seats_per_shard: Vec::from([0]), block_producer_kickout_threshold: 90, chunk_producer_kickout_threshold: 60, + chunk_validator_only_kickout_threshold: 60, + target_validator_mandates_per_shard: 10, fishermen_threshold: 0, online_min_threshold: Ratio::new(90, 100), online_max_threshold: Ratio::new(99, 100), @@ -2308,7 +2246,7 @@ fn test_protocol_version_switch_with_many_seats() { fn test_protocol_version_switch_after_switch() { let store = create_test_store(); let epoch_length: usize = 10; - let config = epoch_config(epoch_length as u64, 1, 2, 0, 90, 60, 0); + let config = epoch_config(epoch_length as u64, 1, 2, 90, 60, 0); let amount_staked = 1_000_000; let validators = vec![ stake("test1".parse().unwrap(), amount_staked), @@ -2387,7 +2325,7 @@ fn test_final_block_consistency() { let amount_staked = 1_000_000; let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_default_epoch_manager(validators, 10, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 10, 1, 3, 90, 60); let h = hash_range(10); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); @@ -2421,7 +2359,7 @@ fn test_epoch_validators_cache() { let amount_staked = 1_000_000; let validators = vec![("test1".parse().unwrap(), amount_staked), ("test2".parse().unwrap(), amount_staked)]; - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 10, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 1, 10, 90, 60); let h = hash_range(10); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); for i in 1..4 { @@ -2458,7 +2396,7 @@ fn test_chunk_producers() { // There are 2 shards, and 2 block producers seats. // So test1 and test2 should become block producers, and chunk_only should become chunk only producer. - let mut epoch_manager = setup_default_epoch_manager(validators, 2, 2, 2, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 2, 2, 2, 90, 60); let h = hash_range(10); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); for i in 1..=4 { @@ -2490,70 +2428,183 @@ fn test_chunk_producers() { ); } -/// A sanity test for the compute_kickout_info function, tests that -/// the validators that don't meet the block/chunk producer kickout threshold is kicked out +/// A sanity test for the compute_validators_to_reward_and_kickout function, +/// checks that validators that don't meet their kickout thresholds are kicked out. #[test] fn test_validator_kickout_sanity() { - let epoch_config = epoch_config(5, 2, 4, 0, 90, 80, 0).for_protocol_version(PROTOCOL_VERSION); + let epoch_config = epoch_config_with_production_config(5, 2, 4, 4, 90, 80, 90, false) + .for_protocol_version(PROTOCOL_VERSION); let accounts = vec![ ("test0".parse().unwrap(), 1000), ("test1".parse().unwrap(), 1000), ("test2".parse().unwrap(), 1000), ("test3".parse().unwrap(), 1000), ("test4".parse().unwrap(), 500), + ("test5".parse().unwrap(), 500), ]; - let epoch_info = epoch_info( - 0, - accounts, - vec![0, 1, 2, 3], - vec![vec![0, 1, 2], vec![0, 1, 3, 4]], - vec![], - vec![], - BTreeMap::new(), - vec![], - HashMap::new(), - 0, + let epoch_info = epoch_info(0, accounts, vec![0, 1, 2, 3], vec![vec![0, 1, 2], vec![0, 1, 3]]); + let block_validator_tracker = HashMap::from([ + (0, ValidatorStats { produced: 100, expected: 100 }), + (1, ValidatorStats { produced: 90, expected: 100 }), + (2, ValidatorStats { produced: 100, expected: 100 }), + (3, ValidatorStats { produced: 89, expected: 100 }), + ]); + let chunk_stats_tracker = HashMap::from([ + ( + 0, + HashMap::from([ + (0, ChunkStats::new_with_production(100, 100)), + ( + 1, + ChunkStats { + production: ValidatorStats { produced: 80, expected: 100 }, + // Note that test1 would not pass chunk endorsement + // threshold, but it is applied to nodes which are only + // chunk validators. + endorsement: ValidatorStats { produced: 0, expected: 100 }, + }, + ), + (2, ChunkStats::new_with_production(70, 100)), + (5, ChunkStats::new_with_endorsement(91, 100)), + ]), + ), + ( + 1, + HashMap::from([ + (0, ChunkStats::new_with_production(70, 100)), + ( + 1, + ChunkStats { + production: ValidatorStats { produced: 81, expected: 100 }, + endorsement: ValidatorStats { produced: 1, expected: 100 }, + }, + ), + (3, ChunkStats::new_with_production(100, 100)), + // test4 is only a chunk validator and should be kicked out. + (4, ChunkStats::new_with_endorsement(89, 100)), + ]), + ), + ]); + let (validator_stats, kickouts) = EpochManager::compute_validators_to_reward_and_kickout( + &epoch_config, + &epoch_info, + &block_validator_tracker, + &chunk_stats_tracker, + &HashMap::new(), + &HashMap::new(), + ); + assert_eq!( + kickouts, + HashMap::from([ + ("test2".parse().unwrap(), NotEnoughChunks { produced: 70, expected: 100 }), + ("test3".parse().unwrap(), NotEnoughBlocks { produced: 89, expected: 100 }), + ("test4".parse().unwrap(), NotEnoughChunkEndorsements { produced: 89, expected: 100 }), + ]) + ); + let expected_validator_stats: HashMap = HashMap::from([ + ( + "test0".parse().unwrap(), + BlockChunkValidatorStats { + block_stats: ValidatorStats { produced: 100, expected: 100 }, + chunk_stats: ChunkStats::new_with_production(170, 200), + }, + ), + ( + "test1".parse().unwrap(), + BlockChunkValidatorStats { + block_stats: ValidatorStats { produced: 90, expected: 100 }, + chunk_stats: ChunkStats { + production: ValidatorStats { produced: 161, expected: 200 }, + endorsement: ValidatorStats { produced: 1, expected: 200 }, + }, + }, + ), + ( + "test2".parse().unwrap(), + BlockChunkValidatorStats { + block_stats: ValidatorStats { produced: 100, expected: 100 }, + chunk_stats: ChunkStats::new_with_production(70, 100), + }, + ), + ( + "test3".parse().unwrap(), + BlockChunkValidatorStats { + block_stats: ValidatorStats { produced: 89, expected: 100 }, + chunk_stats: ChunkStats::new_with_production(100, 100), + }, + ), + ( + "test4".parse().unwrap(), + BlockChunkValidatorStats { + block_stats: ValidatorStats { produced: 0, expected: 0 }, + chunk_stats: ChunkStats::new_with_endorsement(89, 100), + }, + ), + ( + "test5".parse().unwrap(), + BlockChunkValidatorStats { + block_stats: ValidatorStats { produced: 0, expected: 0 }, + chunk_stats: ChunkStats::new_with_endorsement(91, 100), + }, + ), + ]); + assert_eq!( + validator_stats.keys().sorted().collect_vec(), + expected_validator_stats.keys().sorted().collect_vec() ); - let (kickouts, validator_stats) = EpochManager::compute_kickout_info( + for account_id in validator_stats.keys() { + assert_eq!( + validator_stats.get(account_id).unwrap(), + expected_validator_stats.get(account_id).unwrap(), + "Validator stats mismatch for account_id: {account_id}" + ); + } +} + +/// We include some validators that are both block/chunk producers and also chunk validators +/// as well as some validators that are only chunk validators. +/// This test does not test kickouts at all. +#[test] +fn test_chunk_endorsement_stats() { + let epoch_config = epoch_config(5, 2, 4, 90, 80, 0).for_protocol_version(PROTOCOL_VERSION); + let accounts = vec![ + ("test0".parse().unwrap(), 1000), + ("test1".parse().unwrap(), 1000), + ("test2".parse().unwrap(), 1000), + ("test3".parse().unwrap(), 1000), + ]; + let epoch_info = epoch_info(0, accounts, vec![0, 1, 2, 3], vec![vec![0, 1, 2], vec![0, 1, 3]]); + let (validator_stats, kickouts) = EpochManager::compute_validators_to_reward_and_kickout( &epoch_config, &epoch_info, &HashMap::from([ (0, ValidatorStats { produced: 100, expected: 100 }), (1, ValidatorStats { produced: 90, expected: 100 }), - (2, ValidatorStats { produced: 100, expected: 100 }), - // test3 will be kicked out - (3, ValidatorStats { produced: 89, expected: 100 }), ]), &HashMap::from([ ( 0, HashMap::from([ - (0, ChunkValidatorStats::new_with_production(100, 100)), - (1, ChunkValidatorStats::new_with_production(80, 100)), - (2, ChunkValidatorStats::new_with_production(70, 100)), + (0, ChunkStats::new(100, 100, 100, 100)), + (1, ChunkStats::new(90, 100, 100, 100)), + (2, ChunkStats::new_with_endorsement(100, 100)), + (3, ChunkStats::new_with_endorsement(95, 100)), ]), ), ( 1, HashMap::from([ - (0, ChunkValidatorStats::new_with_production(70, 100)), - (1, ChunkValidatorStats::new_with_production(79, 100)), - (3, ChunkValidatorStats::new_with_production(100, 100)), - (4, ChunkValidatorStats::new_with_production(100, 100)), + (0, ChunkStats::new(95, 100, 100, 100)), + (1, ChunkStats::new(95, 100, 90, 100)), + (2, ChunkStats::new_with_endorsement(95, 100)), + (3, ChunkStats::new_with_endorsement(90, 100)), ]), ), ]), &HashMap::new(), &HashMap::new(), ); - assert_eq!( - kickouts, - HashMap::from([ - ("test1".parse().unwrap(), NotEnoughChunks { produced: 159, expected: 200 }), - ("test2".parse().unwrap(), NotEnoughChunks { produced: 70, expected: 100 }), - ("test3".parse().unwrap(), NotEnoughBlocks { produced: 89, expected: 100 }), - ]) - ); + assert_eq!(kickouts, HashMap::new(),); assert_eq!( validator_stats, HashMap::from([ @@ -2561,35 +2612,28 @@ fn test_validator_kickout_sanity() { "test0".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 100, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(170, 200), + chunk_stats: ChunkStats::new(195, 200, 200, 200), } ), ( "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 90, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(159, 200), + chunk_stats: ChunkStats::new(185, 200, 190, 200), } ), ( "test2".parse().unwrap(), BlockChunkValidatorStats { - block_stats: ValidatorStats { produced: 100, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(70, 100), + block_stats: ValidatorStats { produced: 0, expected: 0 }, + chunk_stats: ChunkStats::new_with_endorsement(195, 200), } ), ( "test3".parse().unwrap(), - BlockChunkValidatorStats { - block_stats: ValidatorStats { produced: 89, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(100, 100), - } - ), - ( - "test4".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::new_with_production(100, 100), + chunk_stats: ChunkStats::new_with_endorsement(185, 200), } ), ]) @@ -2599,8 +2643,7 @@ fn test_validator_kickout_sanity() { #[test] /// Test that the stake of validators kicked out in an epoch doesn't exceed the max_kickout_stake_ratio fn test_max_kickout_stake_ratio() { - let mut epoch_config = - epoch_config(5, 2, 4, 0, 90, 80, 0).for_protocol_version(PROTOCOL_VERSION); + let mut epoch_config = epoch_config(5, 2, 4, 90, 80, 0).for_protocol_version(PROTOCOL_VERSION); let accounts = vec![ ("test0".parse().unwrap(), 1000), ("test1".parse().unwrap(), 1000), @@ -2608,18 +2651,7 @@ fn test_max_kickout_stake_ratio() { ("test3".parse().unwrap(), 1000), ("test4".parse().unwrap(), 1000), ]; - let epoch_info = epoch_info( - 0, - accounts, - vec![0, 1, 2, 3], - vec![vec![0, 1], vec![2, 4]], - vec![], - vec![], - BTreeMap::new(), - vec![], - HashMap::new(), - 0, - ); + let epoch_info = epoch_info(0, accounts, vec![0, 1, 2, 3], vec![vec![0, 1], vec![2, 4]]); let block_stats = HashMap::from([ (0, ValidatorStats { produced: 50, expected: 100 }), // here both test1 and test2 produced the most number of blocks, we made that intentionally @@ -2633,21 +2665,21 @@ fn test_max_kickout_stake_ratio() { ( 0, HashMap::from([ - (0, ChunkValidatorStats::new_with_production(0, 100)), - (1, ChunkValidatorStats::new_with_production(0, 100)), + (0, ChunkStats::new_with_production(0, 100)), + (1, ChunkStats::new_with_production(0, 100)), ]), ), ( 1, HashMap::from([ - (2, ChunkValidatorStats::new_with_production(100, 100)), - (4, ChunkValidatorStats::new_with_production(50, 100)), + (2, ChunkStats::new_with_production(100, 100)), + (4, ChunkStats::new_with_production(50, 100)), ]), ), ]); let prev_validator_kickout = HashMap::from([("test3".parse().unwrap(), ValidatorKickoutReason::Unstaked)]); - let (kickouts, validator_stats) = EpochManager::compute_kickout_info( + let (validator_stats, kickouts) = EpochManager::compute_validators_to_reward_and_kickout( &epoch_config, &epoch_info, &block_stats, @@ -2672,42 +2704,42 @@ fn test_max_kickout_stake_ratio() { "test0".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 50, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(0, 100), + chunk_stats: ChunkStats::new_with_production(0, 100), }, ), ( "test1".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 70, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(0, 100), + chunk_stats: ChunkStats::new_with_production(0, 100), }, ), ( "test2".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 70, expected: 100 }, - chunk_stats: ChunkValidatorStats::new_with_production(100, 100), + chunk_stats: ChunkStats::new_with_production(100, 100), }, ), ( "test3".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::default(), + chunk_stats: ChunkStats::default(), }, ), ( "test4".parse().unwrap(), BlockChunkValidatorStats { block_stats: ValidatorStats { produced: 0, expected: 0 }, - chunk_stats: ChunkValidatorStats::new_with_production(50, 100), + chunk_stats: ChunkStats::new_with_production(50, 100), }, ), ]); assert_eq!(validator_stats, wanted_validator_stats,); // At most 50% of total stake can be kicked out epoch_config.validator_max_kickout_stake_perc = 40; - let (kickouts, validator_stats) = EpochManager::compute_kickout_info( + let (validator_stats, kickouts) = EpochManager::compute_validators_to_reward_and_kickout( &epoch_config, &epoch_info, &block_stats, @@ -2728,7 +2760,10 @@ fn test_max_kickout_stake_ratio() { assert_eq!(validator_stats, wanted_validator_stats,); } -fn test_chunk_header(h: &[CryptoHash], signer: &dyn ValidatorSigner) -> ShardChunkHeader { +fn test_chunk_header(h: &[CryptoHash], signer: &ValidatorSigner) -> ShardChunkHeader { + let congestion_info = ProtocolFeature::CongestionControl + .enabled(PROTOCOL_VERSION) + .then_some(CongestionInfo::default()); ShardChunkHeader::V3(ShardChunkHeaderV3::new( PROTOCOL_VERSION, h[0], @@ -2744,7 +2779,7 @@ fn test_chunk_header(h: &[CryptoHash], signer: &dyn ValidatorSigner) -> ShardChu h[2], h[2], vec![], - CongestionInfo::default(), + congestion_info, signer, )) } @@ -2767,7 +2802,7 @@ fn test_verify_chunk_endorsements() { let validators = vec![(account_id.clone(), amount_staked)]; let h = hash_range(6); - let mut epoch_manager = setup_default_epoch_manager(validators, 5, 1, 2, 2, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 5, 1, 2, 90, 60); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block(&mut epoch_manager, h[0], h[1], 1, vec![]); @@ -2813,7 +2848,7 @@ fn test_verify_chunk_endorsements() { let err = epoch_manager.verify_chunk_endorsement(&chunk_header, &chunk_endorsement).unwrap_err(); match err { - Error::NotAValidator => (), + Error::NotAValidator(_) => (), _ => assert!(false, "Expected NotAValidator error but got {:?}", err), } } @@ -2834,7 +2869,7 @@ fn test_verify_partial_witness_signature() { let validators = vec![(account_id.clone(), amount_staked)]; let h = hash_range(6); - let mut epoch_manager = setup_default_epoch_manager(validators, 5, 1, 2, 2, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, 5, 1, 2, 90, 60); record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]); record_block(&mut epoch_manager, h[0], h[1], 1, vec![]); @@ -2851,7 +2886,7 @@ fn test_verify_partial_witness_signature() { // Build a chunk state witness with arbitrary data. let chunk_header = test_chunk_header(&h, signer.as_ref()); let mut partial_witness = PartialEncodedStateWitness::new( - epoch_id.clone(), + epoch_id, chunk_header.clone(), 0, "witness".bytes().collect(), @@ -2900,7 +2935,7 @@ fn test_possible_epochs_of_height_around_tip() { let genesis_epoch = EpochId(CryptoHash::default()); let epoch_length = 5; - let mut epoch_manager = setup_default_epoch_manager(validators, epoch_length, 1, 2, 2, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, epoch_length, 1, 2, 90, 60); // Add the genesis block with height 1000 let genesis_height = 1000; @@ -2910,8 +2945,8 @@ fn test_possible_epochs_of_height_around_tip() { height: genesis_height, last_block_hash: h[0], prev_block_hash: CryptoHash::default(), - epoch_id: genesis_epoch.clone(), - next_epoch_id: genesis_epoch.clone(), + epoch_id: genesis_epoch, + next_epoch_id: genesis_epoch, }; assert_eq!( @@ -2926,17 +2961,17 @@ fn test_possible_epochs_of_height_around_tip() { ); assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&genesis_tip, genesis_height).unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); assert_eq!( epoch_manager .possible_epochs_of_height_around_tip(&genesis_tip, genesis_height + 1) .unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&genesis_tip, 10000000).unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); let epoch1 = EpochId(h[0]); @@ -2951,33 +2986,33 @@ fn test_possible_epochs_of_height_around_tip() { height, last_block_hash: h[i], prev_block_hash: h[i - 1], - epoch_id: genesis_epoch.clone(), - next_epoch_id: epoch1.clone(), + epoch_id: genesis_epoch, + next_epoch_id: epoch1, }; assert_eq!(epoch_manager.possible_epochs_of_height_around_tip(&tip, 0).unwrap(), vec![]); assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&tip, genesis_height).unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&tip, genesis_height + 1).unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); for h in 1..=5 { assert_eq!( epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h as BlockHeight) .unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); } assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&tip, genesis_height + 6).unwrap(), - vec![genesis_epoch.clone(), epoch1.clone()] + vec![genesis_epoch, epoch1] ); assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&tip, 1000000).unwrap(), - vec![genesis_epoch.clone(), epoch1.clone()] + vec![genesis_epoch, epoch1] ); } @@ -2993,8 +3028,8 @@ fn test_possible_epochs_of_height_around_tip() { height, last_block_hash: h[i], prev_block_hash: h[i - 1], - epoch_id: epoch1.clone(), - next_epoch_id: epoch2.clone(), + epoch_id: epoch1, + next_epoch_id: epoch2, }; assert_eq!(epoch_manager.possible_epochs_of_height_around_tip(&tip, 0).unwrap(), vec![]); assert_eq!( @@ -3006,7 +3041,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![genesis_epoch.clone()] + vec![genesis_epoch] ); } for h in 6..=10 { @@ -3014,16 +3049,16 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h as BlockHeight) .unwrap(), - vec![epoch1.clone()] + vec![epoch1] ); } assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&tip, genesis_height + 11).unwrap(), - vec![epoch1.clone(), epoch2.clone()] + vec![epoch1, epoch2] ); assert_eq!( epoch_manager.possible_epochs_of_height_around_tip(&tip, 1000000).unwrap(), - vec![epoch1.clone(), epoch2.clone()] + vec![epoch1, epoch2] ); } @@ -3053,8 +3088,8 @@ fn test_possible_epochs_of_height_around_tip() { height, last_block_hash: h[i], prev_block_hash: h[i - 2], - epoch_id: epoch2.clone(), - next_epoch_id: epoch3.clone(), + epoch_id: epoch2, + next_epoch_id: epoch3, }; for h in 0..=5 { assert_eq!( @@ -3069,7 +3104,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch1.clone()] + vec![epoch1] ); } // Block 11 isn't in any epoch. Block 10 was the last of the previous epoch and block 12 @@ -3083,7 +3118,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch2.clone()] + vec![epoch2] ); } for h in 17..=24 { @@ -3091,7 +3126,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch2.clone(), epoch3.clone()] + vec![epoch2, epoch3] ); } } @@ -3116,8 +3151,8 @@ fn test_possible_epochs_of_height_around_tip() { height, last_block_hash: h[i], prev_block_hash: h[i - 1], - epoch_id: epoch2.clone(), - next_epoch_id: epoch3.clone(), + epoch_id: epoch2, + next_epoch_id: epoch3, }; for h in 0..=5 { assert_eq!( @@ -3132,7 +3167,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch1.clone()] + vec![epoch1] ); } // Block 11 isn't in any epoch. Block 10 was the last of the previous epoch and block 12 @@ -3146,7 +3181,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch2.clone()] + vec![epoch2] ); } for h in 17..=26 { @@ -3154,7 +3189,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch2.clone(), epoch3.clone()] + vec![epoch2, epoch3] ); } } @@ -3169,8 +3204,8 @@ fn test_possible_epochs_of_height_around_tip() { height, last_block_hash: h[i], prev_block_hash: h[i - 1], - epoch_id: epoch3.clone(), - next_epoch_id: epoch4.clone(), + epoch_id: epoch3, + next_epoch_id: epoch4, }; assert_eq!(epoch_manager.possible_epochs_of_height_around_tip(&tip, 0).unwrap(), vec![]); for h in 0..=11 { @@ -3186,7 +3221,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch2.clone()] + vec![epoch2] ); } for h in 27..=31 { @@ -3194,7 +3229,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch3.clone()] + vec![epoch3] ); } for h in 32..40 { @@ -3202,7 +3237,7 @@ fn test_possible_epochs_of_height_around_tip() { epoch_manager .possible_epochs_of_height_around_tip(&tip, genesis_height + h) .unwrap(), - vec![epoch3.clone(), epoch4.clone()] + vec![epoch3, epoch4] ); } } diff --git a/chain/epoch-manager/src/tests/random_epochs.rs b/chain/epoch-manager/src/tests/random_epochs.rs index 2a984185c00..8711fdc24ee 100644 --- a/chain/epoch-manager/src/tests/random_epochs.rs +++ b/chain/epoch-manager/src/tests/random_epochs.rs @@ -56,7 +56,7 @@ fn do_random_test( ("test2".parse().unwrap(), stake_amount), ("test3".parse().unwrap(), stake_amount), ]; - let mut epoch_manager = setup_default_epoch_manager(validators, epoch_length, 1, 3, 0, 90, 60); + let mut epoch_manager = setup_default_epoch_manager(validators, epoch_length, 1, 3, 90, 60); let h = hash_range(num_heights as usize); let skip_height_probability = rng.gen_range(0.0..1.0) * rng.gen_range(0.0..1.0); diff --git a/chain/epoch-manager/src/types.rs b/chain/epoch-manager/src/types.rs index 968ac7ecde8..b0f490f756a 100644 --- a/chain/epoch-manager/src/types.rs +++ b/chain/epoch-manager/src/types.rs @@ -6,8 +6,7 @@ use near_primitives::epoch_manager::epoch_info::EpochInfo; use near_primitives::hash::CryptoHash; use near_primitives::types::validator_stake::ValidatorStake; use near_primitives::types::{ - AccountId, Balance, BlockHeight, ChunkValidatorStats, EpochId, ShardId, ValidatorId, - ValidatorStats, + AccountId, Balance, BlockHeight, ChunkStats, EpochId, ShardId, ValidatorId, ValidatorStats, }; use near_primitives::version::ProtocolVersion; use std::collections::{BTreeMap, HashMap}; @@ -60,7 +59,7 @@ pub struct EpochInfoAggregator { /// Map from validator index to (num_blocks_produced, num_blocks_expected) so far in the given epoch. pub block_tracker: HashMap, /// For each shard, a map of validator id to (num_chunks_produced, num_chunks_expected) so far in the given epoch. - pub shard_tracker: HashMap>, + pub shard_tracker: HashMap>, /// Latest protocol version that each validator supports. pub version_tracker: HashMap, /// All proposals in this epoch up to this block. @@ -161,7 +160,7 @@ impl EpochInfoAggregator { } *stats.expected_mut() += 1; }) - .or_insert_with(|| ChunkValidatorStats::new_with_production(u64::from(*mask), 1)); + .or_insert_with(|| ChunkStats::new_with_production(u64::from(*mask), 1)); let chunk_validators = chunk_validator_assignment .get(i) @@ -178,9 +177,7 @@ impl EpochInfoAggregator { } endorsement_stats.expected += 1; }) - .or_insert_with(|| { - ChunkValidatorStats::new_with_endorsement(u64::from(*mask), 1) - }); + .or_insert_with(|| ChunkStats::new_with_endorsement(u64::from(*mask), 1)); } } @@ -291,6 +288,10 @@ impl EpochInfoAggregator { .and_modify(|entry| { *entry.expected_mut() += stat.expected(); *entry.produced_mut() += stat.produced(); + entry.endorsement_stats_mut().expected += + stat.endorsement_stats().expected; + entry.endorsement_stats_mut().produced += + stat.endorsement_stats().produced; }) .or_insert_with(|| stat.clone()); } diff --git a/chain/epoch-manager/src/validator_selection.rs b/chain/epoch-manager/src/validator_selection.rs index ce68db5126c..484f8ea40f8 100644 --- a/chain/epoch-manager/src/validator_selection.rs +++ b/chain/epoch-manager/src/validator_selection.rs @@ -275,10 +275,11 @@ pub fn proposals_to_epoch_info( // Assign chunk validators to shards using validator mandates abstraction. let validator_mandates = if checked_feature!("stable", StatelessValidationV0, protocol_version) { - // Value chosen based on calculations for the security of the protocol. + // Default production value chosen to 68 based on calculations for the + // security of the mainnet protocol. // With this number of mandates per shard and 6 shards, the theory calculations predict the // protocol is secure for 40 years (at 90% confidence). - let target_mandates_per_shard = 68; + let target_mandates_per_shard = epoch_config.target_validator_mandates_per_shard as usize; let num_shards = shard_ids.len(); let validator_mandates_config = ValidatorMandatesConfig::new(target_mandates_per_shard, num_shards); @@ -1276,6 +1277,8 @@ mod tests { avg_hidden_validator_seats_per_shard: vec![0; num_shards as usize], block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, + target_validator_mandates_per_shard: 68, validator_max_kickout_stake_perc: 100, online_min_threshold: 0.into(), online_max_threshold: 0.into(), diff --git a/chain/indexer/Cargo.toml b/chain/indexer/Cargo.toml index 872cb6814c5..28be1c37055 100644 --- a/chain/indexer/Cargo.toml +++ b/chain/indexer/Cargo.toml @@ -63,6 +63,7 @@ nightly = [ "node-runtime/nightly", ] statelessnet_protocol = [ + "near-client/statelessnet_protocol", "near-primitives/statelessnet_protocol", "nearcore/statelessnet_protocol", ] diff --git a/chain/jsonrpc-primitives/Cargo.toml b/chain/jsonrpc-primitives/Cargo.toml index 7d204f4a715..1705ca07798 100644 --- a/chain/jsonrpc-primitives/Cargo.toml +++ b/chain/jsonrpc-primitives/Cargo.toml @@ -19,7 +19,7 @@ thiserror.workspace = true time.workspace = true near-crypto.workspace = true -near-primitives.workspace = true +near-primitives = { workspace = true, features = ["rand"] } near-chain-configs.workspace = true near-rpc-error-macro.workspace = true near-client-primitives = { workspace = true, optional = true } diff --git a/chain/jsonrpc-primitives/src/errors.rs b/chain/jsonrpc-primitives/src/errors.rs index 4c96a24ffbb..e1d0a0fc24f 100644 --- a/chain/jsonrpc-primitives/src/errors.rs +++ b/chain/jsonrpc-primitives/src/errors.rs @@ -6,7 +6,7 @@ use std::fmt; pub struct RpcParseError(pub String); /// This struct may be returned from JSON RPC server in case of error -/// It is expected that that this struct has impls From<_> all other RPC errors +/// It is expected that this struct has impls From<_> all other RPC errors /// like [RpcBlockError](crate::types::blocks::RpcBlockError) #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] #[serde(deny_unknown_fields)] diff --git a/chain/jsonrpc-primitives/src/types/congestion.rs b/chain/jsonrpc-primitives/src/types/congestion.rs new file mode 100644 index 00000000000..0c592092459 --- /dev/null +++ b/chain/jsonrpc-primitives/src/types/congestion.rs @@ -0,0 +1,16 @@ +use super::chunks::{ChunkReference, RpcChunkError}; + +// Reuse the same error as for chunk lookup since the congestion level call +// simply does a chunk lookup followed by a small and infallible computation. +pub type RpcCongestionLevelError = RpcChunkError; + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +pub struct RpcCongestionLevelRequest { + #[serde(flatten)] + pub chunk_reference: ChunkReference, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct RpcCongestionLevelResponse { + pub congestion_level: f64, +} diff --git a/chain/jsonrpc-primitives/src/types/mod.rs b/chain/jsonrpc-primitives/src/types/mod.rs index 697a41bedfa..65aa39eeae4 100644 --- a/chain/jsonrpc-primitives/src/types/mod.rs +++ b/chain/jsonrpc-primitives/src/types/mod.rs @@ -3,6 +3,7 @@ pub mod changes; pub mod chunks; pub mod client_config; pub mod config; +pub mod congestion; pub mod entity_debug; pub mod gas_price; pub mod light_client; diff --git a/chain/jsonrpc/Cargo.toml b/chain/jsonrpc/Cargo.toml index 7b94843b23c..1f40f3bc0ef 100644 --- a/chain/jsonrpc/Cargo.toml +++ b/chain/jsonrpc/Cargo.toml @@ -76,4 +76,5 @@ nightly_protocol = [ ] sandbox = [ "near-client/sandbox", + "near-o11y/sandbox", ] diff --git a/chain/jsonrpc/fuzz/Cargo.toml b/chain/jsonrpc/fuzz/Cargo.toml index 2dd5ec01ca5..c9f439a9ce0 100644 --- a/chain/jsonrpc/fuzz/Cargo.toml +++ b/chain/jsonrpc/fuzz/Cargo.toml @@ -24,7 +24,7 @@ serde.workspace = true serde_json.workspace = true tokio.workspace = true -near-async.workspace = true +near-time.workspace = true near-jsonrpc.workspace = true near-jsonrpc-primitives.workspace = true near-jsonrpc-tests.workspace = true diff --git a/chain/jsonrpc/fuzz/fuzz_targets_disabled/fuzz_target_1.rs b/chain/jsonrpc/fuzz/fuzz_targets_disabled/fuzz_target_1.rs index ba1f53d5f32..2f29dd5eedd 100644 --- a/chain/jsonrpc/fuzz/fuzz_targets_disabled/fuzz_target_1.rs +++ b/chain/jsonrpc/fuzz/fuzz_targets_disabled/fuzz_target_1.rs @@ -1,7 +1,7 @@ #![no_main] use actix::System; use libfuzzer_sys::{arbitrary, fuzz_target}; -use near_async::time::Clock; +use near_time::Clock; use serde::ser::{Serialize, Serializer}; use serde_json::json; use tokio; diff --git a/chain/jsonrpc/jsonrpc-tests/Cargo.toml b/chain/jsonrpc/jsonrpc-tests/Cargo.toml index e485ac669ea..81e391b3acd 100644 --- a/chain/jsonrpc/jsonrpc-tests/Cargo.toml +++ b/chain/jsonrpc/jsonrpc-tests/Cargo.toml @@ -20,6 +20,7 @@ borsh.workspace = true serde.workspace = true serde_json.workspace = true +near-time.workspace = true near-async.workspace = true near-chain-configs.workspace = true near-crypto.workspace = true @@ -61,7 +62,5 @@ nightly_protocol = [ "near-primitives/nightly_protocol", "near-store/nightly_protocol", ] -statelessnet_protocol = [ - "near-primitives/statelessnet_protocol", -] -sandbox = ["near-jsonrpc/sandbox"] +statelessnet_protocol = ["near-primitives/statelessnet_protocol"] +sandbox = ["near-jsonrpc/sandbox", "near-o11y/sandbox"] diff --git a/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json b/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json index c503c105626..9b3402fcc21 100644 --- a/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json +++ b/chain/jsonrpc/jsonrpc-tests/res/genesis_config.json @@ -1,5 +1,5 @@ { - "protocol_version": 67, + "protocol_version": 69, "genesis_time": "1970-01-01T00:00:00.000000000Z", "chain_id": "sample", "genesis_height": 0, @@ -21,6 +21,8 @@ "max_gas_price": "10000000000000000000000", "block_producer_kickout_threshold": 90, "chunk_producer_kickout_threshold": 90, + "chunk_validator_only_kickout_threshold": 80, + "target_validator_mandates_per_shard": 68, "online_min_threshold": [ 9, 10 diff --git a/chain/jsonrpc/jsonrpc-tests/src/lib.rs b/chain/jsonrpc/jsonrpc-tests/src/lib.rs index 176e73cf3e1..af940f9db64 100644 --- a/chain/jsonrpc/jsonrpc-tests/src/lib.rs +++ b/chain/jsonrpc/jsonrpc-tests/src/lib.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use actix::Addr; use futures::{future, future::LocalBoxFuture, FutureExt, TryFutureExt}; -use near_async::time::Clock; use near_async::{ actix::AddrWithAutoSpanContextExt, messaging::{noop, IntoMultiSender}, @@ -17,6 +16,7 @@ use near_jsonrpc_primitives::{ }; use near_network::tcp; use near_primitives::types::NumBlocks; +use near_time::Clock; use once_cell::sync::Lazy; use serde_json::json; @@ -72,7 +72,7 @@ macro_rules! test_with_client { near_actix_test_utils::run_actix(async { let (_view_client_addr, addr) = - test_utils::start_all(near_async::time::Clock::real(), $node_type); + test_utils::start_all(near_time::Clock::real(), $node_type); let $client = new_client(&format!("http://{}", addr)); diff --git a/chain/jsonrpc/jsonrpc-tests/tests/http_query.rs b/chain/jsonrpc/jsonrpc-tests/tests/http_query.rs index d7abf885314..0d87af34e7f 100644 --- a/chain/jsonrpc/jsonrpc-tests/tests/http_query.rs +++ b/chain/jsonrpc/jsonrpc-tests/tests/http_query.rs @@ -2,9 +2,9 @@ use actix::System; use futures::{future, FutureExt}; use near_actix_test_utils::run_actix; -use near_async::time::Clock; use near_jsonrpc::client::new_http_client; use near_o11y::testonly::init_test_logger; +use near_time::Clock; use near_jsonrpc_tests as test_utils; diff --git a/chain/jsonrpc/jsonrpc-tests/tests/rpc_query.rs b/chain/jsonrpc/jsonrpc-tests/tests/rpc_query.rs index 347c7ffcb53..9ae48513c05 100644 --- a/chain/jsonrpc/jsonrpc-tests/tests/rpc_query.rs +++ b/chain/jsonrpc/jsonrpc-tests/tests/rpc_query.rs @@ -6,7 +6,6 @@ use futures::{future, FutureExt}; use serde_json::json; use near_actix_test_utils::run_actix; -use near_async::time::Clock; use near_crypto::{KeyType, PublicKey, Signature}; use near_jsonrpc::client::{new_client, ChunkId}; use near_jsonrpc_primitives::types::query::QueryResponseKind; @@ -17,6 +16,7 @@ use near_primitives::account::{AccessKey, AccessKeyPermission}; use near_primitives::hash::CryptoHash; use near_primitives::types::{BlockId, BlockReference, EpochId, SyncCheckpoint}; use near_primitives::views::QueryRequest; +use near_time::Clock; use near_jsonrpc_tests::{self as test_utils, test_with_client}; diff --git a/chain/jsonrpc/jsonrpc-tests/tests/rpc_transactions.rs b/chain/jsonrpc/jsonrpc-tests/tests/rpc_transactions.rs index 855d2f359fb..69b10403d84 100644 --- a/chain/jsonrpc/jsonrpc-tests/tests/rpc_transactions.rs +++ b/chain/jsonrpc/jsonrpc-tests/tests/rpc_transactions.rs @@ -5,7 +5,6 @@ use actix::{Actor, System}; use futures::{future, FutureExt, TryFutureExt}; use near_actix_test_utils::run_actix; -use near_async::time::Clock; use near_crypto::{InMemorySigner, KeyType}; use near_jsonrpc::client::new_client; use near_jsonrpc_primitives::types::transactions::{RpcTransactionStatusRequest, TransactionInfo}; @@ -16,6 +15,7 @@ use near_primitives::serialize::to_base64; use near_primitives::transaction::SignedTransaction; use near_primitives::types::BlockReference; use near_primitives::views::{FinalExecutionStatus, TxExecutionStatus}; +use near_time::Clock; use near_jsonrpc_tests::{self as test_utils, test_with_client}; @@ -42,7 +42,7 @@ fn test_send_tx_async() { 1, signer_account_id.parse().unwrap(), "test2".parse().unwrap(), - &signer, + &signer.into(), 100, block_hash, ); @@ -97,7 +97,7 @@ fn test_send_tx_commit() { 1, "test1".parse().unwrap(), "test2".parse().unwrap(), - &signer, + &signer.into(), 100, block_hash, ); @@ -146,7 +146,8 @@ fn test_expired_tx() { "test1".parse().unwrap(), KeyType::ED25519, "test1", - ); + ) + .into(); let tx = SignedTransaction::send_money( 1, "test1".parse().unwrap(), @@ -190,7 +191,8 @@ fn test_expired_tx() { #[test] fn test_replay_protection() { test_with_client!(test_utils::NodeType::Validator, client, async move { - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::send_money( 1, "test1".parse().unwrap(), @@ -236,7 +238,7 @@ fn test_check_invalid_tx() { 1, "test1".parse().unwrap(), "test2".parse().unwrap(), - &signer, + &signer.into(), 100, hash(&[1]), )), diff --git a/chain/jsonrpc/res/congestion_control.css b/chain/jsonrpc/res/congestion_control.css new file mode 100644 index 00000000000..d9eed4dd1f5 --- /dev/null +++ b/chain/jsonrpc/res/congestion_control.css @@ -0,0 +1,43 @@ +.explanation { + color: black; +} + +.error { + color: red; + white-space: pre; +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; +} + +table, +th, +td { + border: 1px solid black; +} + +td { + text-align: left; + padding: 8px; + vertical-align: middle; +} + +th { + text-align: center; + vertical-align: middle; + padding: 8px; + background-color: lightgrey; +} + +.block_height { + background-color: lightgray; + font-weight: bold; +} + +.not_available { + font-style: italic; + color: lightgray; +} \ No newline at end of file diff --git a/chain/jsonrpc/res/congestion_control.html b/chain/jsonrpc/res/congestion_control.html new file mode 100644 index 00000000000..9de689bd809 --- /dev/null +++ b/chain/jsonrpc/res/congestion_control.html @@ -0,0 +1,16 @@ + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/chain/jsonrpc/res/congestion_control.js b/chain/jsonrpc/res/congestion_control.js new file mode 100644 index 00000000000..364e0f0f6fc --- /dev/null +++ b/chain/jsonrpc/res/congestion_control.js @@ -0,0 +1,135 @@ +function sortBlocks(blocks) { + function sortingKey(row) { + // Note: using the expression `row.block_timestamp / 1e12 % 1` reduces the timestamp + // to a number between 0 and 1, making block_timestamp strictly weaker than block_height + // in this comparison. + return row.block_height + (row.block_timestamp / 1e12 % 1); + } + blocks.sort((a, b) => sortingKey(b) - sortingKey(a)); + return blocks; +} + +function toTgas(gas) { + return (gas / (1024 * 1024 * 1024 * 1024)).toFixed(2) +} + +function toMiB(bytes) { + return (bytes / (1024 * 1024)).toFixed(2) +} + +function toCongestionLevelCell(level) { + if (level == null) { + return N/A + } + return {level.toFixed(2)} +} + +function BlocksTable({ rows }) { + let numShards = 0; + for (let row of rows) { + for (let chunk of row.chunks) { + numShards = Math.max(numShards, chunk.shard_id + 1); + } + } + const header = + Height + {[...Array(numShards).keys()].map(i => + Shard {i} (congestion_level/delayed(Tgas)/buffered(Tgas)/receipt(MiB)/allowed(shard)))} + ; + + // One 'tr' element per row. + const tableRows = []; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + + const chunkCells = []; + row.chunks.forEach((chunk, shardId) => { + if (chunk.congestion_info) { + const info = chunk.congestion_info.V1; + chunkCells.push( + {toCongestionLevelCell(chunk.congestion_level)} + {toTgas(info.delayed_receipts_gas)} + {toTgas(info.buffered_receipts_gas)} + {toMiB(info.receipt_bytes)} + {info.allowed_shard} + ); + } else { + chunkCells.push( + {toCongestionLevelCell(chunk.congestion_level)} + N/A + N/A + N/A + N/A + ); + } + }); + + tableRows.push( + + + {row.block_height} + + {chunkCells} + ); + } + return
+ + + {header} + {tableRows} + +
+
+} + +function Page() { + const [rows, setRows] = React.useState([]); + const [error, setError] = React.useState(null); + let blockStatusApiPath = '../api/block_status'; + const url = new URL(window.location.toString()); + let title = 'Congestion control'; + if (url.searchParams.has('height')) { + blockStatusApiPath += '/' + url.searchParams.get('height'); + title = 'Blocks from ' + url.searchParams.get('height'); + } + // useEffect with empty dependency list means to run this once at beginning. + React.useEffect(() => { + (async () => { + try { + let resp = await fetch('../api/status'); + if (resp.status == 405) { + throw new Error('Debug not allowed - did you set enable_debug_rpc: true in your config?'); + } else if (!resp.ok) { + throw new Error('Debug API call failed: ' + resp.statusText); + } + + resp = await fetch(blockStatusApiPath); + if (!resp.ok) { + throw new Error('Could not fetch block debug status: ' + resp.statusText); + } + const { status_response: { BlockStatus: data } } = await resp.json(); + setRows(sortBlocks(data.blocks)); + } catch (error) { + setError(error); + } + })(); + }, []); + + return
+

{title}

+
+ delayed: sum of gas in currently delayed receipts
+ buffered: sum of gas in currently buffered receipts
+ receipt: size of borsh serialized receipts stored in state because they were delayed, buffered, postponed, or yielded
+ allowed: if fully congested, only this shard can forward receipts
+
+ {error &&
{error.stack}
} +

Blocks

+ +
; +} + +ReactDOM + .createRoot(document.getElementById('react-container')) + .render(); diff --git a/chain/jsonrpc/res/debug.html b/chain/jsonrpc/res/debug.html index 532c792606e..328b3332419 100644 --- a/chain/jsonrpc/res/debug.html +++ b/chain/jsonrpc/res/debug.html @@ -68,6 +68,7 @@

Sync info

Validator info

Client Config

Split Store

+

Congestion control

diff --git a/chain/jsonrpc/res/last_blocks.js b/chain/jsonrpc/res/last_blocks.js index 6eab53042fb..05245a2d617 100644 --- a/chain/jsonrpc/res/last_blocks.js +++ b/chain/jsonrpc/res/last_blocks.js @@ -5,6 +5,13 @@ function ellipsify(str, maxLen) { return str; } +function toPercentage(number, fractionDigits) { + if (isNaN(number)) { + return number; + } + return (number * 100).toFixed(fractionDigits) + '%'; +} + // Makes an element that when clicked, expands or ellipsifies the hash and creator. function HashElement({ hashValue, creator, expandAll, knownProducers }) { let [expanded, setExpanded] = React.useState(false); @@ -132,7 +139,7 @@ function BlocksTable({ rows, knownProducers, expandAll, hideMissingHeights }) { Block Delay (s) Gas price ratio {[...Array(numShards).keys()].map(i => - Shard {i} (hash/gas(Tgas)/time(ms)))} + Shard {i} (hash/gas(Tgas)/time(ms)/endorsement(stake)))} ; // One xarrow element per arrow (from block to block). @@ -166,6 +173,7 @@ function BlocksTable({ rows, knownProducers, expandAll, hideMissingHeights }) { {(chunk.gas_used / (1024 * 1024 * 1024 * 1024)).toFixed(1)} {chunk.processing_time_ms} + {toPercentage(chunk.endorsement_ratio, 1)} ); }); diff --git a/chain/jsonrpc/res/rpc_errors_schema.json b/chain/jsonrpc/res/rpc_errors_schema.json index a09dfebc2fe..34a35d104d6 100644 --- a/chain/jsonrpc/res/rpc_errors_schema.json +++ b/chain/jsonrpc/res/rpc_errors_schema.json @@ -133,10 +133,12 @@ "props": { "final_accounts_balance": "", "final_postponed_receipts_balance": "", + "forwarded_buffered_receipts_balance": "", "incoming_receipts_balance": "", "incoming_validator_rewards": "", "initial_accounts_balance": "", "initial_postponed_receipts_balance": "", + "new_buffered_receipts_balance": "", "new_delayed_receipts_balance": "", "other_burnt_amount": "", "outgoing_receipts_balance": "", @@ -569,7 +571,10 @@ "Expired", "ActionsValidation", "TransactionSizeExceeded", - "InvalidTransactionVersion" + "InvalidTransactionVersion", + "StorageError", + "ShardCongested", + "ShardStuck" ], "props": {} }, @@ -733,6 +738,14 @@ "method_name": "" } }, + "ReceiptSizeExceeded": { + "name": "ReceiptSizeExceeded", + "subtypes": [], + "props": { + "limit": "", + "size": "" + } + }, "ReceiptValidationError": { "name": "ReceiptValidationError", "subtypes": [ @@ -742,7 +755,8 @@ "InvalidDataReceiverId", "ReturnedValueLengthExceeded", "NumberInputDataDependenciesExceeded", - "ActionsValidation" + "ActionsValidation", + "ReceiptSizeExceeded" ], "props": {} }, @@ -772,6 +786,22 @@ "subtypes": [], "props": {} }, + "ShardCongested": { + "name": "ShardCongested", + "subtypes": [], + "props": { + "congestion_level": "", + "shard_id": "" + } + }, + "ShardStuck": { + "name": "ShardStuck", + "subtypes": [], + "props": { + "missed_chunks": "", + "shard_id": "" + } + }, "SignerDoesNotExist": { "name": "SignerDoesNotExist", "subtypes": [], diff --git a/chain/jsonrpc/src/api/chunks.rs b/chain/jsonrpc/src/api/chunks.rs index 686d621fc94..badf0bf4654 100644 --- a/chain/jsonrpc/src/api/chunks.rs +++ b/chain/jsonrpc/src/api/chunks.rs @@ -8,23 +8,28 @@ use near_primitives::types::BlockId; use super::{Params, RpcFrom, RpcRequest}; +pub(crate) fn parse_chunk_reference(value: Value) -> Result { + // params can be: + // - chunk_reference (an object), + // - [[block_id, shard_id]] (a one-element array with array element) or + // - [chunk_id] (a one-element array with hash element). + let chunk_reference = Params::new(value) + .try_singleton(|value: Value| { + if value.is_array() { + let (block_id, shard_id) = Params::parse(value)?; + Ok(ChunkReference::BlockShardId { block_id, shard_id }) + } else { + let chunk_id = Params::parse(value)?; + Ok(ChunkReference::ChunkHash { chunk_id }) + } + }) + .unwrap_or_parse()?; + Ok(chunk_reference) +} + impl RpcRequest for RpcChunkRequest { fn parse(value: Value) -> Result { - // params can be: - // - chunk_reference (an object), - // - [[block_id, shard_id]] (a one-element array with array element) or - // - [chunk_id] (a one-element array with hash element). - let chunk_reference = Params::new(value) - .try_singleton(|value: Value| { - if value.is_array() { - let (block_id, shard_id) = Params::parse(value)?; - Ok(ChunkReference::BlockShardId { block_id, shard_id }) - } else { - let chunk_id = Params::parse(value)?; - Ok(ChunkReference::ChunkHash { chunk_id }) - } - }) - .unwrap_or_parse()?; + let chunk_reference = parse_chunk_reference(value)?; Ok(Self { chunk_reference }) } } diff --git a/chain/jsonrpc/src/api/congestion.rs b/chain/jsonrpc/src/api/congestion.rs new file mode 100644 index 00000000000..2d8f1e2f47f --- /dev/null +++ b/chain/jsonrpc/src/api/congestion.rs @@ -0,0 +1,13 @@ +use near_jsonrpc_primitives::errors::RpcParseError; +use near_jsonrpc_primitives::types::congestion::RpcCongestionLevelRequest; +use serde_json::Value; + +use super::chunks::parse_chunk_reference; +use super::RpcRequest; + +impl RpcRequest for RpcCongestionLevelRequest { + fn parse(value: Value) -> Result { + let chunk_reference = parse_chunk_reference(value)?; + Ok(Self { chunk_reference }) + } +} diff --git a/chain/jsonrpc/src/api/mod.rs b/chain/jsonrpc/src/api/mod.rs index 290d410e41c..1feb48a44da 100644 --- a/chain/jsonrpc/src/api/mod.rs +++ b/chain/jsonrpc/src/api/mod.rs @@ -9,6 +9,7 @@ mod changes; mod chunks; mod client_config; mod config; +mod congestion; mod gas_price; mod light_client; mod maintenance; diff --git a/chain/jsonrpc/src/lib.rs b/chain/jsonrpc/src/lib.rs index 5504e22ab53..409952a7113 100644 --- a/chain/jsonrpc/src/lib.rs +++ b/chain/jsonrpc/src/lib.rs @@ -23,7 +23,7 @@ use near_client_primitives::types::GetSplitStorageInfo; pub use near_jsonrpc_client as client; use near_jsonrpc_primitives::errors::RpcError; use near_jsonrpc_primitives::message::{Message, Request}; -use near_jsonrpc_primitives::types::config::RpcProtocolConfigResponse; +use near_jsonrpc_primitives::types::config::{RpcProtocolConfigError, RpcProtocolConfigResponse}; use near_jsonrpc_primitives::types::entity_debug::{EntityDebugHandler, EntityQuery}; use near_jsonrpc_primitives::types::query::RpcQueryRequest; use near_jsonrpc_primitives::types::split_storage::{ @@ -37,7 +37,7 @@ use near_network::tcp; use near_o11y::metrics::{prometheus, Encoder, TextEncoder}; use near_primitives::hash::CryptoHash; use near_primitives::transaction::SignedTransaction; -use near_primitives::types::{AccountId, BlockHeight}; +use near_primitives::types::{AccountId, BlockHeight, BlockId, BlockReference}; use near_primitives::views::{QueryRequest, TxExecutionStatus}; use serde_json::{json, Value}; use std::path::PathBuf; @@ -411,6 +411,9 @@ impl JsonRpcHandler { "EXPERIMENTAL_changes_in_block" => { process_method_call(request, |params| self.changes_in_block(params)).await } + "EXPERIMENTAL_congestion_level" => { + process_method_call(request, |params| self.congestion_level(params)).await + } "EXPERIMENTAL_genesis_config" => { process_method_call(request, |_params: ()| async { Result::<_, std::convert::Infallible>::Ok(&self.genesis_config) @@ -640,10 +643,11 @@ impl JsonRpcHandler { .map_err(|_| { metrics::RPC_TIMEOUT_TOTAL.inc(); tracing::warn!( - target: "jsonrpc", "Timeout: tx_status_fetch method. tx_info {:?} fetch_receipt {:?} result {:?}", + target: "jsonrpc", "Timeout: tx_status_fetch method. tx_info {:?} fetch_receipt {:?} result {:?} timeout {:?}", tx_info, fetch_receipt, - tx_status_result + tx_status_result, + self.polling_config.polling_timeout, ); near_jsonrpc_primitives::types::transactions::RpcTransactionError::TimeoutError })? @@ -916,6 +920,41 @@ impl JsonRpcHandler { Ok(near_jsonrpc_primitives::types::chunks::RpcChunkResponse { chunk_view }) } + async fn congestion_level( + &self, + request_data: near_jsonrpc_primitives::types::congestion::RpcCongestionLevelRequest, + ) -> Result< + near_jsonrpc_primitives::types::congestion::RpcCongestionLevelResponse, + near_jsonrpc_primitives::types::congestion::RpcCongestionLevelError, + > { + let chunk_view = + self.view_client_send(GetChunk::rpc_from(request_data.chunk_reference)).await?; + let config_result = self + .view_client_send(GetProtocolConfig(BlockReference::BlockId(BlockId::Height( + chunk_view.header.height_included, + )))) + .await; + let config = config_result.map_err(|err: RpcProtocolConfigError| match err { + RpcProtocolConfigError::UnknownBlock { error_message } => { + near_jsonrpc_primitives::types::congestion::RpcCongestionLevelError::UnknownBlock { + error_message, + } + } + RpcProtocolConfigError::InternalError { error_message } => { + near_jsonrpc_primitives::types::congestion::RpcCongestionLevelError::InternalError { + error_message, + } + } + })?; + let congestion_info = chunk_view.header.congestion_info; + let congestion_level = congestion_info + .map(|info| info.congestion_level(config.runtime_config.congestion_control_config)) + .unwrap_or(0.0); + Ok(near_jsonrpc_primitives::types::congestion::RpcCongestionLevelResponse { + congestion_level, + }) + } + async fn receipt( &self, request_data: near_jsonrpc_primitives::types::receipts::RpcReceiptRequest, @@ -1482,6 +1521,9 @@ async fn display_debug_html( "validator" => Some(debug_page_string!("validator.html", handler)), "validator.css" => Some(debug_page_string!("validator.css", handler)), "split_store" => Some(debug_page_string!("split_store.html", handler)), + "congestion_control" => Some(debug_page_string!("congestion_control.html", handler)), + "congestion_control.css" => Some(debug_page_string!("congestion_control.css", handler)), + "congestion_control.js" => Some(debug_page_string!("congestion_control.js", handler)), _ => None, }; diff --git a/chain/network/Cargo.toml b/chain/network/Cargo.toml index 3d28bcb5254..8aeabef35f3 100644 --- a/chain/network/Cargo.toml +++ b/chain/network/Cargo.toml @@ -26,6 +26,7 @@ bytesize.workspace = true chrono.workspace = true crossbeam-channel.workspace = true derive_more.workspace = true +enum-map.workspace = true futures-util.workspace = true futures.workspace = true im.workspace = true @@ -54,6 +55,7 @@ time.workspace = true near-async.workspace = true near-fmt.workspace = true near-o11y.workspace = true +near-chain-configs.workspace = true near-crypto.workspace = true near-performance-metrics.workspace = true near-performance-metrics-macros.workspace = true @@ -70,10 +72,12 @@ rlimit.workspace = true tempfile.workspace = true turn.workspace = true webrtc-util.workspace = true +serde_json.workspace = true [features] nightly_protocol = [ "near-async/nightly_protocol", + "near-chain-configs/nightly_protocol", "near-fmt/nightly_protocol", "near-o11y/nightly_protocol", "near-primitives/nightly_protocol", @@ -81,6 +85,7 @@ nightly_protocol = [ ] nightly = [ "near-async/nightly", + "near-chain-configs/nightly", "near-fmt/nightly", "near-o11y/nightly", "near-primitives/nightly", diff --git a/chain/network/src/accounts_data/mod.rs b/chain/network/src/accounts_data/mod.rs index 9d962dc1532..923cfb7ae99 100644 --- a/chain/network/src/accounts_data/mod.rs +++ b/chain/network/src/accounts_data/mod.rs @@ -55,7 +55,7 @@ pub(crate) enum AccountDataError { /// for more details. #[derive(Clone)] pub struct LocalAccountData { - pub signer: Arc, + pub signer: Arc, pub data: Arc, } diff --git a/chain/network/src/accounts_data/tests.rs b/chain/network/src/accounts_data/tests.rs index 9c5d57a4337..7f43c38cbdd 100644 --- a/chain/network/src/accounts_data/tests.rs +++ b/chain/network/src/accounts_data/tests.rs @@ -17,7 +17,7 @@ fn make_account_data( ) -> SignedAccountData { let peer_id = data::make_peer_id(rng); data::make_account_data(rng, version, clock.now_utc(), signer.public_key(), peer_id) - .sign(signer) + .sign(&signer.clone().into()) .unwrap() } @@ -225,7 +225,7 @@ async fn set_local() { // Set local while local.signer is in cache.keys. // A new AccountData should be signed. let local = LocalAccountData { - signer: Arc::new(signers[0].clone()), + signer: Arc::new(signers[0].clone().into()), data: Arc::new(make_account_data(rng, &clock.clock(), 1, &signers[0]).data.clone()), }; let got = cache.set_local(&clock.clock(), local.clone()).unwrap(); @@ -266,7 +266,7 @@ async fn set_local() { // Update local data to a signer in cache.keys. let local = LocalAccountData { - signer: Arc::new(signers[2].clone()), + signer: Arc::new(signers[2].clone().into()), data: Arc::new(make_account_data(rng, &clock.clock(), 1, &signers[2]).data.clone()), }; let got = cache.set_local(&clock.clock(), local.clone()).unwrap(); @@ -276,7 +276,7 @@ async fn set_local() { // Update local data to a signer outside of cache.keys. let local = LocalAccountData { - signer: Arc::new(signers[0].clone()), + signer: Arc::new(signers[0].clone().into()), data: Arc::new(make_account_data(rng, &clock.clock(), 1, &signers[0]).data.clone()), }; assert_eq!(None, cache.set_local(&clock.clock(), local)); diff --git a/chain/network/src/announce_accounts/mod.rs b/chain/network/src/announce_accounts/mod.rs index 09139b40b73..fe1ffb596d6 100644 --- a/chain/network/src/announce_accounts/mod.rs +++ b/chain/network/src/announce_accounts/mod.rs @@ -4,6 +4,7 @@ use near_primitives::network::{AnnounceAccount, PeerId}; use near_primitives::types::AccountId; use parking_lot::Mutex; use std::collections::HashMap; +use std::num::NonZeroUsize; #[cfg(test)] mod tests; @@ -48,8 +49,10 @@ pub(crate) struct AnnounceAccountCache(Mutex); impl AnnounceAccountCache { pub fn new(store: store::Store) -> Self { Self(Mutex::new(Inner { - account_peers: LruCache::new(ANNOUNCE_ACCOUNT_CACHE_SIZE), - account_peers_broadcasted: LruCache::new(ANNOUNCE_ACCOUNT_CACHE_SIZE), + account_peers: LruCache::new(NonZeroUsize::new(ANNOUNCE_ACCOUNT_CACHE_SIZE).unwrap()), + account_peers_broadcasted: LruCache::new( + NonZeroUsize::new(ANNOUNCE_ACCOUNT_CACHE_SIZE).unwrap(), + ), store, })) } diff --git a/chain/network/src/announce_accounts/tests.rs b/chain/network/src/announce_accounts/tests.rs index cf2199d4657..07ff0be4c09 100644 --- a/chain/network/src/announce_accounts/tests.rs +++ b/chain/network/src/announce_accounts/tests.rs @@ -16,7 +16,7 @@ fn announcement_same_epoch() { let announce0 = AnnounceAccount { account_id: "near0".parse().unwrap(), peer_id: peer_id0.clone(), - epoch_id: epoch_id0.clone(), + epoch_id: epoch_id0, signature: Signature::default(), }; diff --git a/chain/network/src/client.rs b/chain/network/src/client.rs index becbb3b46f6..b6551a962e6 100644 --- a/chain/network/src/client.rs +++ b/chain/network/src/client.rs @@ -1,6 +1,7 @@ use crate::network_protocol::StateResponseInfo; use crate::types::{NetworkInfo, ReasonForBan}; use near_async::messaging::AsyncSender; +use near_async::{MultiSend, MultiSendMessage, MultiSenderFrom}; use near_primitives::block::{Approval, Block, BlockHeader}; use near_primitives::challenge::Challenge; use near_primitives::errors::InvalidTxError; @@ -116,9 +117,7 @@ pub struct AnnounceAccountRequest(pub Vec<(AnnounceAccount, Option)>); #[rtype(result = "()")] pub struct ChunkEndorsementMessage(pub ChunkEndorsement); -#[derive( - Clone, near_async::MultiSend, near_async::MultiSenderFrom, near_async::MultiSendMessage, -)] +#[derive(Clone, MultiSend, MultiSenderFrom, MultiSendMessage)] #[multi_send_message_derive(Debug)] #[multi_send_input_derive(Debug, Clone, PartialEq, Eq)] pub struct ClientSenderForNetwork { diff --git a/chain/network/src/config.rs b/chain/network/src/config.rs index c64ec4524bc..6e020c799c2 100644 --- a/chain/network/src/config.rs +++ b/chain/network/src/config.rs @@ -3,12 +3,15 @@ use crate::concurrency::rate; use crate::network_protocol::PeerAddr; use crate::network_protocol::PeerInfo; use crate::peer_manager::peer_store; +use crate::rate_limits::messages_limits; use crate::snapshot_hosts; use crate::stun; use crate::tcp; use crate::types::ROUTED_MESSAGE_TTL; use anyhow::Context; use near_async::time; +use near_chain_configs::MutableConfigValue; +use near_chain_configs::MutableValidatorSigner; use near_crypto::{KeyType, SecretKey}; use near_primitives::network::PeerId; use near_primitives::test_utils::create_test_signer; @@ -56,13 +59,26 @@ pub enum ValidatorProxies { #[derive(Clone)] pub struct ValidatorConfig { - pub signer: Arc, + /// Contains signer key for this node. This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + pub signer: MutableValidatorSigner, pub proxies: ValidatorProxies, } +/// A snapshot of ValidatorConfig. Use to freeze the value of the mutable validator signer field. +pub struct FrozenValidatorConfig<'a> { + pub signer: Option>, + pub proxies: &'a ValidatorProxies, +} + impl ValidatorConfig { - pub fn account_id(&self) -> AccountId { - self.signer.validator_id().clone() + pub fn account_id(&self) -> Option { + self.signer.get().map(|s| s.validator_id().clone()) + } + + pub fn frozen_view(&self) -> FrozenValidatorConfig { + FrozenValidatorConfig { signer: self.signer.get(), proxies: &self.proxies } } } @@ -87,12 +103,24 @@ pub struct Tier1 { pub enable_outbound: bool, } +#[derive(Clone)] +pub struct SocketOptions { + pub recv_buffer_size: Option, + pub send_buffer_size: Option, +} + +impl SocketOptions { + pub fn default() -> SocketOptions { + SocketOptions { recv_buffer_size: None, send_buffer_size: None } + } +} + /// Validated configuration for the peer-to-peer manager. #[derive(Clone)] pub struct NetworkConfig { pub node_addr: Option, pub node_key: SecretKey, - pub validator: Option, + pub validator: ValidatorConfig, pub peer_store: peer_store::Config, pub snapshot_hosts: snapshot_hosts::Config, @@ -112,6 +140,8 @@ pub struct NetworkConfig { pub ideal_connections_lo: u32, /// Upper bound of the ideal number of connections. pub ideal_connections_hi: u32, + /// Socket options for peer connections. + pub socket_options: SocketOptions, /// Peers which last message is was within this period of time are considered active recent peers. pub peer_recent_time_window: time::Duration, /// Number of peers to keep while removing a connection. @@ -163,6 +193,9 @@ pub struct NetworkConfig { // * ignoring received deleted edges as well pub skip_tombstones: Option, + /// Configuration of rate limits for incoming messages. + pub received_messages_rate_limits: messages_limits::Config, + #[cfg(test)] pub(crate) event_sink: near_async::messaging::Sender, @@ -208,12 +241,15 @@ impl NetworkConfig { ) { self.routing_table_update_rate_limit = rate::Limit { qps, burst } } + if let Some(rate_limits) = overrides.received_messages_rate_limits { + self.received_messages_rate_limits.apply_overrides(rate_limits); + } } pub fn new( cfg: crate::config_json::Config, node_key: SecretKey, - validator_signer: Option>, + validator_signer: MutableValidatorSigner, archive: bool, ) -> anyhow::Result { if cfg.public_addrs.len() > MAX_PEER_ADDRS { @@ -249,14 +285,14 @@ impl NetworkConfig { } let mut this = Self { node_key, - validator: validator_signer.map(|signer| ValidatorConfig { - signer, + validator: ValidatorConfig { + signer: validator_signer, proxies: if !cfg.public_addrs.is_empty() { ValidatorProxies::Static(cfg.public_addrs) } else { ValidatorProxies::Dynamic(cfg.trusted_stun_servers) }, - }), + }, node_addr: match cfg.addr.as_str() { "" => None, addr => Some(tcp::ListenerAddr::new( @@ -310,6 +346,10 @@ impl NetworkConfig { minimum_outbound_peers: cfg.minimum_outbound_peers, ideal_connections_lo: cfg.ideal_connections_lo, ideal_connections_hi: cfg.ideal_connections_hi, + socket_options: SocketOptions { + recv_buffer_size: cfg.so_recv_buffer_size, + send_buffer_size: cfg.so_send_buffer_size, + }, peer_recent_time_window: cfg.peer_recent_time_window.try_into()?, safe_set_size: cfg.safe_set_size, archival_peer_connections_lower_bound: cfg.archival_peer_connections_lower_bound, @@ -338,6 +378,8 @@ impl NetworkConfig { } else { None }, + // Use a preset to configure rate limits and override entries with user defined values later. + received_messages_rate_limits: messages_limits::Config::standard_preset(), #[cfg(test)] event_sink: near_async::messaging::IntoSender::into_sender( near_async::messaging::noop(), @@ -355,7 +397,10 @@ impl NetworkConfig { pub fn from_seed(seed: &str, node_addr: tcp::ListenerAddr) -> Self { let node_key = SecretKey::from_seed(KeyType::ED25519, seed); let validator = ValidatorConfig { - signer: Arc::new(create_test_signer(seed)), + signer: MutableConfigValue::new( + Some(Arc::new(create_test_signer(seed))), + "validator_signer", + ), proxies: ValidatorProxies::Static(vec![PeerAddr { addr: *node_addr, peer_id: PeerId::new(node_key.public_key()), @@ -364,7 +409,7 @@ impl NetworkConfig { NetworkConfig { node_addr: Some(node_addr), node_key, - validator: Some(validator), + validator, peer_store: peer_store::Config { boot_nodes: vec![], blacklist: blacklist::Blacklist::default(), @@ -385,6 +430,7 @@ impl NetworkConfig { minimum_outbound_peers: 5, ideal_connections_lo: 30, ideal_connections_hi: 35, + socket_options: SocketOptions { recv_buffer_size: None, send_buffer_size: None }, peer_recent_time_window: time::Duration::seconds(600), safe_set_size: 20, archival_peer_connections_lower_bound: 10, @@ -411,6 +457,7 @@ impl NetworkConfig { enable_outbound: true, }), skip_tombstones: None, + received_messages_rate_limits: messages_limits::Config::default(), #[cfg(test)] event_sink: near_async::messaging::IntoSender::into_sender( near_async::messaging::noop(), @@ -463,6 +510,11 @@ impl NetworkConfig { self.routing_table_update_rate_limit .validate() .context("routing_table_update_rate_limit")?; + + if let Err(err) = self.received_messages_rate_limits.validate() { + anyhow::bail!("One or more invalid rate limits: {err:?}"); + } + Ok(VerifiedConfig { node_id: self.node_id(), inner: self }) } } @@ -501,6 +553,9 @@ mod test { use crate::network_protocol; use crate::network_protocol::testonly as data; use crate::network_protocol::{AccountData, VersionedAccountData}; + use crate::rate_limits::messages_limits::{ + RateLimitedPeerMessageKey::BlockHeaders, SingleMessageConfig, + }; use crate::tcp; use crate::testonly::make_rng; use near_async::time; @@ -636,7 +691,22 @@ mod test { version: 0, timestamp: clock.now_utc(), }; - let sad = ad.sign(&signer).unwrap(); + let sad = ad.sign(&signer.into()).unwrap(); assert!(sad.payload().len() <= network_protocol::MAX_ACCOUNT_DATA_SIZE_BYTES); } + + #[test] + fn received_messages_rate_limits_error() { + let mut nc = config::NetworkConfig::from_seed("123", tcp::ListenerAddr::reserve_for_test()); + nc.received_messages_rate_limits + .rate_limits + .insert(BlockHeaders, SingleMessageConfig::new(1, -4.0, None)); + assert!(nc.verify().is_err()); + + let mut nc = config::NetworkConfig::from_seed("123", tcp::ListenerAddr::reserve_for_test()); + nc.received_messages_rate_limits + .rate_limits + .insert(BlockHeaders, SingleMessageConfig::new(1, 4.0, None)); + assert!(nc.verify().is_ok()); + } } diff --git a/chain/network/src/config_json.rs b/chain/network/src/config_json.rs index b79394d6756..90ef57c44fd 100644 --- a/chain/network/src/config_json.rs +++ b/chain/network/src/config_json.rs @@ -1,4 +1,5 @@ use crate::network_protocol::PeerAddr; +use crate::rate_limits::messages_limits; use crate::stun; use near_async::time::Duration; @@ -21,6 +22,13 @@ fn default_ideal_connections_lo() -> u32 { fn default_ideal_connections_hi() -> u32 { 35 } +/// Default socket options for peer connections. +fn default_so_recv_buffer_size() -> Option { + Some(1000000) +} +fn default_so_send_buffer_size() -> Option { + Some(1000000) +} /// Peers which last message is was within this period of time are considered active recent peers. fn default_peer_recent_time_window() -> Duration { Duration::seconds(600) @@ -65,7 +73,7 @@ fn default_peer_expiration_duration() -> Duration { /// a centralized entity (and DNS used for domain resolution), /// prefer to set up your own STUN server, or (even better) /// use public_addrs instead. -fn default_trusted_stun_servers() -> Vec { +pub(crate) fn default_trusted_stun_servers() -> Vec { vec![ "stun.l.google.com:19302".to_string(), "stun1.l.google.com:19302".to_string(), @@ -104,6 +112,10 @@ pub struct Config { /// Upper bound of the ideal number of connections. #[serde(default = "default_ideal_connections_hi")] pub ideal_connections_hi: u32, + #[serde(default = "default_so_recv_buffer_size")] + pub so_recv_buffer_size: Option, + #[serde(default = "default_so_send_buffer_size")] + pub so_send_buffer_size: Option, /// Peers which last message is was within this period of time are considered active recent peers (in seconds). #[serde(default = "default_peer_recent_time_window")] #[serde(with = "near_async::time::serde_duration_as_std")] @@ -274,6 +286,7 @@ pub struct NetworkConfigOverrides { pub accounts_data_broadcast_rate_limit_qps: Option, pub routing_table_update_rate_limit_burst: Option, pub routing_table_update_rate_limit_qps: Option, + pub received_messages_rate_limits: Option, } impl Default for ExperimentalConfig { @@ -301,6 +314,8 @@ impl Default for Config { minimum_outbound_peers: default_minimum_outbound_connections(), ideal_connections_lo: default_ideal_connections_lo(), ideal_connections_hi: default_ideal_connections_hi(), + so_recv_buffer_size: default_so_recv_buffer_size(), + so_send_buffer_size: default_so_send_buffer_size(), peer_recent_time_window: default_peer_recent_time_window(), safe_set_size: default_safe_set_size(), archival_peer_connections_lower_bound: default_archival_peer_connections_lower_bound(), diff --git a/chain/network/src/lib.rs b/chain/network/src/lib.rs index 05bf13f6e0b..506a0ff9c05 100644 --- a/chain/network/src/lib.rs +++ b/chain/network/src/lib.rs @@ -1,4 +1,5 @@ pub use crate::peer_manager::peer_manager_actor::{Event, PeerManagerActor}; +pub use crate::rate_limits::messages_limits::OverrideConfig as MessagesLimitsOverrideConfig; mod accounts_data; mod announce_accounts; @@ -6,6 +7,7 @@ mod network_protocol; mod peer; mod peer_manager; mod private_actix; +mod rate_limits; mod snapshot_hosts; mod stats; mod store; diff --git a/chain/network/src/network_protocol/mod.rs b/chain/network/src/network_protocol/mod.rs index 80acf1c767d..e8ed2fc9cef 100644 --- a/chain/network/src/network_protocol/mod.rs +++ b/chain/network/src/network_protocol/mod.rs @@ -170,7 +170,7 @@ impl VersionedAccountData { /// due to account_id mismatch. Then instead of panicking we could return an error /// and the caller (who constructs the arguments) would do an unwrap(). This would /// consistute a cleaner never-panicking interface. - pub fn sign(self, signer: &dyn ValidatorSigner) -> anyhow::Result { + pub fn sign(self, signer: &ValidatorSigner) -> anyhow::Result { assert_eq!( self.account_key, signer.public_key(), @@ -256,7 +256,7 @@ impl OwnedAccount { /// Serializes OwnedAccount to proto and signs it using `signer`. /// Panics if OwnedAccount.account_key doesn't match signer.public_key(), /// as this would likely be a bug. - pub fn sign(self, signer: &dyn ValidatorSigner) -> SignedOwnedAccount { + pub fn sign(self, signer: &ValidatorSigner) -> SignedOwnedAccount { assert_eq!( self.account_key, signer.public_key(), diff --git a/chain/network/src/network_protocol/testonly.rs b/chain/network/src/network_protocol/testonly.rs index 44b9435005a..5019cceb757 100644 --- a/chain/network/src/network_protocol/testonly.rs +++ b/chain/network/src/network_protocol/testonly.rs @@ -41,8 +41,8 @@ pub fn make_genesis_block(clock: &time::Clock, chunks: Vec) -> Block } pub fn make_block( - clock: &time::Clock, - signer: &dyn ValidatorSigner, + clock: time::Clock, + signer: &ValidatorSigner, prev: &Block, chunks: Vec, ) -> Block { @@ -67,7 +67,8 @@ pub fn make_block( signer, CryptoHash::default(), CryptoHash::default(), - clock.now_utc(), + clock, + None, ) } @@ -160,7 +161,7 @@ pub fn make_signed_transaction(rng: &mut R) -> SignedTransaction { rng.gen(), sender.account_id.clone(), receiver, - &sender, + &sender.into(), 15, CryptoHash::default(), ) @@ -172,7 +173,7 @@ pub fn make_challenge(rng: &mut R) -> Challenge { left_block_header: rng.sample_iter(&Standard).take(65).collect(), right_block_header: rng.sample_iter(&Standard).take(34).collect(), }), - &make_validator_signer(rng), + &make_validator_signer(rng).into(), ) } @@ -213,8 +214,14 @@ impl ChunkSet { let shard_ids: Vec<_> = (0..4).collect(); // TODO: these are always genesis chunks. // Consider making this more realistic. - let chunks = - genesis_chunks(vec![StateRoot::new()], &shard_ids, 1000, 0, version::PROTOCOL_VERSION); + let chunks = genesis_chunks( + vec![StateRoot::new()], + vec![Default::default(); shard_ids.len()], + &shard_ids, + 1000, + 0, + version::PROTOCOL_VERSION, + ); self.chunks.extend(chunks.iter().map(|c| (c.chunk_hash(), c.clone()))); chunks } @@ -247,7 +254,12 @@ impl Chain { let signer = make_validator_signer(rng); for _ in 1..block_count { clock.advance(time::Duration::seconds(15)); - blocks.push(make_block(&clock.clock(), &signer, blocks.last().unwrap(), chunks.make())); + blocks.push(make_block( + clock.clock(), + &signer.clone().into(), + blocks.last().unwrap(), + chunks.make(), + )); } Chain { genesis_id: GenesisId { @@ -314,7 +326,7 @@ impl Chain { let peer_id = make_peer_id(rng); Arc::new( make_account_data(rng, 1, clock.now_utc(), v.public_key(), peer_id) - .sign(v) + .sign(&v.clone().into()) .unwrap(), ) }) @@ -400,7 +412,9 @@ pub fn make_account_data( pub fn make_signed_account_data(rng: &mut impl Rng, clock: &time::Clock) -> SignedAccountData { let signer = make_validator_signer(rng); let peer_id = make_peer_id(rng); - make_account_data(rng, 1, clock.now_utc(), signer.public_key(), peer_id).sign(&signer).unwrap() + make_account_data(rng, 1, clock.now_utc(), signer.public_key(), peer_id) + .sign(&signer.into()) + .unwrap() } // Accessors for creating malformed SignedAccountData diff --git a/chain/network/src/network_protocol/tests.rs b/chain/network/src/network_protocol/tests.rs index 2d339b1b2cc..7a27e49fea7 100644 --- a/chain/network/src/network_protocol/tests.rs +++ b/chain/network/src/network_protocol/tests.rs @@ -53,7 +53,7 @@ fn bad_account_data_size() { version: rng.gen(), timestamp: clock.now_utc(), }; - assert!(ad.sign(&signer).is_err()); + assert!(ad.sign(&signer.into()).is_err()); } #[test] diff --git a/chain/network/src/peer/peer_actor.rs b/chain/network/src/peer/peer_actor.rs index 30238f2fb69..bce87330329 100644 --- a/chain/network/src/peer/peer_actor.rs +++ b/chain/network/src/peer/peer_actor.rs @@ -22,6 +22,7 @@ use crate::peer_manager::network_state::{NetworkState, PRUNE_EDGES_AFTER}; use crate::peer_manager::peer_manager_actor::Event; use crate::peer_manager::peer_manager_actor::MAX_TIER2_PEERS; use crate::private_actix::{RegisterPeerError, SendMessage}; +use crate::rate_limits::messages_limits; use crate::routing::edge::verify_nonce; use crate::routing::NetworkTopologyChange; use crate::shards_manager::ShardsManagerRequestFromNetwork; @@ -57,6 +58,7 @@ use std::cmp::min; use std::fmt::Debug; use std::io; use std::net::SocketAddr; +use std::num::NonZeroUsize; use std::sync::atomic::Ordering; use std::sync::Arc; use tracing::Instrument as _; @@ -189,6 +191,9 @@ pub(crate) struct PeerActor { // TODO: move it to ConnectingStatus::Outbound. // When ready, use connection.peer_info instead. peer_info: DisplayOption, + + /// Per-message rate limits for incoming messages. + received_messages_rate_limits: messages_limits::RateLimits, } impl Debug for PeerActor { @@ -306,8 +311,14 @@ impl PeerActor { let my_node_info = PeerInfo { id: network_state.config.node_id(), addr: network_state.config.node_addr.as_ref().map(|a| **a), - account_id: network_state.config.validator.as_ref().map(|v| v.account_id()), + // TODO(validator-key-hot-swap) Consider using mutable validator signer instead of PeerInfo.account_id ? + // That likely requires bigger changes and account_id here is later used for debug / logging purposes only. + account_id: network_state.config.validator.account_id(), }; + let received_messages_rate_limits = messages_limits::RateLimits::from_config( + &network_state.config.received_messages_rate_limits, + clock.now(), + ); // recv is the HandshakeSignal returned by this spawn_inner() call. let (send, recv): (HandshakeSignalSender, HandshakeSignal) = tokio::sync::oneshot::channel(); @@ -333,7 +344,9 @@ impl PeerActor { framed, tracker: Default::default(), stats, - routed_message_cache: LruCache::new(ROUTED_MESSAGE_CACHE_SIZE), + routed_message_cache: LruCache::new( + NonZeroUsize::new(ROUTED_MESSAGE_CACHE_SIZE).unwrap(), + ), protocol_buffers_supported: false, force_encoding, peer_info: match &stream_type { @@ -346,6 +359,7 @@ impl PeerActor { } .into(), network_state, + received_messages_rate_limits, } }), recv, @@ -461,13 +475,13 @@ impl PeerActor { archival: self.network_state.config.archive, }, partial_edge_info: spec.partial_edge_info, - owned_account: self.network_state.config.validator.as_ref().map(|vc| { + owned_account: self.network_state.config.validator.signer.get().map(|signer| { OwnedAccount { - account_key: vc.signer.public_key(), + account_key: signer.public_key(), peer_id: self.network_state.config.node_id(), timestamp: self.clock.now_utc(), } - .sign(vc.signer.as_ref()) + .sign(&signer) }), }; let msg = match spec.tier { @@ -1533,7 +1547,7 @@ impl PeerActor { .into_iter() .map(|aa| { let id = aa.account_id.clone(); - (aa, old.get(&id).map(|old| old.epoch_id.clone())) + (aa, old.get(&id).map(|old| old.epoch_id)) }) .collect(); match network_state.client.send_async(AnnounceAccountRequest(accounts)).await { @@ -1727,12 +1741,18 @@ impl actix::Handler for PeerActor { tracing::trace!(target: "network", "Received message: {}", peer_msg); + let now = self.clock.now(); { let labels = [peer_msg.msg_variant()]; metrics::PEER_MESSAGE_RECEIVED_BY_TYPE_TOTAL.with_label_values(&labels).inc(); metrics::PEER_MESSAGE_RECEIVED_BY_TYPE_BYTES .with_label_values(&labels) .inc_by(msg.len() as u64); + if !self.received_messages_rate_limits.is_allowed(&peer_msg, now) { + metrics::PEER_MESSAGE_RATE_LIMITED_BY_TYPE_TOTAL.with_label_values(&labels).inc(); + tracing::debug!(target: "network", "Peer {} is being rate limited for message {}", self.peer_info, peer_msg.msg_variant()); + return; + } } match &self.peer_status { PeerStatus::Connecting { .. } => self.handle_msg_connecting(ctx, peer_msg), @@ -1741,7 +1761,7 @@ impl actix::Handler for PeerActor { tracing::warn!(target: "network", "Received {} from closing connection {:?}. Ignoring", peer_msg, self.peer_type); return; } - conn.last_time_received_message.store(self.clock.now()); + conn.last_time_received_message.store(now); // Check if the message type is allowed given the TIER of the connection: // TIER1 connections are reserved exclusively for BFT consensus messages. if !conn.tier.is_allowed(&peer_msg) { @@ -1758,7 +1778,7 @@ impl actix::Handler for PeerActor { // case when our peer doesn't use that logic yet. if let Some(skip_tombstones) = self.network_state.config.skip_tombstones { if let PeerMessage::SyncRoutingTable(routing_table) = &mut peer_msg { - if conn.established_time + skip_tombstones > self.clock.now() { + if conn.established_time + skip_tombstones > now { routing_table .edges .retain(|edge| edge.edge_type() == EdgeState::Active); diff --git a/chain/network/src/peer/tests/mod.rs b/chain/network/src/peer/tests/mod.rs index fd35dbe7b0d..9e3cfb80df3 100644 --- a/chain/network/src/peer/tests/mod.rs +++ b/chain/network/src/peer/tests/mod.rs @@ -1,2 +1,3 @@ mod communication; +mod rate_limits; mod stream; diff --git a/chain/network/src/peer/tests/rate_limits.rs b/chain/network/src/peer/tests/rate_limits.rs new file mode 100644 index 00000000000..a507802a15b --- /dev/null +++ b/chain/network/src/peer/tests/rate_limits.rs @@ -0,0 +1,209 @@ +use crate::broadcast::Receiver; +use crate::config::NetworkConfig; +use crate::network_protocol::{testonly as data, PartialEncodedChunkRequestMsg, RoutedMessageBody}; +use crate::network_protocol::{Encoding, PeerMessage}; +use crate::peer::testonly::{Event, PeerConfig, PeerHandle}; +use crate::peer_manager::peer_manager_actor::Event as PME; +use crate::rate_limits::messages_limits; +use crate::tcp; +use crate::testonly::{make_rng, Rng}; +use near_async::time::FakeClock; +use near_o11y::testonly::init_test_logger; +use near_primitives::hash::CryptoHash; +use rand::Rng as _; +use std::sync::Arc; +use std::time::Duration; +use tokio::time::{sleep, sleep_until, Instant}; + +#[tokio::test] +// Verifies that peer traffic is rate limited per message type. Not all messages are rate limited. +// This test works by sending many messages very quickly and then check how many of them +// were effectively processed by the receiver. +async fn test_message_rate_limits() -> anyhow::Result<()> { + init_test_logger(); + tracing::info!("test_message_rate_limits"); + + let mut clock = FakeClock::default(); + let mut rng = make_rng(89028037453); + let (outbound, inbound) = setup_test_peers(&mut clock, &mut rng).await; + + const MESSAGES: u32 = 7; + // Let's gather all events received from now on. We'll check them later, after producing messages. + let mut events = inbound.events.from_now(); + let messages_samples = send_messages(&inbound, &outbound, &mut rng, MESSAGES).await; + + // Check how many messages of each type have been received. + let messages_received = + wait_for_similar_messages(&messages_samples, &mut events, Duration::from_secs(3)).await; + tracing::debug!(target:"test","received {messages_received:?} messages"); + // BlockRequest gets rate limited (7 sent vs 5 bucket_start). + assert!(messages_received[0] < MESSAGES); + // PartialEncodedChunkRequest gets rate limited (7 sent vs 5 bucket_start). + assert!(messages_received[1] < MESSAGES); + // Transaction doesn't get rate limited (7 sent vs 50 bucket_start). + assert_eq!(messages_received[2], MESSAGES); + + Ok(()) +} + +#[tokio::test] +// Verifies that peer traffic is not rate limited when messages are sent at regular intervals, +// and the total number of messages is below the limit. +async fn test_message_rate_limits_over_time() -> anyhow::Result<()> { + init_test_logger(); + tracing::info!("test_message_rate_limits_over_time"); + + let mut clock = FakeClock::default(); + let mut rng = make_rng(89028037453); + let (outbound, inbound) = setup_test_peers(&mut clock, &mut rng).await; + + const MESSAGES: u32 = 4; + const INTERVAL: Duration = Duration::from_secs(2); + // Let's gather all events received from now on. We'll check them later, after producing messages. + let mut events = inbound.events.from_now(); + + // Send 4 messages of each type every 2 seconds, three times. + let mut messages_samples = Vec::new(); + let now = clock.now(); + for i in 0..3 { + messages_samples = send_messages(&inbound, &outbound, &mut rng, MESSAGES).await; + // Advance the fake clock to refresh rate limits. + clock.advance_until(now + INTERVAL * (i + 1)); + // Give some time to peer actors to process messages. + sleep(Duration::from_secs(1)).await; + } + + let messages_received = + wait_for_similar_messages(&messages_samples, &mut events, Duration::from_secs(3)).await; + tracing::debug!(target:"test","received {messages_received:?} messages"); + // BlockRequest and PartialEncodedChunkRequest don't get rate limited + // 12 sent vs 5 bucket_start + 2.5 refilled * 4s + assert_eq!(messages_received[0], MESSAGES * 3); + assert_eq!(messages_received[1], MESSAGES * 3); + // Transaction doesn't get rate limited (12 sent vs 50 bucket_start). + assert_eq!(messages_received[2], MESSAGES * 3); + + Ok(()) +} + +/// Waits up to `duration` and then checks how many events equal (in type only) to each one of `samples` +/// have been received. +/// +/// Returns a vector of the same size of `samples`. +async fn wait_for_similar_messages( + samples: &[PeerMessage], + events: &mut Receiver, + duration: Duration, +) -> Vec { + let mut messages_received = vec![0; 3]; + sleep_until(Instant::now() + duration).await; + while let Some(event) = events.try_recv() { + match event { + Event::Network(PME::MessageProcessed(_, got)) => { + for (i, sample) in samples.iter().enumerate() { + if sample.msg_variant() == got.msg_variant() { + messages_received[i] += 1; + } + } + } + _ => {} + } + } + messages_received +} + +/// Setup two connected peers. +/// +/// Rate limits configuration: +/// - `BlockRequest`, `PartialEncodedChunkRequest`: bucket_start = 5, bucket_max = 10, refill_rate = 2.5/s +/// - `Transaction`: bucket_start = bucket_max = 50, refill_rate = 5/s +async fn setup_test_peers(clock: &mut FakeClock, mut rng: &mut Rng) -> (PeerHandle, PeerHandle) { + let chain = Arc::new(data::Chain::make(clock, &mut rng, 12)); + + // Customize the network configuration to set some arbitrary rate limits. + let add_rate_limits = |mut network_config: NetworkConfig| { + let rate_limits = &mut network_config.received_messages_rate_limits.rate_limits; + use messages_limits::RateLimitedPeerMessageKey::*; + rate_limits + .insert(BlockRequest, messages_limits::SingleMessageConfig::new(10, 2.5, Some(5))); + rate_limits.insert( + PartialEncodedChunkRequest, + messages_limits::SingleMessageConfig::new(10, 2.5, Some(5)), + ); + rate_limits.insert(Transaction, messages_limits::SingleMessageConfig::new(50, 5.0, None)); + network_config + }; + + let inbound_cfg = PeerConfig { + chain: chain.clone(), + network: add_rate_limits(chain.make_config(&mut rng)), + force_encoding: Some(Encoding::Proto), + }; + let outbound_cfg = PeerConfig { + chain: chain.clone(), + network: add_rate_limits(chain.make_config(&mut rng)), + force_encoding: Some(Encoding::Proto), + }; + let (outbound_stream, inbound_stream) = + tcp::Stream::loopback(inbound_cfg.id(), tcp::Tier::T2).await; + let mut inbound = PeerHandle::start_endpoint(clock.clock(), inbound_cfg, inbound_stream).await; + let mut outbound = + PeerHandle::start_endpoint(clock.clock(), outbound_cfg, outbound_stream).await; + + outbound.complete_handshake().await; + inbound.complete_handshake().await; + (outbound, inbound) +} + +/// Sends samples of various messages: +/// - `BlockRequest` +/// - `PartialEncodedChunkRequest` +/// - `Transaction` +/// +/// Messages are sent `count` times each. +/// +/// Returns a vector with an example of one of each message above (useful for comparisons.) +async fn send_messages( + inbound: &PeerHandle, + outbound: &PeerHandle, + rng: &mut Rng, + count: u32, +) -> Vec { + let mut messages_samples = Vec::new(); + + tracing::info!(target:"test","send BlockRequest"); + let message = PeerMessage::BlockRequest(CryptoHash::default()); + for _ in 0..count { + outbound.send(message.clone()).await; + } + messages_samples.push(message); + + tracing::info!(target:"test","send PartialEncodedChunkRequest"); + // Duplicated routed messages are filtered out so we must tweak each message to make it unique. + + for i in 0..count { + let message = PeerMessage::Routed(Box::new(outbound.routed_message( + RoutedMessageBody::PartialEncodedChunkRequest(PartialEncodedChunkRequestMsg { + chunk_hash: outbound.cfg.chain.blocks[5].chunks()[2].chunk_hash(), + part_ords: vec![rng.gen()], + tracking_shards: Default::default(), + }), + inbound.cfg.id(), + 1, + None, + ))); + outbound.send(message.clone()).await; + if i == count - 1 { + messages_samples.push(message); + } + } + + tracing::info!(target:"test","send Transaction"); + let message = PeerMessage::Transaction(data::make_signed_transaction(rng)); + for _ in 0..count { + outbound.send(message.clone()).await; + } + messages_samples.push(message); + + messages_samples +} diff --git a/chain/network/src/peer_manager/connection/mod.rs b/chain/network/src/peer_manager/connection/mod.rs index 3c45ad99760..7bbc50f23f7 100644 --- a/chain/network/src/peer_manager/connection/mod.rs +++ b/chain/network/src/peer_manager/connection/mod.rs @@ -47,6 +47,8 @@ impl tcp::Tier { match body { RoutedMessageBody::BlockApproval(..) | RoutedMessageBody::ChunkEndorsement(..) + | RoutedMessageBody::PartialEncodedStateWitness(..) + | RoutedMessageBody::PartialEncodedStateWitnessForward(..) | RoutedMessageBody::VersionedPartialEncodedChunk(..) => true, _ => self == tcp::Tier::T2, } diff --git a/chain/network/src/peer_manager/network_state/mod.rs b/chain/network/src/peer_manager/network_state/mod.rs index d66b8208120..b4090fa9fe3 100644 --- a/chain/network/src/peer_manager/network_state/mod.rs +++ b/chain/network/src/peer_manager/network_state/mod.rs @@ -33,6 +33,7 @@ use near_primitives::network::PeerId; use near_primitives::types::AccountId; use parking_lot::Mutex; use std::net::SocketAddr; +use std::num::NonZeroUsize; use std::sync::atomic::AtomicUsize; use std::sync::Arc; use tracing::Instrument as _; @@ -196,7 +197,7 @@ impl NetworkState { tier2_route_back: Mutex::new(RouteBackCache::default()), tier1_route_back: Mutex::new(RouteBackCache::default()), recent_routed_messages: Mutex::new(lru::LruCache::new( - RECENT_ROUTED_MESSAGES_CACHE_SIZE, + NonZeroUsize::new(RECENT_ROUTED_MESSAGES_CACHE_SIZE).unwrap(), )), txns_since_last_block: AtomicUsize::new(0), whitelist_nodes, @@ -426,9 +427,10 @@ impl NetworkState { interval.tick(&clock).await; let result = async { - let stream = tcp::Stream::connect(&peer_info, tcp::Tier::T2) - .await - .context("tcp::Stream::connect()")?; + let stream = + tcp::Stream::connect(&peer_info, tcp::Tier::T2, &self.config.socket_options) + .await + .context("tcp::Stream::connect()")?; PeerActor::spawn_and_handshake(clock.clone(), stream, None, self.clone()) .await .context("PeerActor::spawn()")?; @@ -501,7 +503,7 @@ impl NetworkState { // Check if the message is for myself and don't try to send it in that case. if let PeerIdOrHash::PeerId(target) = &msg.target { if target == &my_peer_id { - tracing::debug!(target: "network", account_id = ?self.config.validator.as_ref().map(|v|v.account_id()), ?my_peer_id, ?msg, "Drop signed message to myself"); + tracing::debug!(target: "network", account_id = ?self.config.validator.account_id(), ?my_peer_id, ?msg, "Drop signed message to myself"); metrics::CONNECTED_TO_MYSELF.inc(); return false; } @@ -538,7 +540,7 @@ impl NetworkState { metrics::MessageDropped::NoRouteFound.inc(&msg.body); tracing::debug!(target: "network", - account_id = ?self.config.validator.as_ref().map(|v|v.account_id()), + account_id = ?self.config.validator.account_id(), to = ?msg.target, reason = ?find_route_error, known_peers = ?self.graph.routing_table.reachable_peers(), @@ -608,7 +610,7 @@ impl NetworkState { // TODO(MarX, #1369): Message is dropped here. Define policy for this case. metrics::MessageDropped::UnknownAccount.inc(&msg); tracing::debug!(target: "network", - account_id = ?self.config.validator.as_ref().map(|v|v.account_id()), + account_id = ?self.config.validator.account_id(), to = ?account_id, ?msg,"Drop message: unknown account", ); diff --git a/chain/network/src/peer_manager/network_state/routing.rs b/chain/network/src/peer_manager/network_state/routing.rs index f01c4cb9435..0fe045fcdad 100644 --- a/chain/network/src/peer_manager/network_state/routing.rs +++ b/chain/network/src/peer_manager/network_state/routing.rs @@ -45,7 +45,7 @@ impl NetworkState { let this = self.clone(); self.spawn(async move { let new_accounts = this.account_announcements.add_accounts(accounts); - tracing::debug!(target: "network", account_id = ?this.config.validator.as_ref().map(|v|v.account_id()), ?new_accounts, "Received new accounts"); + tracing::debug!(target: "network", account_id = ?this.config.validator.account_id(), ?new_accounts, "Received new accounts"); #[cfg(test)] this.config.event_sink.send(crate::peer_manager::peer_manager_actor::Event::AccountsAdded(new_accounts.clone())); this.broadcast_routing_table_update(RoutingTableUpdate::from_accounts( diff --git a/chain/network/src/peer_manager/network_state/tier1.rs b/chain/network/src/peer_manager/network_state/tier1.rs index d7d06a82c30..2aa677bc922 100644 --- a/chain/network/src/peer_manager/network_state/tier1.rs +++ b/chain/network/src/peer_manager/network_state/tier1.rs @@ -1,5 +1,5 @@ use crate::accounts_data::{AccountDataCacheSnapshot, LocalAccountData}; -use crate::config; +use crate::config::{self, FrozenValidatorConfig}; use crate::network_protocol::{ AccountData, PeerAddr, PeerInfo, PeerMessage, SignedAccountData, SyncAccountsData, }; @@ -18,18 +18,23 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; impl super::NetworkState { - // Returns ValidatorConfig of this node iff it belongs to TIER1 according to `accounts_data`. - pub fn tier1_validator_config( + // Returns a snapshot of ValidatorConfig of this node iff it belongs to TIER1 according to `accounts_data`. + fn tier1_validator_config( &self, accounts_data: &AccountDataCacheSnapshot, - ) -> Option<&config::ValidatorConfig> { + ) -> Option { if self.config.tier1.is_none() { return None; } - self.config - .validator + let signer = self.config.validator.signer.get(); + if signer .as_ref() - .filter(|cfg| accounts_data.keys.contains(&cfg.signer.public_key())) + .filter(|signer| accounts_data.keys.contains(&signer.public_key())) + .is_none() + { + return None; + } + Some(FrozenValidatorConfig { signer, proxies: &self.config.validator.proxies }) } async fn tier1_connect_to_my_proxies( @@ -54,6 +59,7 @@ impl super::NetworkState { account_id: None, }, tcp::Tier::T1, + &self.config.socket_options, ) .await?; anyhow::Ok(PeerActor::spawn_and_handshake(clock.clone(), stream, None, self.clone()).await?) @@ -94,6 +100,7 @@ impl super::NetworkState { let accounts_data = self.accounts_data.load(); let vc = self.tier1_validator_config(&accounts_data)?; + let signer = vc.signer?; let proxies = match (&self.config.node_addr, &vc.proxies) { (None, _) => vec![], (_, config::ValidatorProxies::Static(peer_addrs)) => peer_addrs.clone(), @@ -106,8 +113,10 @@ impl super::NetworkState { // Query all the STUN servers in parallel. let queries = stun_servers.iter().map(|addr| { let clock = clock.clone(); + let want_ipv4 = node_addr.is_ipv4(); let addr = addr.clone(); self.spawn(async move { + let addr = stun::lookup_host(&addr, want_ipv4).await?; match stun::query(&clock, &addr).await { Ok(ip) => Some(ip), Err(err) => { @@ -188,7 +197,7 @@ impl super::NetworkState { let new_data = self.accounts_data.set_local( clock, LocalAccountData { - signer: vc.signer.clone(), + signer, data: Arc::new(AccountData { peer_id: self.config.node_id(), proxies: my_proxies }), }, ); @@ -271,7 +280,7 @@ impl super::NetworkState { // Construct a safe set of connections. let mut safe_set: HashSet = safe.values().map(|v| (*v).clone()).collect(); // Add proxies of our node to the safe set. - if let Some(vc) = validator_cfg { + if let Some(vc) = validator_cfg.as_ref() { match &vc.proxies { config::ValidatorProxies::Dynamic(_) => { safe_set.insert(self.config.node_id()); @@ -291,6 +300,7 @@ impl super::NetworkState { } } if let Some(vc) = validator_cfg { + let validator_signer = if let Some(v) = vc.signer { v } else { return }; // Try to establish new TIER1 connections to accounts in random order. let mut handles = vec![]; let mut account_keys: Vec<_> = proxies_by_account.keys().copied().collect(); @@ -299,7 +309,7 @@ impl super::NetworkState { // tier1_connect() is responsible for connecting to proxies // of this node. tier1_connect() connects only to proxies // of other TIER1 nodes. - if account_key == &vc.signer.public_key() { + if account_key == &validator_signer.public_key() { continue; } // Bound the number of connections established at a single call to @@ -327,6 +337,7 @@ impl super::NetworkState { account_id: None, }, tcp::Tier::T1, + &self.config.socket_options, ) .await?; PeerActor::spawn_and_handshake(clock.clone(), stream, None, self.clone()) diff --git a/chain/network/src/peer_manager/peer_manager_actor.rs b/chain/network/src/peer_manager/peer_manager_actor.rs index 07f8dbc4bed..fed881aa6ef 100644 --- a/chain/network/src/peer_manager/peer_manager_actor.rs +++ b/chain/network/src/peer_manager/peer_manager_actor.rs @@ -601,7 +601,7 @@ impl PeerManagerActor { let clock = self.clock.clone(); async move { let result = async { - let stream = tcp::Stream::connect(&peer_info, tcp::Tier::T2).await.context("tcp::Stream::connect()")?; + let stream = tcp::Stream::connect(&peer_info, tcp::Tier::T2, &state.config.socket_options).await.context("tcp::Stream::connect()")?; PeerActor::spawn_and_handshake(clock.clone(),stream,None,state.clone()).await.context("PeerActor::spawn()")?; anyhow::Ok(()) }.await; @@ -1026,6 +1026,14 @@ impl PeerManagerActor { self.handle_msg_network_requests(msg, ctx), ) } + PeerManagerMessageRequest::AdvertiseTier1Proxies => { + let state = self.state.clone(); + let clock = self.clock.clone(); + ctx.spawn(wrap_future(async move { + state.tier1_advertise_proxies(&clock).await; + })); + PeerManagerMessageResponse::AdvertiseTier1Proxies + } PeerManagerMessageRequest::OutboundTcpConnect(stream) => { let peer_addr = stream.peer_addr; if let Err(err) = diff --git a/chain/network/src/peer_manager/peer_store/mod.rs b/chain/network/src/peer_manager/peer_store/mod.rs index d358189dd83..4e5dbf88b24 100644 --- a/chain/network/src/peer_manager/peer_store/mod.rs +++ b/chain/network/src/peer_manager/peer_store/mod.rs @@ -11,6 +11,7 @@ use parking_lot::Mutex; use rand::seq::IteratorRandom; use rand::thread_rng; use std::net::SocketAddr; +use std::num::NonZeroUsize; use std::ops::Not; #[cfg(test)] @@ -290,7 +291,8 @@ impl PeerStore { pub fn new(clock: &time::Clock, config: Config) -> anyhow::Result { let boot_nodes: HashSet<_> = config.boot_nodes.iter().map(|p| p.id.clone()).collect(); // A mapping from `PeerId` to `KnownPeerState`. - let mut peerid_2_state = LruCache::new(config.peer_states_cache_size as usize); + let mut peerid_2_state = + LruCache::new(NonZeroUsize::new(config.peer_states_cache_size as usize).unwrap()); // Stores mapping from `SocketAddr` to `VerifiedPeer`, which contains `PeerId`. // Only one peer can exist with given `PeerId` or `SocketAddr`. // In case of collision, we will choose the first one. diff --git a/chain/network/src/peer_manager/testonly.rs b/chain/network/src/peer_manager/testonly.rs index 282e3f9575a..5ef9abce822 100644 --- a/chain/network/src/peer_manager/testonly.rs +++ b/chain/network/src/peer_manager/testonly.rs @@ -110,7 +110,7 @@ pub(crate) fn make_chain_info( let mut chain_info = chain.get_chain_info(); let mut account_keys = AccountKeys::new(); for cfg in validators { - let s = &cfg.validator.as_ref().unwrap().signer; + let s = &cfg.validator.signer.get().unwrap(); account_keys.entry(s.validator_id().clone()).or_default().insert(s.public_key()); } chain_info.tier1_accounts = Arc::new(account_keys); @@ -181,7 +181,9 @@ impl ActorHandler { pub async fn send_outbound_connect(&self, peer_info: &PeerInfo, tier: tcp::Tier) { let addr = self.actix.addr.clone(); let peer_info = peer_info.clone(); - let stream = tcp::Stream::connect(&peer_info, tier).await.unwrap(); + let stream = tcp::Stream::connect(&peer_info, tier, &config::SocketOptions::default()) + .await + .unwrap(); addr.do_send(PeerManagerMessageRequest::OutboundTcpConnect(stream).with_span_context()); } @@ -194,7 +196,9 @@ impl ActorHandler { let events = self.events.clone(); let peer_info = peer_info.clone(); async move { - let stream = tcp::Stream::connect(&peer_info, tier).await.unwrap(); + let stream = tcp::Stream::connect(&peer_info, tier, &config::SocketOptions::default()) + .await + .unwrap(); let mut events = events.from_now(); let stream_id = stream.id(); addr.do_send(PeerManagerMessageRequest::OutboundTcpConnect(stream).with_span_context()); diff --git a/chain/network/src/peer_manager/tests/accounts_data.rs b/chain/network/src/peer_manager/tests/accounts_data.rs index bf8350c7507..660ddb78545 100644 --- a/chain/network/src/peer_manager/tests/accounts_data.rs +++ b/chain/network/src/peer_manager/tests/accounts_data.rs @@ -322,7 +322,7 @@ async fn validator_node_restart() { // Now pm0 should learn from pm1 about the conflicting version and should broadcast // new AccountData (with higher version) to override the old AccountData. - let pm0_account_key = cfg.validator.as_ref().unwrap().signer.public_key(); + let pm0_account_key = cfg.validator.signer.get().unwrap().public_key(); pm1.wait_for_accounts_data_pred(|accounts_data| { let data = match accounts_data.data.get(&pm0_account_key) { Some(it) => it, diff --git a/chain/network/src/peer_manager/tests/connection_pool.rs b/chain/network/src/peer_manager/tests/connection_pool.rs index 34fe0b20f56..79e00807f20 100644 --- a/chain/network/src/peer_manager/tests/connection_pool.rs +++ b/chain/network/src/peer_manager/tests/connection_pool.rs @@ -1,3 +1,4 @@ +use crate::config::SocketOptions; use crate::network_protocol::testonly as data; use crate::network_protocol::PeerMessage; use crate::network_protocol::{Encoding, Handshake, OwnedAccount, PartialEdgeInfo}; @@ -84,7 +85,9 @@ async fn loop_connection() { ); // An inbound connection pretending to be a loop should be rejected. - let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2).await.unwrap(); + let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2, &SocketOptions::default()) + .await + .unwrap(); let stream_id = stream.id(); let port = stream.local_addr.port(); let mut events = pm.events.from_now(); @@ -142,13 +145,15 @@ async fn owned_account_mismatch() { .await; // An inbound connection pretending to be a loop should be rejected. - let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2).await.unwrap(); + let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2, &SocketOptions::default()) + .await + .unwrap(); let stream_id = stream.id(); let port = stream.local_addr.port(); let mut events = pm.events.from_now(); let mut stream = Stream::new(Some(Encoding::Proto), stream); let cfg = chain.make_config(rng); - let vc = cfg.validator.clone().unwrap(); + let signer = cfg.validator.signer.get().unwrap(); stream .write(&PeerMessage::Tier2Handshake(Handshake { protocol_version: PROTOCOL_VERSION, @@ -165,12 +170,12 @@ async fn owned_account_mismatch() { ), owned_account: Some( OwnedAccount { - account_key: vc.signer.public_key().clone(), + account_key: signer.public_key(), // random peer_id, different than the expected cfg.node_id(). peer_id: data::make_peer_id(rng), timestamp: clock.now_utc(), } - .sign(vc.signer.as_ref()), + .sign(&signer), ), })) .await; @@ -218,7 +223,7 @@ async fn owned_account_conflict() { ClosingReason::RejectedByPeerManager(RegisterPeerError::PoolError( connection::PoolError::AlreadyConnectedAccount { peer_id: cfg1.node_id(), - account_key: cfg1.validator.as_ref().unwrap().signer.public_key(), + account_key: cfg1.validator.signer.get().unwrap().public_key(), } )) ); @@ -270,12 +275,14 @@ async fn invalid_edge() { for (name, edge) in &testcases { for tier in [tcp::Tier::T1, tcp::Tier::T2] { tracing::info!(target:"test","{name} {tier:?}"); - let stream = tcp::Stream::connect(&pm.peer_info(), tier).await.unwrap(); + let stream = tcp::Stream::connect(&pm.peer_info(), tier, &SocketOptions::default()) + .await + .unwrap(); let stream_id = stream.id(); let port = stream.local_addr.port(); let mut events = pm.events.from_now(); let mut stream = Stream::new(Some(Encoding::Proto), stream); - let vc = cfg.validator.clone().unwrap(); + let signer = cfg.validator.signer.get().unwrap(); let handshake = Handshake { protocol_version: PROTOCOL_VERSION, oldest_supported_version: PROTOCOL_VERSION, @@ -286,11 +293,11 @@ async fn invalid_edge() { partial_edge_info: edge.clone(), owned_account: Some( OwnedAccount { - account_key: vc.signer.public_key().clone(), + account_key: signer.public_key(), peer_id: cfg.node_id(), timestamp: clock.now_utc(), } - .sign(vc.signer.as_ref()), + .sign(&signer), ), }; let handshake = match tier { diff --git a/chain/network/src/peer_manager/tests/nonce.rs b/chain/network/src/peer_manager/tests/nonce.rs index ac12bf8b46c..f14771d3c02 100644 --- a/chain/network/src/peer_manager/tests/nonce.rs +++ b/chain/network/src/peer_manager/tests/nonce.rs @@ -1,3 +1,4 @@ +use crate::config::SocketOptions; use crate::network_protocol::testonly as data; use crate::network_protocol::{Encoding, Handshake, PartialEdgeInfo, PeerMessage}; use crate::peer_manager::testonly::{ActorHandler, Event}; @@ -56,7 +57,10 @@ async fn test_nonces() { ) .await; - let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2).await.unwrap(); + let stream = + tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2, &SocketOptions::default()) + .await + .unwrap(); let mut stream = stream::Stream::new(Some(Encoding::Proto), stream); let peer_key = data::make_secret_key(rng); let peer_id = PeerId::new(peer_key.public_key()); diff --git a/chain/network/src/peer_manager/tests/routing.rs b/chain/network/src/peer_manager/tests/routing.rs index 53b7381e05f..3772149564b 100644 --- a/chain/network/src/peer_manager/tests/routing.rs +++ b/chain/network/src/peer_manager/tests/routing.rs @@ -1,6 +1,6 @@ use crate::blacklist; use crate::broadcast; -use crate::config::NetworkConfig; +use crate::config::{NetworkConfig, SocketOptions}; use crate::network_protocol::testonly as data; use crate::network_protocol::{Encoding, Ping, Pong, RoutedMessageBody, RoutingTableUpdate}; use crate::peer; @@ -899,7 +899,9 @@ async fn ttl() { chain, force_encoding: Some(Encoding::Proto), }; - let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2).await.unwrap(); + let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2, &SocketOptions::default()) + .await + .unwrap(); let mut peer = peer::testonly::PeerHandle::start_endpoint(clock.clock(), cfg, stream).await; peer.complete_handshake().await; pm.wait_for_routing_table(&[(peer.cfg.id(), vec![peer.cfg.id()])]).await; @@ -954,7 +956,9 @@ async fn repeated_data_in_sync_routing_table() { chain, force_encoding: Some(Encoding::Proto), }; - let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2).await.unwrap(); + let stream = tcp::Stream::connect(&pm.peer_info(), tcp::Tier::T2, &SocketOptions::default()) + .await + .unwrap(); let mut peer = peer::testonly::PeerHandle::start_endpoint(clock.clock(), cfg, stream).await; peer.complete_handshake().await; diff --git a/chain/network/src/peer_manager/tests/tier1.rs b/chain/network/src/peer_manager/tests/tier1.rs index 24c2ca6f859..2a7945ddb59 100644 --- a/chain/network/src/peer_manager/tests/tier1.rs +++ b/chain/network/src/peer_manager/tests/tier1.rs @@ -18,7 +18,7 @@ use std::collections::HashSet; use std::sync::Arc; /// Constructs a random TIER1 message. -fn make_block_approval(rng: &mut Rng, signer: &dyn ValidatorSigner) -> Approval { +fn make_block_approval(rng: &mut Rng, signer: &ValidatorSigner) -> Approval { let inner = ApprovalInner::Endorsement(data::make_hash(rng)); let target_height = rng.gen_range(0..100000); Approval { @@ -55,8 +55,8 @@ async fn send_tier1_message( from: &peer_manager::testonly::ActorHandler, to: &peer_manager::testonly::ActorHandler, ) -> Option { - let from_signer = from.cfg.validator.as_ref().unwrap().signer.clone(); - let to_signer = to.cfg.validator.as_ref().unwrap().signer.clone(); + let from_signer = from.cfg.validator.signer.get().unwrap(); + let to_signer = to.cfg.validator.signer.get().unwrap(); let target = to_signer.validator_id().clone(); let want = RoutedMessageBody::BlockApproval(make_block_approval(rng, from_signer.as_ref())); let clock = clock.clone(); @@ -211,11 +211,10 @@ async fn proxy_connections() { let mut validators = vec![]; for i in 0..N { let mut cfg = chain.make_config(rng); - cfg.validator.as_mut().unwrap().proxies = - config::ValidatorProxies::Static(vec![PeerAddr { - peer_id: proxies[i].cfg.node_id(), - addr: **proxies[i].cfg.node_addr.as_ref().unwrap(), - }]); + cfg.validator.proxies = config::ValidatorProxies::Static(vec![PeerAddr { + peer_id: proxies[i].cfg.node_id(), + addr: **proxies[i].cfg.node_addr.as_ref().unwrap(), + }]); validators .push(start_pm(clock.clock(), near_store::db::TestDB::new(), cfg, chain.clone()).await); } @@ -307,12 +306,12 @@ async fn proxy_change() { let p0cfg = chain.make_config(rng); let p1cfg = chain.make_config(rng); let mut v0cfg = chain.make_config(rng); - v0cfg.validator.as_mut().unwrap().proxies = config::ValidatorProxies::Static(vec![ + v0cfg.validator.proxies = config::ValidatorProxies::Static(vec![ PeerAddr { peer_id: p0cfg.node_id(), addr: **p0cfg.node_addr.as_ref().unwrap() }, PeerAddr { peer_id: p1cfg.node_id(), addr: **p1cfg.node_addr.as_ref().unwrap() }, ]); let mut v1cfg = chain.make_config(rng); - v1cfg.validator.as_mut().unwrap().proxies = config::ValidatorProxies::Static(vec![]); + v1cfg.validator.proxies = config::ValidatorProxies::Static(vec![]); tracing::info!(target:"test", "Start all nodes."); let p0 = start_pm(clock.clock(), TestDB::new(), p0cfg.clone(), chain.clone()).await; @@ -401,8 +400,10 @@ async fn stun_self_discovery() { let stun_server1 = stun::testonly::Server::new().await; let stun_server2 = stun::testonly::Server::new().await; let mut cfg = chain.make_config(rng); - let vc = cfg.validator.as_mut().unwrap(); - vc.proxies = config::ValidatorProxies::Dynamic(vec![stun_server1.addr(), stun_server2.addr()]); + cfg.validator.proxies = config::ValidatorProxies::Dynamic(vec![ + stun_server1.addr().to_string(), + stun_server2.addr().to_string(), + ]); tracing::info!(target:"test", "spawn a node and advertize AccountData."); let pm = start_pm(clock.clock(), TestDB::new(), cfg, chain.clone()).await; diff --git a/chain/network/src/rate_limits/messages_limits.rs b/chain/network/src/rate_limits/messages_limits.rs new file mode 100644 index 00000000000..fead2da18d0 --- /dev/null +++ b/chain/network/src/rate_limits/messages_limits.rs @@ -0,0 +1,444 @@ +//! This module facilitates the initialization and the storage +//! of rate limits per message. + +use std::collections::HashMap; + +use enum_map::{enum_map, EnumMap}; +use near_async::time::Instant; + +use crate::network_protocol::{PeerMessage, RoutedMessageBody}; + +use super::token_bucket::{TokenBucket, TokenBucketError}; + +/// Object responsible to manage the rate limits of all network messages +/// for a single connection/peer. +#[derive(Default)] +pub struct RateLimits { + buckets: EnumMap>, +} + +impl RateLimits { + /// Creates all buckets as configured in `config`. + /// See also [TokenBucket::new]. + pub fn from_config(config: &Config, start_time: Instant) -> Self { + let mut buckets = enum_map! { _ => None }; + // Configuration is assumed to be correct. Any failure to build a bucket is ignored. + for (key, message_config) in &config.rate_limits { + let initial_size = message_config.initial_size.unwrap_or(message_config.maximum_size); + match TokenBucket::new( + initial_size, + message_config.maximum_size, + message_config.refill_rate, + start_time, + ) { + Ok(bucket) => buckets[*key] = Some(bucket), + Err(err) => { + tracing::warn!(target: "network", "ignoring rate limit for {key} due to an error ({err})") + } + } + } + Self { buckets } + } + + /// Checks if the given message is under the rate limits. + /// + /// # Arguments + /// + /// * `message` - The network message to be checked + /// * `now` - Current time + /// + /// Returns `true` if the message should be allowed to continue. Otherwise, + /// if it should be rate limited, returns `false`. + pub fn is_allowed(&mut self, message: &PeerMessage, now: Instant) -> bool { + if let Some((key, cost)) = get_key_and_token_cost(message) { + if let Some(bucket) = &mut self.buckets[key] { + return bucket.acquire(cost, now); + } + } + true + } +} + +/// Rate limit configuration for a single network message. +#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct SingleMessageConfig { + pub maximum_size: u32, + pub refill_rate: f32, + /// Optional initial size. Defaults to `maximum_size` if absent. + pub initial_size: Option, +} + +impl SingleMessageConfig { + pub fn new(maximum_size: u32, refill_rate: f32, initial_size: Option) -> Self { + Self { maximum_size, refill_rate, initial_size } + } +} + +/// Network messages rate limits configuration. +#[derive(Default, Clone)] +pub struct Config { + pub rate_limits: HashMap, +} + +/// Struct to manage user defined overrides for [Config]. The key difference with the base struct +/// is that in this values can be set to `None` to disable preset rate limits. +#[derive(serde::Serialize, serde::Deserialize, Default, Clone, Debug)] +pub struct OverrideConfig { + pub rate_limits: HashMap>, +} + +impl Config { + /// Validates this configuration object. + /// + /// # Errors + /// + /// If at least one error is present, returns the list of all configuration errors. + pub fn validate(&self) -> Result<(), Vec<(RateLimitedPeerMessageKey, TokenBucketError)>> { + let mut errors = Vec::new(); + for (key, message_config) in &self.rate_limits { + if let Err(err) = TokenBucket::validate_refill_rate(message_config.refill_rate) { + errors.push((*key, err)); + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } + + /// Returns a good preset of rate limit configuration valid for any type of node. + pub fn standard_preset() -> Self { + // TODO(trisfald): make preset + Self::default() + } + + /// Applies rate limits configuration overrides to `self`. In practice, merges the two configurations + /// giving preference to the values defined by the `overrides` parameter. + pub fn apply_overrides(&mut self, overrides: OverrideConfig) { + for (key, message_config) in overrides.rate_limits { + match message_config { + Some(value) => self.rate_limits.insert(key, value), + None => self.rate_limits.remove(&key), + }; + } + } +} + +/// This enum represents the variants of [PeerMessage] that can be rate limited. +/// It is meant to be used as an index for mapping peer messages to a value. +#[derive( + Clone, + Copy, + enum_map::Enum, + strum::Display, + Debug, + PartialEq, + Eq, + Hash, + serde::Serialize, + serde::Deserialize, +)] +#[allow(clippy::large_enum_variant)] +pub enum RateLimitedPeerMessageKey { + SyncRoutingTable, + DistanceVector, + RequestUpdateNonce, + SyncAccountsData, + PeersRequest, + PeersResponse, + BlockHeadersRequest, + BlockHeaders, + BlockRequest, + Block, + Transaction, + SyncSnapshotHosts, + StateRequestHeader, + StateRequestPart, + VersionedStateResponse, + BlockApproval, + ForwardTx, + TxStatusRequest, + TxStatusResponse, + StateResponse, + PartialEncodedChunkRequest, + PartialEncodedChunkResponse, + VersionedPartialEncodedChunk, + PartialEncodedChunkForward, + ChunkEndorsement, + ChunkStateWitnessAck, + PartialEncodedStateWitness, + PartialEncodedStateWitnessForward, +} + +/// Given a `PeerMessage` returns a tuple containing the `RateLimitedPeerMessageKey` +/// corresponding to the message's type and its the cost (in tokens) for rate limiting +/// purposes. +/// +/// Returns `Some` if the message has the potential to be rate limited (through the correct configuration). +/// Returns `None` if the message is not meant to be rate limited in any scenario. +fn get_key_and_token_cost(message: &PeerMessage) -> Option<(RateLimitedPeerMessageKey, u32)> { + use RateLimitedPeerMessageKey::*; + match message { + PeerMessage::SyncRoutingTable(_) => Some((SyncRoutingTable, 1)), + PeerMessage::DistanceVector(_) => Some((DistanceVector, 1)), + PeerMessage::RequestUpdateNonce(_) => Some((RequestUpdateNonce, 1)), + PeerMessage::SyncAccountsData(_) => Some((SyncAccountsData, 1)), + PeerMessage::PeersRequest(_) => Some((PeersRequest, 1)), + PeerMessage::PeersResponse(_) => Some((PeersResponse, 1)), + PeerMessage::BlockHeadersRequest(_) => Some((BlockHeadersRequest, 1)), + PeerMessage::BlockHeaders(_) => Some((BlockHeaders, 1)), + PeerMessage::BlockRequest(_) => Some((BlockRequest, 1)), + PeerMessage::Block(_) => Some((Block, 1)), + PeerMessage::Transaction(_) => Some((Transaction, 1)), + PeerMessage::Routed(msg) => match msg.body { + RoutedMessageBody::BlockApproval(_) => Some((BlockApproval, 1)), + RoutedMessageBody::ForwardTx(_) => Some((ForwardTx, 1)), + RoutedMessageBody::TxStatusRequest(_, _) => Some((TxStatusRequest, 1)), + RoutedMessageBody::TxStatusResponse(_) => Some((TxStatusResponse, 1)), + RoutedMessageBody::StateResponse(_) => Some((StateResponse, 1)), + RoutedMessageBody::PartialEncodedChunkRequest(_) => { + Some((PartialEncodedChunkRequest, 1)) + } + RoutedMessageBody::PartialEncodedChunkResponse(_) => { + Some((PartialEncodedChunkResponse, 1)) + } + RoutedMessageBody::VersionedPartialEncodedChunk(_) => { + Some((VersionedPartialEncodedChunk, 1)) + } + RoutedMessageBody::PartialEncodedChunkForward(_) => { + Some((PartialEncodedChunkForward, 1)) + } + RoutedMessageBody::ChunkEndorsement(_) => Some((ChunkEndorsement, 1)), + RoutedMessageBody::ChunkStateWitnessAck(_) => Some((ChunkStateWitnessAck, 1)), + RoutedMessageBody::PartialEncodedStateWitness(_) => { + Some((PartialEncodedStateWitness, 1)) + } + RoutedMessageBody::PartialEncodedStateWitnessForward(_) => { + Some((PartialEncodedStateWitnessForward, 1)) + } + RoutedMessageBody::Ping(_) + | RoutedMessageBody::Pong(_) + | RoutedMessageBody::_UnusedChunkStateWitness + | RoutedMessageBody::_UnusedVersionedStateResponse + | RoutedMessageBody::_UnusedPartialEncodedChunk + | RoutedMessageBody::_UnusedQueryRequest + | RoutedMessageBody::_UnusedQueryResponse + | RoutedMessageBody::_UnusedReceiptOutcomeRequest(_) + | RoutedMessageBody::_UnusedReceiptOutcomeResponse + | RoutedMessageBody::_UnusedStateRequestHeader + | RoutedMessageBody::_UnusedStateRequestPart => None, + }, + PeerMessage::SyncSnapshotHosts(_) => Some((SyncSnapshotHosts, 1)), + PeerMessage::StateRequestHeader(_, _) => Some((StateRequestHeader, 1)), + PeerMessage::StateRequestPart(_, _, _) => Some((StateRequestPart, 1)), + PeerMessage::VersionedStateResponse(_) => Some((VersionedStateResponse, 1)), + PeerMessage::Tier1Handshake(_) + | PeerMessage::Tier2Handshake(_) + | PeerMessage::HandshakeFailure(_, _) + | PeerMessage::LastEdge(_) + | PeerMessage::Disconnect(_) + | PeerMessage::Challenge(_) => None, + } +} + +#[cfg(test)] +mod tests { + use near_async::time::Duration; + use near_primitives::hash::CryptoHash; + + use crate::network_protocol::{Disconnect, PeerMessage}; + + use super::*; + + #[test] + fn is_allowed() { + let disconnect = + PeerMessage::Disconnect(Disconnect { remove_from_connection_store: false }); + let block_request = PeerMessage::BlockRequest(CryptoHash::default()); + let now = Instant::now(); + + // Test message that can't be rate limited. + { + let mut limits = RateLimits::default(); + assert!(limits.is_allowed(&disconnect, now)); + } + + // Test message that might be rate limited, but the system is not configured to do so. + { + let mut limits = RateLimits::default(); + assert!(limits.is_allowed(&block_request, now)); + } + + // Test rate limited message with enough tokens. + { + let mut limits = RateLimits::default(); + limits.buckets[RateLimitedPeerMessageKey::BlockRequest] = + Some(TokenBucket::new(1, 1, 0.0, now).unwrap()); + assert!(limits.is_allowed(&block_request, now)); + } + + // Test rate limited message without enough tokens. + { + let mut limits = RateLimits::default(); + limits.buckets[RateLimitedPeerMessageKey::BlockRequest] = + Some(TokenBucket::new(0, 1, 0.0, now).unwrap()); + assert!(!limits.is_allowed(&block_request, now)); + } + } + + #[test] + fn configuration() { + use RateLimitedPeerMessageKey::*; + let mut config = Config::default(); + + config.rate_limits.insert(Block, SingleMessageConfig::new(5, 1.0, Some(1))); + config.rate_limits.insert(BlockApproval, SingleMessageConfig::new(5, 1.0, None)); + config.rate_limits.insert(BlockHeaders, SingleMessageConfig::new(1, -4.0, None)); + + let now = Instant::now(); + let mut limits = RateLimits::from_config(&config, now); + + // Bucket should exist with capacity = 1. + assert!(!limits.buckets[Block].as_mut().unwrap().acquire(2, now)); + // Bucket should exist with capacity = 5. + assert!(limits.buckets[BlockApproval].as_mut().unwrap().acquire(2, now)); + // Bucket should not exist due to a config error. + assert!(limits.buckets[BlockHeaders].is_none()); + // Buckets are not instantiated for message types not present in the config. + assert!(limits.buckets[RequestUpdateNonce].is_none()); + } + + #[test] + fn configuration_errors() { + use RateLimitedPeerMessageKey::*; + let mut config = Config::default(); + assert!(config.validate().is_ok()); + + config.rate_limits.insert(Block, SingleMessageConfig::new(0, 1.0, None)); + assert!(config.validate().is_ok()); + + config.rate_limits.insert(BlockApproval, SingleMessageConfig::new(0, -1.0, None)); + assert_eq!( + config.validate(), + Err(vec![(BlockApproval, TokenBucketError::InvalidRefillRate(-1.0))]) + ); + + config.rate_limits.insert(BlockHeaders, SingleMessageConfig::new(0, -2.0, None)); + let result = config.validate(); + let error = result.expect_err("a configuration error is expected"); + assert!(error + .iter() + .find(|(key, err)| *key == BlockApproval + && *err == TokenBucketError::InvalidRefillRate(-1.0)) + .is_some()); + assert!(error + .iter() + .find(|(key, err)| *key == BlockHeaders + && *err == TokenBucketError::InvalidRefillRate(-2.0)) + .is_some()); + } + + #[test] + fn buckets_get_refreshed() { + use RateLimitedPeerMessageKey::*; + let mut config = Config::default(); + let now = Instant::now(); + + config.rate_limits.insert(Block, SingleMessageConfig::new(5, 1.0, Some(0))); + config.rate_limits.insert(BlockApproval, SingleMessageConfig::new(5, 1.0, Some(0))); + + let mut limits = RateLimits::from_config(&config, now); + + assert!(!limits.buckets[Block].as_mut().unwrap().acquire(1, now)); + assert!(!limits.buckets[BlockApproval].as_mut().unwrap().acquire(1, now)); + + let now = now + Duration::seconds(1); + + assert!(limits.buckets[Block].as_mut().unwrap().acquire(1, now)); + assert!(limits.buckets[BlockApproval].as_mut().unwrap().acquire(1, now)); + } + + #[test] + fn apply_overrides() { + use RateLimitedPeerMessageKey::*; + + // Create a config with three entries. + let mut config = Config::default(); + config.rate_limits.insert(Block, SingleMessageConfig::new(1, 1.0, None)); + config.rate_limits.insert(BlockApproval, SingleMessageConfig::new(2, 1.0, None)); + config.rate_limits.insert(BlockHeaders, SingleMessageConfig::new(3, 1.0, None)); + + // Override the config with the following patch: + // - one entry is modified + // - one entry is untouched + // - one entry is removed + // - one entry is added + let mut overrides = OverrideConfig::default(); + overrides.rate_limits.insert(Block, Some(SingleMessageConfig::new(4, 1.0, None))); + overrides.rate_limits.insert(BlockHeaders, None); + overrides + .rate_limits + .insert(StateRequestHeader, Some(SingleMessageConfig::new(5, 1.0, None))); + + config.apply_overrides(overrides); + assert_eq!(config.rate_limits.len(), 3); + assert_eq!(config.rate_limits.get(&Block), Some(&SingleMessageConfig::new(4, 1.0, None))); + assert_eq!(config.rate_limits.get(&BlockHeaders), None); + assert_eq!( + config.rate_limits.get(&StateRequestHeader), + Some(&SingleMessageConfig::new(5, 1.0, None)) + ); + } + + #[test] + fn override_config_deserialization() { + use RateLimitedPeerMessageKey::*; + + // Check object with no entries. + let json = serde_json::json!({"rate_limits": {}}); + let config: OverrideConfig = + serde_json::from_value(json).expect("deserializing OverrideConfig should work"); + assert_eq!(config.rate_limits.len(), 0); + + // Check object with a single entry. + let json = serde_json::json!({"rate_limits": { + "Block": { + "maximum_size": 1, + "refill_rate": 1.0, + "initial_size": 1, + } + }}); + let config: OverrideConfig = + serde_json::from_value(json).expect("deserializing OverrideConfig should work"); + assert_eq!(config.rate_limits.len(), 1); + assert!(config.rate_limits.contains_key(&Block)); + + // Check object with multiple entries. + let json = serde_json::json!({"rate_limits": { + "Block": { + "maximum_size": 1, + "refill_rate": 1.0, + "initial_size": 1, + }, + "BlockApproval": { + "maximum_size": 2, + "refill_rate": 1.0, + } + }}); + let config: OverrideConfig = + serde_json::from_value(json).expect("deserializing OverrideConfig should work"); + assert_eq!(config.rate_limits.len(), 2); + assert!(config.rate_limits.contains_key(&Block)); + assert!(config.rate_limits.contains_key(&BlockApproval)); + + // Check object with errors. + let json = serde_json::json!({"rate_limits": { + "Block": { + "foo": 1, + } + }}); + assert!(serde_json::from_value::(json).is_err()); + } +} diff --git a/chain/network/src/rate_limits/mod.rs b/chain/network/src/rate_limits/mod.rs new file mode 100644 index 00000000000..e623412c3b2 --- /dev/null +++ b/chain/network/src/rate_limits/mod.rs @@ -0,0 +1,2 @@ +pub mod messages_limits; +pub mod token_bucket; diff --git a/chain/network/src/rate_limits/token_bucket.rs b/chain/network/src/rate_limits/token_bucket.rs new file mode 100644 index 00000000000..5b5cd8c8eb7 --- /dev/null +++ b/chain/network/src/rate_limits/token_bucket.rs @@ -0,0 +1,288 @@ +//! Implementation of the token bucket algorithm, used to put limits on +//! bandwidth and burstiness of network traffic. +//! +//! The algorithm depicts an imaginary bucket into which tokens are added +//! at regular intervals of time. The bucket has a well defined maximum size +//! and overflowing tokens are simply discarded. +//! Network traffic (packets, messages, etc) 'consume' a given amount of tokens +//! in order to be allowed to pass. +//! If there aren't enough tokens in the bucket, the traffic might be stopped +//! or delayed. However, this module responsibility stops at telling +//! whether or not the incoming messages are allowed. + +use near_async::time::Instant; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum TokenBucketError { + #[error("invalid value for refill rate ({0})")] + InvalidRefillRate(f32), +} + +/// Into how many parts a token can be divided. +const TOKEN_PARTS_NUMBER: u64 = 1 << 31; + +/// Struct to hold the state for the token bucket algorithm. +/// +/// The precision guarantee is, at least, such that a bucket having `refill_rate` = 0.001s +/// update at regular intervals every 10ms will successfully generate a token after 1000±1s. +pub struct TokenBucket { + /// Maximum number of tokens the bucket can hold. + maximum_size: u32, + /// Tokens in the bucket. They are stored as `tokens * TOKEN_PARTS_NUMBER`. + /// In this way we can refill the bucket at shorter intervals. + size: u64, + /// Refill rate in token per second. + refill_rate: f32, + /// Last time the bucket was refreshed. + last_refill: Instant, +} + +impl TokenBucket { + /// Creates a new token bucket. + /// + /// # Arguments + /// + /// * `initial_size` - Initial amount of tokens in the bucket + /// * `maximum_size` - Maximum amount of tokens the bucket can hold + /// * `refill_rate` - Bucket refill rate in token per second + /// * `start_time` - Point in time used as a start to calculate the bucket refill. + /// + /// # Errors + /// + /// Returns an error if any of the arguments has an invalid value. + pub fn new( + initial_size: u32, + maximum_size: u32, + refill_rate: f32, + start_time: Instant, + ) -> Result { + let size = to_tokens_with_parts(maximum_size.min(initial_size)); + TokenBucket::validate_refill_rate(refill_rate)?; + Ok(Self { maximum_size, size, refill_rate, last_refill: start_time }) + } + + /// Makes an attempt to acquire `token` tokens. + /// + /// This method takes a parameter called `now` which should be equivalent to the current time. + /// The latter is used to refill the bucket before subtracting tokens. + /// + /// If the tokens are available they are subtracted from the current `size` and + /// the method returns `true`. Otherwise, `size` is not changed and the method + /// returns `false`. + pub fn acquire(&mut self, tokens: u32, now: Instant) -> bool { + self.refill(now); + let tokens = to_tokens_with_parts(tokens); + if self.size >= tokens { + self.size -= tokens; + true + } else { + false + } + } + + /// Refills the bucket with the right number of tokens according to + /// the `refill_rate` and the new current time `now`. + /// + /// For example: if `refill_rate` == 1 and `now - last_refill` == 1s then exactly 1 token + /// will be added. + fn refill(&mut self, now: Instant) { + // Sanity check: now should be bigger than the last refill time. + if now <= self.last_refill { + return; + } + // Compute how many tokens should be added to the current size. + let duration = now - self.last_refill; + let tokens_to_add = duration.as_secs_f64() * self.refill_rate as f64; + let tokens_to_add = (tokens_to_add * TOKEN_PARTS_NUMBER as f64) as u64; + // Update `last_refill` and `size` only if there's a change. This is done to prevent + // losing token parts to clamping if the duration is too small. + if tokens_to_add > 0 { + self.size = self + .size + .saturating_add(tokens_to_add) + .min(to_tokens_with_parts(self.maximum_size)); + self.last_refill = now; + } + } + + /// Returns an error if the value provided is not in the correct range for + /// `refill_rate`. + pub(crate) fn validate_refill_rate(refill_rate: f32) -> Result<(), TokenBucketError> { + if refill_rate < 0.0 { + return Err(TokenBucketError::InvalidRefillRate(refill_rate)); + } + if !refill_rate.is_normal() && refill_rate != 0.0 { + return Err(TokenBucketError::InvalidRefillRate(refill_rate)); + } + Ok(()) + } +} + +/// Transforms a value of `tokens` without a fractional part into a representation +/// having a fractional part. +fn to_tokens_with_parts(tokens: u32) -> u64 { + // Safe (check the test `token_fractional_representation_cant_overflow`). + tokens as u64 * TOKEN_PARTS_NUMBER +} + +#[cfg(test)] +mod tests { + use super::*; + use near_async::time::{Duration, Instant}; + + #[test] + fn token_fractional_representation_cant_overflow() { + assert!(TOKEN_PARTS_NUMBER.saturating_mul(u32::MAX as u64) < u64::MAX); + } + + #[test] + fn initial_more_than_max() { + let bucket = + TokenBucket::new(5, 2, 1.0, Instant::now()).expect("bucket should be well formed"); + assert_eq!(bucket.size, to_tokens_with_parts(2)); + assert_eq!(bucket.maximum_size, 2); + } + + #[test] + fn invalid_refill_rate() { + assert!(TokenBucket::new(2, 2, f32::NAN, Instant::now()).is_err()); + assert!(TokenBucket::new(2, 2, f32::INFINITY, Instant::now()).is_err()); + assert!(TokenBucket::new(2, 2, f32::NEG_INFINITY, Instant::now()).is_err()); + assert!(TokenBucket::new(2, 2, -1.0, Instant::now()).is_err()); + } + + #[test] + fn valid_refill_rate() { + assert!(TokenBucket::new(2, 2, 0.0, Instant::now()).is_ok()); + assert!(TokenBucket::new(2, 2, 0.3, Instant::now()).is_ok()); + } + + #[test] + fn acquire() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(5, 10, 1.0, now).expect("bucket should be well formed"); + + assert!(bucket.acquire(0, now)); + assert_eq!(bucket.size, to_tokens_with_parts(5)); + + assert!(bucket.acquire(1, now)); + assert_eq!(bucket.size, to_tokens_with_parts(4)); + + assert!(!bucket.acquire(10, now)); + assert_eq!(bucket.size, to_tokens_with_parts(4)); + + assert!(bucket.acquire(4, now)); + assert_eq!(bucket.size, to_tokens_with_parts(0)); + + assert!(!bucket.acquire(1, now)); + assert_eq!(bucket.size, to_tokens_with_parts(0)); + } + + #[test] + fn max_is_zero() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(0, 0, 0.0, now).expect("bucket should be well formed"); + assert!(bucket.acquire(0, now)); + assert!(!bucket.acquire(1, now)); + } + + #[test] + fn buckets_get_refilled() { + let now = Instant::now(); + let mut bucket = + TokenBucket::new(0, 1000, 10.0, now).expect("bucket should be well formed"); + assert!(!bucket.acquire(1, now)); + assert!(bucket.acquire(1, now + Duration::milliseconds(500))); + } + + #[test] + fn zero_refill_rate() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(10, 10, 0.0, now).expect("bucket should be well formed"); + assert!(bucket.acquire(10, now)); + assert!(!bucket.acquire(1, now)); + assert!(!bucket.acquire(1, now + Duration::seconds(100))); + } + + #[test] + fn refill_no_time_elapsed() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(10, 10, 1.0, now).expect("bucket should be well formed"); + let size = bucket.size; + bucket.refill(now); + assert_eq!(bucket.size, size); + } + + #[test] + fn check_non_monotonic_clocks_safety() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(10, 10, 1.0, now).expect("bucket should be well formed"); + let size = bucket.size; + bucket.refill(now - Duration::seconds(100)); + assert_eq!(bucket.size, size); + } + + #[test] + fn refill_partial_token() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(0, 5, 0.4, now).expect("bucket should be well formed"); + assert!(!bucket.acquire(1, now)); + assert!(!bucket.acquire(1, now + Duration::seconds(1))); + assert!(!bucket.acquire(1, now + Duration::seconds(2))); + assert!(bucket.acquire(1, now + Duration::seconds(3))); + } + + #[test] + fn refill_overflow_bucket_max_size() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(2, 5, 1.0, now).expect("bucket should be well formed"); + + bucket.refill(now + Duration::seconds(2)); + assert_eq!(bucket.size, to_tokens_with_parts(4)); + + bucket.refill(now + Duration::seconds(4)); + assert_eq!(bucket.size, to_tokens_with_parts(5)); + + assert!(bucket.acquire(5, now + Duration::seconds(4))); + assert_eq!(bucket.size, to_tokens_with_parts(0)); + + assert!(bucket.acquire(5, now + Duration::seconds(10))); + assert_eq!(bucket.size, to_tokens_with_parts(0)); + } + + #[test] + fn check_with_numeric_limits() { + let now = Instant::now(); + let mut bucket = TokenBucket::new(u32::MAX, u32::MAX, 1_000_000.0, now) + .expect("bucket should be well formed"); + + assert!(bucket.acquire(u32::MAX, now)); + assert!(!bucket.acquire(1, now)); + + let now = now + Duration::days(100); + assert!(bucket.acquire(u32::MAX, now)); + assert!(!bucket.acquire(1, now)); + } + + #[test] + /// Validate if `TokenBucket` meets the requirement of being able to refresh tokens successfully + /// when both the refill rate and the elapsed time are very low. + fn validate_guaranteed_resolution() { + let mut now = Instant::now(); + let mut bucket = TokenBucket::new(0, 10, 0.001, now).expect("bucket should be well formed"); + // Up to 999s: no new token added. + for _ in 0..99_900 { + now += Duration::milliseconds(10); + assert!(!bucket.acquire(1, now)); + } + // From 999s to 1001s: the new token should get added. + let mut tokens_added = 0; + for _ in 99_900..100_100 { + now += Duration::milliseconds(10); + if bucket.acquire(1, now) { + tokens_added += 1; + } + } + assert_eq!(tokens_added, 1); + } +} diff --git a/chain/network/src/raw/connection.rs b/chain/network/src/raw/connection.rs index 36047a35890..5b27412b68e 100644 --- a/chain/network/src/raw/connection.rs +++ b/chain/network/src/raw/connection.rs @@ -1,3 +1,4 @@ +use crate::config::SocketOptions; use crate::network_protocol::{ Encoding, Handshake, HandshakeFailureReason, PartialEdgeInfo, PeerChainInfoV2, PeerIdOrHash, PeerMessage, Ping, Pong, RawRoutedMessage, RoutedMessageBody, RoutingTableUpdate, @@ -19,6 +20,7 @@ use near_primitives::version::{ProtocolVersion, PROTOCOL_VERSION}; use std::fmt; use std::io; use std::net::SocketAddr; +use std::num::NonZeroUsize; use time::ext::InstantExt as _; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -237,9 +239,13 @@ impl Connection { let my_peer_id = PeerId::new(secret_key.public_key()); let start = Instant::now(); - let stream = tcp::Stream::connect(&PeerInfo::new(peer_id.clone(), addr), tcp::Tier::T2) - .await - .map_err(ConnectError::TcpConnect)?; + let stream = tcp::Stream::connect( + &PeerInfo::new(peer_id.clone(), addr), + tcp::Tier::T2, + &SocketOptions::default(), + ) + .await + .map_err(ConnectError::TcpConnect)?; tracing::info!( target: "network", %peer_id, ?addr, latency=?start.elapsed(), "Connection established", @@ -249,7 +255,7 @@ impl Connection { peer_id, secret_key, my_peer_id, - route_cache: lru::LruCache::new(1_000_000), + route_cache: lru::LruCache::new(NonZeroUsize::new(1_000_000).unwrap()), borsh_message_expected: false, }; peer.do_handshake( @@ -318,7 +324,7 @@ impl Connection { my_peer_id, stream, peer_id, - route_cache: lru::LruCache::new(1_000_000), + route_cache: lru::LruCache::new(NonZeroUsize::new(1_000_000).unwrap()), borsh_message_expected, }) } diff --git a/chain/network/src/routing/routing_table_view/mod.rs b/chain/network/src/routing/routing_table_view/mod.rs index b7c84f24e83..7dc40e17635 100644 --- a/chain/network/src/routing/routing_table_view/mod.rs +++ b/chain/network/src/routing/routing_table_view/mod.rs @@ -2,6 +2,7 @@ use crate::routing; use lru::LruCache; use near_primitives::network::PeerId; use parking_lot::Mutex; +use std::num::NonZeroUsize; use std::sync::Arc; #[cfg(test)] @@ -64,7 +65,7 @@ impl RoutingTableView { next_hops: Default::default(), distance: Default::default(), find_route_calls: 0, - last_routed: LruCache::new(LAST_ROUTED_CACHE_SIZE), + last_routed: LruCache::new(NonZeroUsize::new(LAST_ROUTED_CACHE_SIZE).unwrap()), })) } diff --git a/chain/network/src/snapshot_hosts/mod.rs b/chain/network/src/snapshot_hosts/mod.rs index fd9f33d1b4e..59362401090 100644 --- a/chain/network/src/snapshot_hosts/mod.rs +++ b/chain/network/src/snapshot_hosts/mod.rs @@ -16,6 +16,7 @@ use parking_lot::Mutex; use rayon::iter::ParallelBridge; use sha2::{Digest, Sha256}; use std::collections::{BinaryHeap, HashMap, HashSet}; +use std::num::NonZeroUsize; use std::sync::Arc; #[cfg(test)] @@ -253,7 +254,8 @@ pub(crate) struct SnapshotHostsCache(Mutex); impl SnapshotHostsCache { pub fn new(config: Config) -> Self { debug_assert!(config.part_selection_cache_batch_size > 0); - let hosts = LruCache::new(config.snapshot_hosts_cache_size as usize); + let hosts = + LruCache::new(NonZeroUsize::new(config.snapshot_hosts_cache_size as usize).unwrap()); let state_part_selectors = HashMap::new(); Self(Mutex::new(Inner { hosts, diff --git a/chain/network/src/stats/metrics.rs b/chain/network/src/stats/metrics.rs index 10f9066b2c0..e5735820546 100644 --- a/chain/network/src/stats/metrics.rs +++ b/chain/network/src/stats/metrics.rs @@ -192,6 +192,14 @@ pub(crate) static PEER_MESSAGE_SENT_BY_TYPE_TOTAL: Lazy = Lazy::n ) .unwrap() }); +pub(crate) static PEER_MESSAGE_RATE_LIMITED_BY_TYPE_TOTAL: Lazy = Lazy::new(|| { + try_create_int_counter_vec( + "near_peer_message_rate_limited_by_type_total", + "Number of messages dropped because rate limited by message types", + &["type"], + ) + .unwrap() +}); pub(crate) static SYNC_ACCOUNTS_DATA: Lazy = Lazy::new(|| { try_create_int_counter_vec( "near_sync_accounts_data", diff --git a/chain/network/src/store/mod.rs b/chain/network/src/store/mod.rs index 9bf69187e00..7582d5fd7b2 100644 --- a/chain/network/src/store/mod.rs +++ b/chain/network/src/store/mod.rs @@ -10,7 +10,7 @@ mod schema; /// Opaque error type representing storage errors. /// -/// Invariant: any store error is a critical operational operational error +/// Invariant: any store error is a critical operational error /// which signals about data corruption. It wouldn't be wrong to replace all places /// where the error originates with outright panics. /// /// If you have an error condition which needs to be handled somehow, it should be diff --git a/chain/network/src/stun/mod.rs b/chain/network/src/stun/mod.rs index f1380f83dfa..9980d1f56cf 100644 --- a/chain/network/src/stun/mod.rs +++ b/chain/network/src/stun/mod.rs @@ -1,4 +1,5 @@ use near_async::time; +use std::net::SocketAddr; use std::sync::Arc; use stun::message::Getter as _; @@ -9,11 +10,21 @@ mod tests; pub(crate) mod testonly; /// Address of the format ":" of STUN servers. -// TODO(gprusak): turn into a proper struct implementing Display and FromStr. pub type ServerAddr = String; pub(crate) type Error = stun::Error; +/// Convert from ServerAddr to SocketAddr via DNS resolution. +/// Looks for IPv4 or IPv6 according to `want_ipv4`. +pub(crate) async fn lookup_host(addr: &ServerAddr, want_ipv4: bool) -> Option { + for socket_addr in tokio::net::lookup_host(addr).await.ok()? { + if want_ipv4 == socket_addr.is_ipv4() { + return Some(socket_addr); + } + } + None +} + const QUERY_TIMEOUT: time::Duration = time::Duration::seconds(5); /// Sends a STUN BINDING request to `addr`. @@ -21,7 +32,7 @@ const QUERY_TIMEOUT: time::Duration = time::Duration::seconds(5); /// It should be used to determine the public IP of this machine. pub(crate) async fn query( clock: &time::Clock, - addr: &ServerAddr, + addr: &SocketAddr, ) -> Result { let socket = tokio::net::UdpSocket::bind("[::]:0").await?; socket.connect(addr).await?; diff --git a/chain/network/src/stun/testonly.rs b/chain/network/src/stun/testonly.rs index aaebe43e3a9..63e920d87cc 100644 --- a/chain/network/src/stun/testonly.rs +++ b/chain/network/src/stun/testonly.rs @@ -50,8 +50,8 @@ impl Server { } } - pub fn addr(&self) -> super::ServerAddr { - self.addr.to_string() + pub fn addr(&self) -> super::SocketAddr { + self.addr } /// Closes the STUN server. close() is async so it cannot be implemented as Drop. diff --git a/chain/network/src/stun/tests.rs b/chain/network/src/stun/tests.rs index da1afd7d206..68ddc88227f 100644 --- a/chain/network/src/stun/tests.rs +++ b/chain/network/src/stun/tests.rs @@ -1,3 +1,4 @@ +use crate::config_json::default_trusted_stun_servers; use crate::stun; use near_async::time; use near_o11y::testonly::init_test_logger; @@ -11,3 +12,25 @@ async fn test_query() { assert_eq!(std::net::Ipv6Addr::LOCALHOST, ip); server.close().await; } + +#[tokio::test] +async fn test_lookup_host() { + init_test_logger(); + + for addr in default_trusted_stun_servers() { + tracing::debug!("Querying STUN server at {}", addr); + + // Allow lookup to return nothing; the server may be unreachable. + // What we want to check here is that if an address is returned, + // it has the expected type (IPv4 vs IPv6). + if let Some(ipv4) = stun::lookup_host(&addr, true).await { + assert!(ipv4.is_ipv4()); + tracing::debug!("My IPv4 addr is {}", ipv4); + } + + if let Some(ipv6) = stun::lookup_host(&addr, false).await { + assert!(ipv6.is_ipv6()); + tracing::debug!("My IPv6 addr is {}", ipv6); + } + } +} diff --git a/chain/network/src/tcp.rs b/chain/network/src/tcp.rs index 94adfc67c56..27fcbf0ceee 100644 --- a/chain/network/src/tcp.rs +++ b/chain/network/src/tcp.rs @@ -1,3 +1,4 @@ +use crate::config::SocketOptions; use crate::network_protocol::PeerInfo; use anyhow::{anyhow, Context as _}; use near_primitives::network::PeerId; @@ -83,10 +84,36 @@ impl Stream { Ok(Self { peer_addr: stream.peer_addr()?, local_addr: stream.local_addr()?, stream, type_ }) } - pub async fn connect(peer_info: &PeerInfo, tier: Tier) -> anyhow::Result { + pub async fn connect( + peer_info: &PeerInfo, + tier: Tier, + socket_options: &SocketOptions, + ) -> anyhow::Result { let addr = peer_info .addr .ok_or_else(|| anyhow!("Trying to connect to peer with no public address"))?; + + let socket = match addr { + std::net::SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?, + std::net::SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?, + }; + + // Avoid setting the buffer sizes for T1 connections, which are numerous and lightweight. + match tier { + Tier::T2 => { + if let Some(so_rcvbuf) = socket_options.recv_buffer_size { + socket.set_recv_buffer_size(so_rcvbuf)?; + tracing::debug!(target: "network", "SO_RCVBUF wanted {} got {:?}", so_rcvbuf, socket.recv_buffer_size()); + } + + if let Some(so_sndbuf) = socket_options.send_buffer_size { + socket.set_send_buffer_size(so_sndbuf)?; + tracing::debug!(target: "network", "SO_SNDBUF wanted {} got {:?}", so_sndbuf, socket.send_buffer_size()); + } + } + _ => {} + }; + // The `connect` may take several minutes. This happens when the // `SYN` packet for establishing a TCP connection gets silently // dropped, in which case the default TCP timeout is applied. That's @@ -95,12 +122,9 @@ impl Stream { // Why exactly a second? It was hard-coded in a library we used // before, so we keep it to preserve behavior. Removing the timeout // completely was observed to break stuff for real on the testnet. - let stream = tokio::time::timeout( - std::time::Duration::from_secs(1), - tokio::net::TcpStream::connect(addr), - ) - .await? - .context("TcpStream::connect()")?; + let stream = tokio::time::timeout(std::time::Duration::from_secs(1), socket.connect(addr)) + .await? + .context("TcpStream::connect()")?; Ok(Stream::new(stream, StreamType::Outbound { peer_id: peer_info.id.clone(), tier })?) } @@ -110,9 +134,10 @@ impl Stream { pub async fn loopback(peer_id: PeerId, tier: Tier) -> (Stream, Stream) { let listener_addr = ListenerAddr::reserve_for_test(); let peer_info = PeerInfo { id: peer_id, addr: Some(*listener_addr), account_id: None }; + let socket_options = SocketOptions::default(); let mut listener = listener_addr.listener().unwrap(); let (outbound, inbound) = - tokio::join!(Stream::connect(&peer_info, tier), listener.accept()); + tokio::join!(Stream::connect(&peer_info, tier, &socket_options), listener.accept()); (outbound.unwrap(), inbound.unwrap()) } @@ -209,6 +234,10 @@ impl ListenerAddr { socket.bind(self.0)?; Ok(Listener(socket.listen(LISTENER_BACKLOG)?)) } + + pub(crate) fn is_ipv4(&self) -> bool { + self.0.is_ipv4() + } } pub(crate) struct Listener(tokio::net::TcpListener); diff --git a/chain/network/src/test_loop.rs b/chain/network/src/test_loop.rs index f35cccfd612..c67623a6516 100644 --- a/chain/network/src/test_loop.rs +++ b/chain/network/src/test_loop.rs @@ -1,21 +1,308 @@ +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use near_async::messaging::{Actor, AsyncSender, CanSend, Handler, SendAsync, Sender}; +use near_async::time::Clock; +use near_async::{MultiSend, MultiSenderFrom}; +use near_primitives::hash::CryptoHash; +use near_primitives::network::PeerId; use near_primitives::types::AccountId; +use once_cell::sync::Lazy; + +use crate::client::{ + BlockApproval, BlockResponse, ChunkEndorsementMessage, ProcessTxRequest, ProcessTxResponse, +}; +use crate::shards_manager::ShardsManagerRequestFromNetwork; +use crate::state_witness::{ + ChunkStateWitnessAckMessage, PartialEncodedStateWitnessForwardMessage, + PartialEncodedStateWitnessMessage, PartialWitnessSenderForNetwork, +}; +use crate::types::{ + NetworkRequests, NetworkResponses, PeerManagerMessageRequest, PeerManagerMessageResponse, + SetChainInfo, +}; + +/// Subset of ClientSenderForNetwork required for the TestLoop network. +/// We skip over the message handlers from view client. +#[derive(Clone, MultiSend, MultiSenderFrom)] +pub struct ClientSenderForTestLoopNetwork { + pub block: AsyncSender, + pub block_approval: AsyncSender, + pub transaction: AsyncSender, + pub chunk_endorsement: AsyncSender, +} + +type NetworkRequestHandler = Arc Option>; + +/// A custom actor for the TestLoop framework that can be used to send network messages across clients +/// in a multi-node test. +/// +/// This actor has a set of handlers to handle PeerManagerMessageRequest messages. We have a set of +/// default handlers that handle messages sent to client, partial_witness actor, and shards_manager. +/// It is possible to override these handlers by registering a new handler using the +/// `register_override_handler()` method. +/// +/// The signature of the handler is `dyn Fn(NetworkRequests) -> Option`. +/// If the handler returns None, it means that the message was handled and no further processing is +/// required. If the handler returns Some(request), it means that the message was not handled and +/// the request should be passed to the next handler in the chain. +/// +/// It's possible for a handler to modify the data in request and return it. This can be useful for +/// simulating things like malicious actors where we can modify the data in the request. +/// +/// In case no handler is able to handle the request, the actor will panic. +/// +/// NOTE: To make the override functionality work with the default handlers, the handlers are tried in +/// reverse order. +/// +/// Examples of custom handlers +/// - Override handler to skip sending messages to or from a specific client. +/// - Override handler to simulate more network delays. +/// - Override handler to modify data and simulate malicious behavior. +#[derive(Default)] +pub struct TestLoopPeerManagerActor { + handlers: Vec, +} + +impl Actor for TestLoopPeerManagerActor {} + +impl TestLoopPeerManagerActor { + /// Create a new TestLoopPeerManagerActor with default handlers for client, partial_witness, and shards_manager. + /// Note that we should be able to access the senders for these actors from the data type. + pub fn new<'a, T>(clock: Clock, account_id: &AccountId, datas: &'a Vec) -> Self + where + AccountId: From<&'a T>, + ClientSenderForTestLoopNetwork: From<&'a T>, + PartialWitnessSenderForNetwork: From<&'a T>, + Sender: From<&'a T>, + { + let handlers = vec![ + network_message_to_client_handler(&account_id, make_sender_map(datas)), + network_message_to_partial_witness_handler(&account_id, make_sender_map(datas)), + network_message_to_shards_manager_handler(clock, &account_id, make_sender_map(datas)), + network_message_to_state_snapshot_handler(), + ]; + Self { handlers } + } + + /// Register a new handler to override the default handlers. + pub fn register_override_handler(&mut self, handler: NetworkRequestHandler) { + // We add the handler to the end of the list and while processing the request, we iterate + // over the handlers in reverse order. + self.handlers.push(handler); + } +} + +// Helper function to create a map of senders from a list of data. +// Converts Vec to HashMap +fn make_sender_map<'a, T, U>(datas: &'a Vec) -> HashMap +where + AccountId: From<&'a T>, + U: From<&'a T>, +{ + let mut senders = HashMap::new(); + for data in datas.iter() { + senders.insert(data.into(), data.into()); + } + senders +} -/// A multi-instance test using the TestLoop framework can support routing -/// lookup for network messages, as long as the Data type contains AccountId. -/// This trait is just a helper for looking up the index. -pub trait SupportsRoutingLookup { - fn index_for_account(&self, account: &AccountId) -> usize; - fn num_accounts(&self) -> usize; +impl Handler for TestLoopPeerManagerActor { + fn handle(&mut self, _msg: SetChainInfo) {} } -impl> SupportsRoutingLookup for Vec { - fn index_for_account(&self, account: &AccountId) -> usize { - self.iter() - .position(|data| data.as_ref() == account) - .unwrap_or_else(|| panic!("Account not found: {}", account)) +impl Handler for TestLoopPeerManagerActor { + fn handle(&mut self, msg: PeerManagerMessageRequest) -> PeerManagerMessageResponse { + let PeerManagerMessageRequest::NetworkRequests(request) = msg else { + panic!("Unexpected message: {:?}", msg); + }; + + // Iterate over the handlers in reverse order to allow for overriding the default handlers. + let mut request = Some(request); + for handler in self.handlers.iter().rev() { + if let Some(new_request) = handler(request.take().unwrap()) { + request = Some(new_request); + } else { + // Some handler was successfully able to handle the request. + return PeerManagerMessageResponse::NetworkResponses(NetworkResponses::NoResponse); + } + } + // If no handler was able to handle the request, panic. + panic!("Unhandled request: {:?}", request); + } +} + +fn network_message_to_client_handler( + my_account_id: &AccountId, + client_senders: HashMap, +) -> NetworkRequestHandler { + let my_account_id = my_account_id.clone(); + Arc::new(move |request| match request { + NetworkRequests::Block { block } => { + for (account_id, sender) in client_senders.iter() { + if account_id != &my_account_id { + let future = sender.send_async(BlockResponse { + block: block.clone(), + peer_id: PeerId::random(), + was_requested: false, + }); + drop(future); + } + } + None + } + NetworkRequests::Approval { approval_message } => { + assert_ne!( + approval_message.target, my_account_id, + "Sending message to self not supported." + ); + let sender = client_senders.get(&approval_message.target).unwrap(); + let future = + sender.send_async(BlockApproval(approval_message.approval, PeerId::random())); + drop(future); + None + } + NetworkRequests::ForwardTx(account, transaction) => { + assert_ne!(account, my_account_id, "Sending message to self not supported."); + let sender = client_senders.get(&account).unwrap(); + let future = sender.send_async(ProcessTxRequest { + transaction, + is_forwarded: true, + check_only: false, + }); + drop(future); + None + } + NetworkRequests::ChunkEndorsement(target, endorsement) => { + assert_ne!(target, my_account_id, "Sending message to self not supported."); + let sender = client_senders.get(&target).unwrap(); + let future = sender.send_async(ChunkEndorsementMessage(endorsement)); + drop(future); + None + } + _ => Some(request), + }) +} + +fn network_message_to_partial_witness_handler( + my_account_id: &AccountId, + partial_witness_senders: HashMap, +) -> NetworkRequestHandler { + let my_account_id = my_account_id.clone(); + Arc::new(move |request| match request { + NetworkRequests::ChunkStateWitnessAck(target, witness_ack) => { + assert_ne!(target, my_account_id, "Sending message to self not supported."); + let sender = partial_witness_senders.get(&target).unwrap(); + sender.send(ChunkStateWitnessAckMessage(witness_ack)); + None + } + + NetworkRequests::PartialEncodedStateWitness(validator_witness_tuple) => { + for (target, partial_witness) in validator_witness_tuple.into_iter() { + assert_ne!(target, my_account_id, "Sending message to self not supported."); + let sender = partial_witness_senders.get(&target).unwrap(); + sender.send(PartialEncodedStateWitnessMessage(partial_witness)); + } + None + } + NetworkRequests::PartialEncodedStateWitnessForward(chunk_validators, partial_witness) => { + for target in chunk_validators { + assert_ne!(target, my_account_id, "Sending message to self not supported."); + let sender = partial_witness_senders.get(&target).unwrap(); + sender.send(PartialEncodedStateWitnessForwardMessage(partial_witness.clone())); + } + None + } + _ => Some(request), + }) +} + +fn network_message_to_state_snapshot_handler() -> NetworkRequestHandler { + Arc::new(move |request| match request { + NetworkRequests::SnapshotHostInfo { .. } => None, + _ => Some(request), + }) +} + +/// While sending the PartialEncodedChunkRequest, we need to know the destination account id. +/// In the PartialEncodedChunkRequest, We specify the `route_back` as a unique identifier that is +/// used by the network layer to figure out who to send the response back to. +/// +/// In network_message_to_shards_manager_handler fn, we use the static initialization for +/// ROUTE_LOOKUP. This is fine to use in the test framework as each generate route is unique and +/// independent of other routes. +#[derive(Default, Clone)] +struct PartialEncodedChunkRequestRouteLookup(Arc>>); + +impl PartialEncodedChunkRequestRouteLookup { + fn new() -> Self { + Self(Arc::new(Mutex::new(HashMap::new()))) } - fn num_accounts(&self) -> usize { - self.len() + // Generating route_id is under a lock and we use the size of hashmap to generate the route_id + // The size of hashmap is strictly increasing which ensures us a unique route_id across multiple runs. + fn add_route(&self, from_account_id: &AccountId) -> CryptoHash { + let mut guard = self.0.lock().unwrap(); + let route_id = CryptoHash::hash_borsh(guard.len()); + guard.insert(route_id, from_account_id.clone()); + route_id } + + fn get_destination(&self, route_id: CryptoHash) -> AccountId { + let guard = self.0.lock().unwrap(); + guard.get(&route_id).unwrap().clone() + } +} + +fn network_message_to_shards_manager_handler( + clock: Clock, + my_account_id: &AccountId, + shards_manager_senders: HashMap>, +) -> Arc Option> { + // Static initialization for ROUTE_LOOKUP. This is fine across tests as we generate a unique route_id + // for each message under a lock. + static ROUTE_LOOKUP: Lazy = + Lazy::new(PartialEncodedChunkRequestRouteLookup::new); + let my_account_id = my_account_id.clone(); + Arc::new(move |request| match request { + NetworkRequests::PartialEncodedChunkRequest { target, request, .. } => { + // Save route information in ROUTE_LOOKUP + let route_back = ROUTE_LOOKUP.add_route(&my_account_id); + let target = target.account_id.unwrap(); + assert!(target != my_account_id, "Sending message to self not supported."); + let sender = shards_manager_senders.get(&target).unwrap(); + sender.send(ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunkRequest { + partial_encoded_chunk_request: request, + route_back, + }); + None + } + NetworkRequests::PartialEncodedChunkResponse { route_back, response } => { + // Use route_back information to send the response back to the correct client. + let target = ROUTE_LOOKUP.get_destination(route_back); + assert!(target != my_account_id, "Sending message to self not supported."); + let sender = shards_manager_senders.get(&target).unwrap(); + sender.send(ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunkResponse { + partial_encoded_chunk_response: response, + received_time: clock.now(), + }); + None + } + NetworkRequests::PartialEncodedChunkMessage { account_id, partial_encoded_chunk } => { + assert!(account_id != my_account_id, "Sending message to self not supported."); + let sender = shards_manager_senders.get(&account_id).unwrap(); + sender.send(ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunk( + partial_encoded_chunk.into(), + )); + None + } + NetworkRequests::PartialEncodedChunkForward { account_id, forward } => { + assert!(account_id != my_account_id, "Sending message to self not supported."); + let sender = shards_manager_senders.get(&account_id).unwrap(); + sender + .send(ShardsManagerRequestFromNetwork::ProcessPartialEncodedChunkForward(forward)); + None + } + _ => Some(request), + }) } diff --git a/chain/network/src/types.rs b/chain/network/src/types.rs index befa5cf5248..add96f797b3 100644 --- a/chain/network/src/types.rs +++ b/chain/network/src/types.rs @@ -162,6 +162,10 @@ pub struct SetChainInfo(pub ChainInfo); #[rtype(result = "PeerManagerMessageResponse")] pub enum PeerManagerMessageRequest { NetworkRequests(NetworkRequests), + /// Request PeerManager to call `tier1_advertise_proxies()`. Used internally. + /// The effect would be accounts data known by this node broadcasted to other tier1 nodes. + /// That includes info about validator signer of this node. + AdvertiseTier1Proxies, /// Request PeerManager to connect to the given peer. /// Used in tests and internally by PeerManager. /// TODO: replace it with AsyncContext::spawn/run_later for internal use. @@ -193,6 +197,7 @@ impl PeerManagerMessageRequest { #[derive(actix::MessageResponse, Debug)] pub enum PeerManagerMessageResponse { NetworkResponses(NetworkResponses), + AdvertiseTier1Proxies, /// TEST-ONLY OutboundTcpConnect, FetchRoutingTable(RoutingTableInfo), diff --git a/chain/pool/src/lib.rs b/chain/pool/src/lib.rs index 9a28a374502..968be3dd90f 100644 --- a/chain/pool/src/lib.rs +++ b/chain/pool/src/lib.rs @@ -351,8 +351,9 @@ mod tests { end_nonce: u64, ) -> Vec { let signer_id: AccountId = signer_id.parse().unwrap(); - let signer = - Arc::new(InMemorySigner::from_seed(signer_id.clone(), KeyType::ED25519, signer_seed)); + let signer = Arc::new( + InMemorySigner::from_seed(signer_id.clone(), KeyType::ED25519, signer_seed).into(), + ); (starting_nonce..=end_nonce) .map(|i| { SignedTransaction::send_money( @@ -466,11 +467,10 @@ mod tests { .map(|i| { let signer_id = AccountId::try_from(format!("user_{}", i % 5)).unwrap(); let signer_seed = format!("user_{}", i % 3); - let signer = Arc::new(InMemorySigner::from_seed( - signer_id.clone(), - KeyType::ED25519, - &signer_seed, - )); + let signer = Arc::new( + InMemorySigner::from_seed(signer_id.clone(), KeyType::ED25519, &signer_seed) + .into(), + ); SignedTransaction::send_money( i, signer_id, @@ -561,11 +561,10 @@ mod tests { .map(|i| { let signer_id = AccountId::try_from(format!("user_{}", i)).unwrap(); let signer_seed = signer_id.as_ref(); - let signer = Arc::new(InMemorySigner::from_seed( - signer_id.clone(), - KeyType::ED25519, - signer_seed, - )); + let signer = Arc::new( + InMemorySigner::from_seed(signer_id.clone(), KeyType::ED25519, signer_seed) + .into(), + ); SignedTransaction::send_money( i, signer_id, diff --git a/chain/rosetta-rpc/Cargo.toml b/chain/rosetta-rpc/Cargo.toml index be1940000be..577d09262e6 100644 --- a/chain/rosetta-rpc/Cargo.toml +++ b/chain/rosetta-rpc/Cargo.toml @@ -41,13 +41,12 @@ node-runtime.workspace = true [dev-dependencies] insta.workspace = true near-actix-test-utils.workspace = true -near-async.workspace = true +near-time.workspace = true [features] protocol_feature_nonrefundable_transfer_nep491 = [] nightly_protocol = [ "near-actix-test-utils/nightly_protocol", - "near-async/nightly_protocol", "near-chain-configs/nightly_protocol", "near-client-primitives/nightly_protocol", "near-client/nightly_protocol", @@ -59,7 +58,6 @@ nightly_protocol = [ ] nightly = [ "near-actix-test-utils/nightly", - "near-async/nightly", "near-chain-configs/nightly", "near-client-primitives/nightly", "near-client/nightly", diff --git a/chain/rosetta-rpc/src/adapters/mod.rs b/chain/rosetta-rpc/src/adapters/mod.rs index ac92f44f9c8..5437430499c 100644 --- a/chain/rosetta-rpc/src/adapters/mod.rs +++ b/chain/rosetta-rpc/src/adapters/mod.rs @@ -848,12 +848,12 @@ mod tests { use super::*; use actix::System; use near_actix_test_utils::run_actix; - use near_async::time::Clock; use near_client::test_utils::setup_no_network; use near_crypto::{KeyType, SecretKey}; use near_parameters::{RuntimeConfig, RuntimeConfigView}; use near_primitives::action::delegate::{DelegateAction, SignedDelegateAction}; use near_primitives::transaction::{Action, TransferAction}; + use near_time::Clock; #[test] fn test_convert_block_changes_to_transactions() { diff --git a/core/async/Cargo.toml b/core/async/Cargo.toml index f46bf5eeffb..b949ca17296 100644 --- a/core/async/Cargo.toml +++ b/core/async/Cargo.toml @@ -19,12 +19,14 @@ once_cell.workspace = true serde.workspace = true serde_json.workspace = true time.workspace = true -tokio.workspace = true +tokio = { workspace = true, features = ["rt", "macros"] } tracing.workspace = true near-async-derive.workspace = true +# TODO(#11652): we use it only for logging. i think, it's a bit too much... near-o11y.workspace = true near-performance-metrics.workspace = true +near-time = { workspace = true, features = ["clock"] } [dev-dependencies] derive-enum-from-into.workspace = true diff --git a/core/async/src/examples/actix_component.rs b/core/async/src/examples/actix_component.rs deleted file mode 100644 index 0c45520175b..00000000000 --- a/core/async/src/examples/actix_component.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate as near_async; // only needed because we're in this crate itself -use crate::futures::{DelayedActionRunner, DelayedActionRunnerExt}; -use crate::messaging::{AsyncSender, SendAsync, Sender}; -use crate::time::Duration; -use futures::future::BoxFuture; -use futures::FutureExt; -use near_async_derive::{MultiSend, MultiSendMessage, MultiSenderFrom}; -use std::ops::{Deref, DerefMut}; - -#[derive(actix::Message, Debug, Clone, PartialEq, Eq)] -#[rtype(result = "ExampleResponse")] -pub struct ExampleRequest { - pub id: u32, -} - -#[derive(actix::MessageResponse, Debug, Clone, PartialEq, Eq)] -pub struct ExampleResponse { - pub id: u32, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PeriodicRequest { - pub id: u32, -} - -/// An example component that represents the backing state of an actor. -/// -/// It supports two functionalities: processing of ExampleRequest, as well as -/// sending a PeriodicRequest every second. We'll be testing both of these -/// functionalities. -pub struct ExampleComponent { - next_periodic_request_id: u32, - periodic_request_sender: Sender, -} - -impl ExampleComponent { - pub fn new(periodic_request_sender: Sender) -> Self { - Self { next_periodic_request_id: 0, periodic_request_sender } - } - - /// Example function that processes a request received by the actor. - pub fn process_request(&mut self, request: ExampleRequest) -> ExampleResponse { - ExampleResponse { id: request.id } - } - - /// Example start function that is called at the start of the actor, - /// to schedule timers. - pub fn start(&mut self, ctx: &mut dyn DelayedActionRunner) { - self.schedule_periodic_request(ctx); - } - - fn schedule_periodic_request(&mut self, ctx: &mut dyn DelayedActionRunner) { - ctx.run_later("periodic_request", Duration::seconds(1), |component, ctx| { - component - .periodic_request_sender - .send(PeriodicRequest { id: component.next_periodic_request_id }); - component.next_periodic_request_id += 1; - component.schedule_periodic_request(ctx); - }); - } -} - -/// Example actix Actor. Actors should have nothing but a shell that -/// forwards messages to the implementing component, because in the -/// TestLoop tests we aren't able to use this part at all. -struct ExampleActor { - component: ExampleComponent, -} - -// This Deref and DerefMut is used to support the DelayedActionRunner. -impl Deref for ExampleActor { - type Target = ExampleComponent; - - fn deref(&self) -> &Self::Target { - &self.component - } -} - -impl DerefMut for ExampleActor { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.component - } -} - -impl actix::Actor for ExampleActor { - type Context = actix::Context; - - fn started(&mut self, ctx: &mut Self::Context) { - self.component.start(ctx); - } -} - -impl actix::Handler for ExampleActor { - type Result = ExampleResponse; - - fn handle(&mut self, msg: ExampleRequest, _ctx: &mut Self::Context) -> Self::Result { - self.component.process_request(msg) - } -} - -/// Typically an actor would handle multiple messages, and this is where -/// multisenders come in handy. For this example we have only message but -/// we still demonstrate the use of multisenders. -#[derive(MultiSend, MultiSenderFrom, MultiSendMessage)] -#[multi_send_message_derive(Debug)] -pub struct ExampleComponentAdapter { - pub example: AsyncSender, -} - -/// Just another component that will send a request to the ExampleComponent. -pub struct OuterComponent { - example: ExampleComponentAdapter, -} - -impl OuterComponent { - pub fn new(example: ExampleComponentAdapter) -> Self { - Self { example } - } - - pub fn call_example_component_for_response(&self, id: u32) -> BoxFuture<'static, u32> { - let response = self.example.send_async(ExampleRequest { id }); - async move { response.await.unwrap().id }.boxed() - } -} diff --git a/core/async/src/examples/actix_component_test.rs b/core/async/src/examples/actix_component_test.rs deleted file mode 100644 index e3950776992..00000000000 --- a/core/async/src/examples/actix_component_test.rs +++ /dev/null @@ -1,90 +0,0 @@ -use super::actix_component::{ - ExampleComponent, ExampleComponentAdapterMessage, OuterComponent, PeriodicRequest, -}; -use crate::futures::FutureSpawnerExt; -use crate::messaging::IntoSender; -use crate::test_loop::event_handler::{capture_events, LoopEventHandler}; -use crate::test_loop::futures::{drive_futures, TestLoopDelayedActionEvent, TestLoopTask}; -use crate::test_loop::TestLoopBuilder; -use derive_enum_from_into::{EnumFrom, EnumTryInto}; -use std::sync::Arc; -use time::Duration; - -#[derive(derive_more::AsMut, derive_more::AsRef)] -struct ExampleComponentTestData { - dummy: (), - example: ExampleComponent, - outer: OuterComponent, - periodic_requests_captured: Vec, -} - -#[derive(Debug, EnumTryInto, EnumFrom)] -enum ExampleComponentTestEvent { - PeriodicRequest(PeriodicRequest), - ExampleRequest(ExampleComponentAdapterMessage), - // Needed to support DelayedActionRunner on the ExampleComponent. - DelayedAction(TestLoopDelayedActionEvent), - // Arc is needed to support futures. - Task(Arc), -} - -fn example_handler() -> LoopEventHandler { - LoopEventHandler::new_simple( - |event: ExampleComponentAdapterMessage, data: &mut ExampleComponent| match event { - ExampleComponentAdapterMessage::_example(request) => { - let response = data.process_request(request.message); - (request.callback)(Ok(response)); - } - }, - ) -} - -#[test] -fn test_actix_component() { - let builder = TestLoopBuilder::::new(); - let data = ExampleComponentTestData { - dummy: (), - example: ExampleComponent::new(builder.sender().into_sender()), - outer: OuterComponent::new( - builder.sender().into_wrapped_multi_sender::(), - ), - periodic_requests_captured: vec![], - }; - let mut test = builder.build(data); - // This is to allow futures to be used in the test even though the - // test itself is synchronous. - test.register_handler(drive_futures().widen()); - // This is to allow the ExampleComponent to run delayed actions (timers). - test.register_delayed_action_handler::(); - // This is to capture the periodic requests sent by the ExampleComponent - // so we can assert against it. - test.register_handler(capture_events::().widen()); - // This is to handle the ExampleComponentAdapterMessage events by - // forwarding them to the ExampleComponent. - test.register_handler(example_handler().widen()); - - // We need to redo whatever the ExampleActor does in its `started` method. - test.data.example.start(&mut test.sender().into_delayed_action_runner(test.shutting_down())); - // Send some requests; this can be done in the asynchronous context. - test.future_spawner().spawn("wait for 5", { - let res = test.data.outer.call_example_component_for_response(5); - async move { - assert_eq!(res.await, 5); - } - }); - test.future_spawner().spawn("wait for 6", { - let res = test.data.outer.call_example_component_for_response(6); - async move { - assert_eq!(res.await, 6); - } - }); - // Run for 3 seconds (not real time, but in the test loop time). - // It should result in sending 3 periodic requests. - test.run_for(Duration::seconds(3)); - assert_eq!( - test.data.periodic_requests_captured, - vec![PeriodicRequest { id: 0 }, PeriodicRequest { id: 1 }, PeriodicRequest { id: 2 },] - ); - - test.shutdown_and_drain_remaining_events(Duration::seconds(1)); -} diff --git a/core/async/src/examples/async_component.rs b/core/async/src/examples/async_component.rs deleted file mode 100644 index 542957b775a..00000000000 --- a/core/async/src/examples/async_component.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::{ - futures::{FutureSpawner, FutureSpawnerExt}, - messaging::{AsyncSender, Sender}, -}; - -// For this test, we have an InnerComponent which handles an InnerRequest and -// responds with InnerResponse, and an OuterComponent which handles an -// OuterRequest, spawns a future to send a request to the InnerComponent, and -// then responds back with an OuterResponse (but not as an Actix response; just -// another message). This mimics how we use Actix in nearcore. - -#[derive(Debug)] -pub(crate) struct InnerRequest(pub String); - -#[derive(Debug)] -pub(crate) struct InnerResponse(pub String); - -#[derive(Debug)] -pub(crate) struct OuterRequest(pub String); - -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct OuterResponse(pub String); - -pub(crate) struct InnerComponent; - -impl InnerComponent { - pub fn process_request(&mut self, request: InnerRequest) -> InnerResponse { - InnerResponse(request.0 + "!") - } -} - -pub(crate) struct OuterComponent { - inner_sender: AsyncSender, - outer_response_sender: Sender, -} - -impl OuterComponent { - pub fn new( - inner_sender: AsyncSender, - outer_response_sender: Sender, - ) -> Self { - Self { inner_sender, outer_response_sender } - } - - pub fn process_request(&mut self, request: OuterRequest, future_spawner: &dyn FutureSpawner) { - let inner_request = InnerRequest(request.0); - let sender = self.inner_sender.clone(); - let response_sender = self.outer_response_sender.clone(); - - // We're mimicing how we use Actix, and in an Actix handler context we don't have access - // to async/await. So we use a FutureSpawner to do that. - future_spawner.spawn("inner request", async move { - let inner_response = sender.send_async(inner_request).await; - let response = OuterResponse(inner_response.unwrap().0.repeat(2)); - response_sender.send(response); - }); - } -} diff --git a/core/async/src/examples/async_component_test.rs b/core/async/src/examples/async_component_test.rs deleted file mode 100644 index d8fec354ffa..00000000000 --- a/core/async/src/examples/async_component_test.rs +++ /dev/null @@ -1,72 +0,0 @@ -use super::async_component::{ - InnerComponent, InnerRequest, InnerResponse, OuterComponent, OuterRequest, OuterResponse, -}; -use crate::{ - messaging::{CanSend, IntoSender, MessageWithCallback}, - test_loop::{ - event_handler::{capture_events, LoopEventHandler}, - futures::{drive_futures, TestLoopFutureSpawner, TestLoopTask}, - TestLoopBuilder, - }, -}; -use derive_enum_from_into::{EnumFrom, EnumTryInto}; -use std::sync::Arc; - -#[derive(derive_more::AsMut, derive_more::AsRef)] -struct TestData { - dummy: (), // needed for any handlers that don't require data - output: Vec, // needed for capture_events handler - inner_component: InnerComponent, - outer_component: OuterComponent, -} - -#[derive(Debug, EnumTryInto, EnumFrom)] -enum TestEvent { - OuterResponse(OuterResponse), - OuterRequest(OuterRequest), - // Requests that need responses need to use MessageWithCallback. - InnerRequest(MessageWithCallback), - // Arc is needed to support futures. - Task(Arc), -} - -fn outer_request_handler( - future_spawner: TestLoopFutureSpawner, -) -> LoopEventHandler { - LoopEventHandler::new_simple(move |event, data: &mut OuterComponent| { - data.process_request(event, &future_spawner); - }) -} - -fn inner_request_handler( -) -> LoopEventHandler> { - LoopEventHandler::new_simple( - |event: MessageWithCallback, data: &mut InnerComponent| { - (event.callback)(Ok(data.process_request(event.message))); - }, - ) -} - -#[test] -fn test_async_component() { - let builder = TestLoopBuilder::::new(); - let sender = builder.sender(); - let future_spawner = builder.sender().into_future_spawner(); - let mut test = builder.build(TestData { - dummy: (), - output: vec![], - inner_component: InnerComponent, - outer_component: OuterComponent::new( - sender.clone().into_sender(), - sender.clone().into_sender(), - ), - }); - test.register_handler(drive_futures().widen()); - test.register_handler(capture_events::().widen()); - test.register_handler(outer_request_handler(future_spawner).widen()); - test.register_handler(inner_request_handler().widen()); - - sender.send(OuterRequest("hello".to_string())); - test.run_instant(); - assert_eq!(test.data.output, vec![OuterResponse("hello!hello!".to_string())]); -} diff --git a/core/async/src/examples/mod.rs b/core/async/src/examples/mod.rs deleted file mode 100644 index 5c56dba55d2..00000000000 --- a/core/async/src/examples/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod actix_component; -mod actix_component_test; -mod async_component; -mod async_component_test; -mod multi_instance_test; -mod sum_numbers; -mod sum_numbers_test; diff --git a/core/async/src/examples/multi_instance_test.rs b/core/async/src/examples/multi_instance_test.rs deleted file mode 100644 index 27ff2d0521b..00000000000 --- a/core/async/src/examples/multi_instance_test.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::time; -use derive_enum_from_into::{EnumFrom, EnumTryInto}; - -use crate::test_loop::delay_sender::DelaySender; -use crate::{ - examples::sum_numbers_test::forward_sum_request, - messaging::{CanSend, IntoSender}, - test_loop::{ - event_handler::{capture_events, LoopEventHandler}, - TestLoopBuilder, - }, -}; - -use super::sum_numbers::{ReportSumMsg, SumNumbersComponent, SumRequest}; - -#[derive(derive_more::AsMut, derive_more::AsRef)] -struct TestData { - summer: SumNumbersComponent, - sums: Vec, -} - -#[derive(Debug, EnumTryInto, EnumFrom)] -enum TestEvent { - RemoteRequest(i64), - LocalRequest(SumRequest), - Sum(ReportSumMsg), -} - -/// Let's pretend that when we send a remote request, the number gets sent to -/// every other instance in the setup as a local request. -fn forward_remote_request_to_other_instances( - sender: DelaySender<(usize, TestEvent)>, -) -> LoopEventHandler, (usize, TestEvent)> { - LoopEventHandler::new(move |event: (usize, TestEvent), data: &mut Vec| { - if let TestEvent::RemoteRequest(number) = event.1 { - for i in 0..data.len() { - if i != event.0 { - sender.send((i, TestEvent::LocalRequest(SumRequest::Number(number)))) - } - } - Ok(()) - } else { - Err(event) - } - }) -} - -#[test] -fn test_multi_instance() { - let builder = TestLoopBuilder::<(usize, TestEvent)>::new(); - // Build the SumNumberComponents so that it sends messages back to the test loop. - let mut data = vec![]; - for i in 0..5 { - data.push(TestData { - // Multi-instance sender can be converted to a single-instance sender - // so we can pass it into a component's constructor. - summer: SumNumbersComponent::new(builder.sender().for_index(i).into_sender()), - sums: vec![], - }); - } - let sender = builder.sender(); - let mut test = builder.build(data); - test.register_handler(forward_remote_request_to_other_instances(test.sender())); - for i in 0..5 { - // Single-instance handlers can be reused for multi-instance tests. - test.register_handler(forward_sum_request().widen().for_index(i)); - test.register_handler(capture_events::().widen().for_index(i)); - } - - // Send a RemoteRequest from each instance. - sender.send((0, TestEvent::RemoteRequest(1))); - sender.send((1, TestEvent::RemoteRequest(2))); - sender.send((2, TestEvent::RemoteRequest(3))); - sender.send((3, TestEvent::RemoteRequest(4))); - sender.send((4, TestEvent::RemoteRequest(5))); - - // Then send a GetSum request for each instance; we use a delay so that we can ensure - // these messages arrive later. (In a real test we wouldn't do this - the component would - // automatically emit some events and we would assert on these events. But for this - // contrived test we'll do it manually as a demonstration.) - for i in 0..5 { - sender.send_with_delay( - (i, TestEvent::LocalRequest(SumRequest::GetSum)), - time::Duration::milliseconds(1), - ); - } - test.run_for(time::Duration::milliseconds(2)); - assert_eq!(test.data[0].sums, vec![ReportSumMsg(14)]); - assert_eq!(test.data[1].sums, vec![ReportSumMsg(13)]); - assert_eq!(test.data[2].sums, vec![ReportSumMsg(12)]); - assert_eq!(test.data[3].sums, vec![ReportSumMsg(11)]); - assert_eq!(test.data[4].sums, vec![ReportSumMsg(10)]); -} diff --git a/core/async/src/examples/sum_numbers.rs b/core/async/src/examples/sum_numbers.rs deleted file mode 100644 index 91fdcb1490a..00000000000 --- a/core/async/src/examples/sum_numbers.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::messaging::Sender; - -#[derive(Debug, PartialEq, Eq)] -pub struct ReportSumMsg(pub i64); - -#[derive(Debug)] -pub enum SumRequest { - Number(i64), - GetSum, -} - -// Mimics a typical backing component of some actor in nearcore. Handles request -// messages, and sends some other messages to another actor. The other actor is -// abstracted with an Sender here. We'll show how to test this in -// sum_numbers_test.rs. -pub struct SumNumbersComponent { - result_sender: Sender, - numbers: Vec, -} - -impl SumNumbersComponent { - pub fn new(result_sender: Sender) -> Self { - Self { result_sender, numbers: vec![] } - } - - pub fn handle(&mut self, msg: SumRequest) { - match msg { - SumRequest::Number(n) => self.numbers.push(n), - SumRequest::GetSum => { - let sum = self.numbers.iter().sum(); - self.numbers.clear(); - self.result_sender.send(ReportSumMsg(sum)); - } - } - } -} diff --git a/core/async/src/examples/sum_numbers_test.rs b/core/async/src/examples/sum_numbers_test.rs deleted file mode 100644 index 428c8db81d0..00000000000 --- a/core/async/src/examples/sum_numbers_test.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::time; -use derive_enum_from_into::{EnumFrom, EnumTryInto}; - -use crate::{ - messaging::{CanSend, IntoSender}, - test_loop::{ - adhoc::{handle_adhoc_events, AdhocEvent, AdhocEventSender}, - event_handler::{capture_events, LoopEventHandler}, - TestLoopBuilder, - }, -}; - -use super::sum_numbers::{ReportSumMsg, SumNumbersComponent, SumRequest}; - -#[derive(derive_more::AsMut)] -struct TestData { - summer: SumNumbersComponent, - sums: Vec, -} - -impl AsMut for TestData { - fn as_mut(&mut self) -> &mut Self { - self - } -} - -#[derive(Debug, EnumTryInto, EnumFrom)] -enum TestEvent { - Request(SumRequest), - Sum(ReportSumMsg), -} - -// Handler that forwards SumRequest messages to the SumNumberComponent. -// Note that typically we would have a single handler like this, and it can -// be reused for any test that needs to send messages to this component. -pub fn forward_sum_request() -> LoopEventHandler { - LoopEventHandler::new_simple(|event, data: &mut SumNumbersComponent| { - data.handle(event); - }) -} - -#[test] -fn test_simple() { - let builder = TestLoopBuilder::::new(); - // Build the SumNumberComponents so that it sends messages back to the test loop. - let data = - TestData { summer: SumNumbersComponent::new(builder.sender().into_sender()), sums: vec![] }; - let mut test = builder.build(data); - test.register_handler(forward_sum_request().widen()); - test.register_handler(capture_events::().widen()); - - test.sender().send(SumRequest::Number(1)); - test.sender().send(SumRequest::Number(2)); - test.sender().send(SumRequest::GetSum); - test.sender().send(SumRequest::Number(3)); - test.sender().send(SumRequest::Number(4)); - test.sender().send(SumRequest::Number(5)); - test.sender().send(SumRequest::GetSum); - - test.run_for(time::Duration::milliseconds(1)); - assert_eq!(test.data.sums, vec![ReportSumMsg(3), ReportSumMsg(12)]); -} - -#[derive(Debug, EnumTryInto, EnumFrom)] -enum TestEventWithAdhoc { - Request(SumRequest), - Sum(ReportSumMsg), - Adhoc(AdhocEvent), -} - -#[test] -fn test_simple_with_adhoc() { - let builder = TestLoopBuilder::::new(); - // Build the SumNumberComponents so that it sends messages back to the test loop. - let data = - TestData { summer: SumNumbersComponent::new(builder.sender().into_sender()), sums: vec![] }; - let mut test = builder.build(data); - test.register_handler(forward_sum_request().widen()); - test.register_handler(capture_events::().widen()); - test.register_handler(handle_adhoc_events::().widen()); - - // It is preferrable to put as much setup logic as possible into an adhoc - // event (queued by .run below), so that as much logic as possible is - // executed in the TestLoop context. This allows the setup logic to show - // up in the visualizer too, with any of its logging shown under the - // adhoc event. - let sender = test.sender(); - test.sender().send_adhoc_event("initial events", move |_| { - sender.send(SumRequest::Number(1)); - sender.send(SumRequest::Number(2)); - sender.send(SumRequest::GetSum); - sender.send(SumRequest::Number(3)); - sender.send(SumRequest::Number(4)); - sender.send(SumRequest::Number(5)); - sender.send(SumRequest::GetSum); - }); - - test.run_instant(); - - // We can put assertions inside an adhoc event as well. This is - // especially useful if we had a multi-instance test, so that in the - // visualizer we can easily see which assertion was problematic. - // - // Here, we queue these events after the first test.run call, so we - // need to remember to call test.run again to actually execute them. - // Alternatively we can use test.sender().schedule_adhoc_event to queue - // the assertion events after the logic we expect to execute has been - // executed; that way we only need to call test.run once. Either way, - // don't worry if you forget to call test.run again; the test will - // panic at the end if there are unhandled events. - test.sender().send_adhoc_event("assertions", |data| { - assert_eq!(data.sums, vec![ReportSumMsg(3), ReportSumMsg(12)]); - }); - test.run_instant(); -} diff --git a/core/async/src/futures.rs b/core/async/src/futures.rs index fb677466e51..33e2a8eb8d8 100644 --- a/core/async/src/futures.rs +++ b/core/async/src/futures.rs @@ -1,7 +1,7 @@ -use crate::time::Duration; use actix::Actor; pub use futures::future::BoxFuture; // pub for macros use futures::FutureExt; +use near_time::Duration; use std::ops::DerefMut; /// Abstraction for something that can drive futures. diff --git a/core/async/src/lib.rs b/core/async/src/lib.rs index a918da412a0..6e71e7c0e5d 100644 --- a/core/async/src/lib.rs +++ b/core/async/src/lib.rs @@ -3,10 +3,10 @@ pub use near_async_derive::{MultiSend, MultiSendMessage, MultiSenderFrom}; pub mod actix; pub mod actix_wrapper; pub mod break_apart; -#[cfg(test)] -mod examples; mod functional; pub mod futures; pub mod messaging; pub mod test_loop; -pub mod time; + +// FIXME: near_time re-export is not optimal solution, but it would require to change time in many places +pub use near_time as time; diff --git a/core/async/src/test_loop.rs b/core/async/src/test_loop.rs index 41d76310b2b..944d473b8ff 100644 --- a/core/async/src/test_loop.rs +++ b/core/async/src/test_loop.rs @@ -11,22 +11,22 @@ //! - There is no need to set up mock objects that implement some //! message sender interface, instead, the test loop provides a sender object that //! can be used to send messages to the event loop. For example, suppose we were -//! to make a Client whose constructor requires a network adapter; instead of having -//! to make a mock for the network adapter, we can simply pass in `loop.sender()`. +//! to make a Client whose constructor requires a shards_manager adapter; instead +//! of having to make a mock for the shards_manager adapter, we can simply register +//! the shards_manager actor with testloop and pass in its sender. //! - Compared to writing synchronous tests, there is no need to manually deliver //! network messages or handle actix messages at certain points of the test. Instead, //! the event loop will invoke the appropriate event handlers whenever there is any //! event remaining in the event loop. This ensures that no messages are ever missed. //! - Test setup code can be modular and reusable, because the test specification -//! consists entirely of registering the desired event handlers. Rather than passing -//! a giant callback into a giant setup(...) function to customize one part of a huge -//! integration test, we can flexibly compose specific event handlers. For example, -//! we may add an event handler to route all ShardsManager-related network messages -//! reliably, and at the same time another event handler to drop 50% of Block network -//! messages. Also, we can use an event handler as long as it is relevant for a test -//! (i.e. a ForwardShardsManagerRequest event handler can be used as long as the test -//! involves ShardsManagers), regardless of the exact architecture of the test. -//! See `LoopEventHandler` for more details. +//! consists entirely of registering the data and actors. Rather than passing a giant +//! callback into a giant setup(...) function to customize one part of a huge +//! integration test, we can flexibly compose specific modules with event handlers. +//! For example, we may add an event handler to route all ShardsManager-related network +//! messages reliably, and at the same time another event handler to drop 50% of Block +//! network messages. Also, we can use an event handler as long as it is relevant for a +//! test (i.e. a ForwardShardsManagerRequest event handler can be used as long as the +//! test involves ShardsManagers), regardless of the exact architecture of the test. //! //! - Debuggability: //! - Because ALL execution is in response of events, the whole test can be cleanly @@ -53,109 +53,88 @@ //! such as distributing chunks to other nodes within X milliseconds provided that //! network messages have a 10ms delay. //! - The framework does not require major migrations to existing code, e.g. it is -//! compatible with the Actix framework (and possibly futures in the future). +//! compatible with the Actix framework and futures. //! //! A note on the order of execution of the events: all events that are due at the same //! timestamp are executed in FIFO order. For example, if the events are emitted in the //! following order: (A due 100ms), (B due 0ms), (C due 200ms), (D due 0ms), (E due 100ms) //! then the actual order of execution is B, D, A, E, C. -pub mod adhoc; -pub mod delay_sender; -pub mod event_handler; +pub mod data; pub mod futures; - -use self::{ - delay_sender::DelaySender, - event_handler::LoopEventHandler, - futures::{TestLoopFutureSpawner, TestLoopTask}, -}; -use crate::time; -use crate::time::{Clock, Duration}; -use ::time::ext::InstantExt as _; -use near_o11y::{testonly::init_test_logger, tracing::info}; +pub mod pending_events_sender; +pub mod sender; + +use data::TestLoopData; +use futures::{TestLoopAsyncComputationSpawner, TestLoopFututeSpawner}; +use near_time::{Clock, Duration, FakeClock}; +use pending_events_sender::{CallbackEvent, PendingEventsSender}; +use sender::TestLoopSender; use serde::Serialize; +use std::collections::BinaryHeap; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::sync::Mutex; -use std::{collections::BinaryHeap, fmt::Debug, sync::Arc}; +use time::ext::InstantExt; + +use crate::messaging::{Actor, LateBoundSender}; /// Main struct for the Test Loop framework. -/// The `Data` type should contain all the business logic state that is relevant -/// to the test. The `Event` type should contain all the possible events that -/// are sent to the event loop. +/// The `TestLoopData` should contain all the business logic state that is relevant +/// to the test. All possible `Event` that are sent to the event loop are callbacks. +/// See TestLoopData for mode details. /// -/// The convention is that, for single-instance tests, -/// - `Data` should be a struct with a derive_more::AsMut and derive_more::AsRef -/// (so that `Data` implements AsMut and AsRef for each of its -/// fields.) -/// - `Event` should be an enum with a derive(EnumTryInto, EnumFrom), so that it -/// implements TryInto and From for each of its variants. -/// and that for multi-instance tests, `Data` is `Vec` and `Event` is -/// `(usize, SingleEvent)`. -pub struct TestLoop { - pub data: Data, - +/// Events are sent to the testloop, with a possible delay, via the pending_events_sender. +pub struct TestLoopV2 { + /// The data that is stored and accessed by the test loop. + pub data: TestLoopData, /// The sender is used to send events to the event loop. - sender: DelaySender, - + pending_events_sender: PendingEventsSender, /// The events that are yet to be handled. They are kept in a heap so that /// events that shall execute earlier (by our own virtual clock) are popped /// first. - events: BinaryHeap>, + events: BinaryHeap, /// The events that will enter the events heap upon the next iteration. - pending_events: Arc>>, + pending_events: Arc>, /// The next ID to assign to an event we receive. next_event_index: usize, /// The current virtual time. current_time: Duration, /// Fake clock that always returns the virtual time. - clock: time::FakeClock, + clock: near_time::FakeClock, /// Shutdown flag. When this flag is true, delayed action runners will no /// longer post any new events to the event loop. shutting_down: Arc, - /// All the event handlers that are registered. We invoke them one by one - /// for each event, until one of them handles the event (or panic if no one - /// handles it). - handlers: Vec>, } /// An event waiting to be executed, ordered by the due time and then by ID. -struct EventInHeap { - event: Event, +struct EventInHeap { + event: CallbackEvent, due: Duration, id: usize, } -impl PartialEq for EventInHeap { +impl PartialEq for EventInHeap { fn eq(&self, other: &Self) -> bool { self.due == other.due && self.id == other.id } } -impl Eq for EventInHeap {} +impl Eq for EventInHeap {} -impl PartialOrd for EventInHeap { +impl PartialOrd for EventInHeap { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for EventInHeap { +impl Ord for EventInHeap { fn cmp(&self, other: &Self) -> std::cmp::Ordering { (self.due, self.id).cmp(&(other.due, other.id)).reverse() } } -/// An event that is in-flight. The delay here is relative to the virtual time -/// when the handler that emitted this event is invoked (e.g. a network routing -/// handler may respond to an outbound message and emit an inbound message with -/// a 10ms delay). -struct EventInFlight { - event: Event, - delay: Duration, -} - -struct InFlightEvents { - events: Vec>, +struct InFlightEvents { + events: Vec, /// The TestLoop thread ID. This and the following field are used to detect unintended /// parallel processing. event_loop_thread_id: std::thread::ThreadId, @@ -163,8 +142,8 @@ struct InFlightEvents { is_handling_event: bool, } -impl InFlightEvents { - fn add(&mut self, event: Event, delay: Duration) { +impl InFlightEvents { + fn add(&mut self, event: CallbackEvent) { if !self.is_handling_event && std::thread::current().id() != self.event_loop_thread_id { // Another thread shall not be sending an event while we're not handling an event. // If that happens, it means we have a rogue thread spawned somewhere that has not been @@ -174,68 +153,11 @@ impl InFlightEvents { "Event was sent from the wrong thread. TestLoop tests should be single-threaded. \ Check if there's any code that spawns computation on another thread such as \ rayon::spawn, and convert it to AsyncComputationSpawner or FutureSpawner. \ - Event: {:?}", - event + Event: {}", + event.description ); } - self.events.push(EventInFlight { event, delay }); - } -} - -/// Builder that should be used to construct a `TestLoop`. The reason why the -/// builder exists is that usually the `Data` type can only be constructed using -/// the event sender provided by the test loop, so this way we can avoid a -/// construction dependency cycle. -pub struct TestLoopBuilder { - clock: time::FakeClock, - pending_events: Arc>>, - pending_events_sender: DelaySender, - shutting_down: Arc, -} - -impl TestLoopBuilder { - pub fn new() -> Self { - // Initialize the logger to make sure the test loop printouts are visible. - init_test_logger(); - let pending_events = Arc::new(Mutex::new(InFlightEvents { - events: Vec::new(), - event_loop_thread_id: std::thread::current().id(), - is_handling_event: false, - })); - Self { - clock: time::FakeClock::default(), - pending_events: pending_events.clone(), - pending_events_sender: DelaySender::new(move |event, delay| { - pending_events.lock().unwrap().add(event, delay); - }), - shutting_down: Arc::new(AtomicBool::new(false)), - } - } - - /// Returns a sender that can be used anywhere to send events to the loop. - pub fn sender(&self) -> DelaySender { - self.pending_events_sender.clone() - } - - /// Returns a clock that will always return the current virtual time. - pub fn clock(&self) -> time::Clock { - self.clock.clock() - } - - /// Returns a flag indicating whether the TestLoop system is being shut down; - /// this is similar to whether the Actix system is shutting down. - pub fn shutting_down(&self) -> Arc { - self.shutting_down.clone() - } - - pub fn build(self, data: Data) -> TestLoop { - TestLoop::new( - self.pending_events, - self.pending_events_sender, - self.clock, - self.shutting_down, - data, - ) + self.events.push(event); } } @@ -262,42 +184,86 @@ struct EventEndLogOutput { total_events: usize, } -impl TestLoop { - fn new( - pending_events: Arc>>, - sender: DelaySender, - clock: time::FakeClock, - shutting_down: Arc, - data: Data, - ) -> Self { +impl TestLoopV2 { + pub fn new() -> Self { + let pending_events = Arc::new(Mutex::new(InFlightEvents { + events: Vec::new(), + event_loop_thread_id: std::thread::current().id(), + is_handling_event: false, + })); + let pending_events_clone = pending_events.clone(); + let pending_events_sender = PendingEventsSender::new(move |callback_event| { + let mut pending_events = pending_events_clone.lock().unwrap(); + pending_events.add(callback_event); + }); + let shutting_down = Arc::new(AtomicBool::new(false)); Self { - data, - sender, + data: TestLoopData::new(pending_events_sender.clone(), shutting_down.clone()), events: BinaryHeap::new(), pending_events, + pending_events_sender, next_event_index: 0, - current_time: time::Duration::ZERO, - clock, + current_time: Duration::ZERO, + clock: FakeClock::default(), shutting_down, - handlers: Vec::new(), } } - pub fn sender(&self) -> DelaySender { - self.sender.clone() + /// Returns a FutureSpawner that can be used to spawn futures into the loop. + pub fn future_spawner(&self) -> TestLoopFututeSpawner { + self.pending_events_sender.clone() + } + + /// Returns an AsyncComputationSpawner that can be used to spawn async computation into the + /// loop. The `artificial_delay` allows the test to determine an artificial delay that the + /// computation should take, based on the name of the computation. + pub fn async_computation_spawner( + &self, + artificial_delay: impl Fn(&str) -> Duration + Send + Sync + 'static, + ) -> TestLoopAsyncComputationSpawner { + TestLoopAsyncComputationSpawner::new(self.pending_events_sender.clone(), artificial_delay) } + /// Returns a sender that can be used anywhere to send events to the loop. + pub fn sender(&self) -> PendingEventsSender { + self.pending_events_sender.clone() + } + + /// Sends any ad-hoc event to the loop. + pub fn send_adhoc_event( + &self, + description: String, + callback: impl FnOnce(&mut TestLoopData) + Send + 'static, + ) { + self.pending_events_sender.send(format!("Adhoc({})", description), Box::new(callback)); + } + + /// Returns a clock that will always return the current virtual time. pub fn clock(&self) -> Clock { self.clock.clock() } - pub fn shutting_down(&self) -> Arc { - self.shutting_down.clone() + pub fn register_actor( + &mut self, + actor: A, + adapter: Option>>>, + ) -> TestLoopSender + where + A: Actor + 'static, + { + self.data.register_actor_for_index(0, actor, adapter) } - /// Registers a new event handler to the test loop. - pub fn register_handler(&mut self, handler: LoopEventHandler) { - self.handlers.push(handler); + pub fn register_actor_for_index( + &mut self, + index: usize, + actor: A, + adapter: Option>>>, + ) -> TestLoopSender + where + A: Actor + 'static, + { + self.data.register_actor_for_index(index, actor, adapter) } /// Helper to push events we have just received into the heap. @@ -305,8 +271,8 @@ impl TestLoop { for event in self.pending_events.lock().unwrap().events.drain(..) { self.events.push(EventInHeap { due: self.current_time + event.delay, - event: event.event, id: self.next_event_index, + event, }); self.next_event_index += 1; } @@ -316,8 +282,8 @@ impl TestLoop { /// Takes a decider to determine whether to advance time, handle the next event, and/or to stop. fn advance_till_next_event( &mut self, - decider: &impl Fn(Option, &mut Data) -> AdvanceDecision, - ) -> Option> { + decider: &impl Fn(Option, &mut TestLoopData) -> AdvanceDecision, + ) -> Option { loop { // New events may have been sent to the TestLoop from outside, and the previous // iteration of the loop may have made new futures ready, so queue up any received @@ -372,33 +338,27 @@ impl TestLoop { } /// Processes the given event, by logging a line first and then finding a handler to run it. - fn process_event(&mut self, mut event: EventInHeap) { + fn process_event(&mut self, event: EventInHeap) { let start_json = serde_json::to_string(&EventStartLogOutput { current_index: event.id, total_events: self.next_event_index, - current_event: format!("{:?}", event.event), + current_event: event.event.description, current_time_ms: event.due.whole_milliseconds() as u64, }) .unwrap(); - info!(target: "test_loop", "TEST_LOOP_EVENT_START {}", start_json); + tracing::info!(target: "test_loop", "TEST_LOOP_EVENT_START {}", start_json); assert_eq!(self.current_time, event.due); - for handler in &mut self.handlers { - if let Err(e) = handler.handle(event.event, &mut self.data) { - event.event = e; - } else { - // Push any new events into the queue. Do this before emitting the end log line, - // so that it contains the correct new total number of events. - self.queue_received_events(); - let end_json = serde_json::to_string(&EventEndLogOutput { - total_events: self.next_event_index, - }) + let callback = event.event.callback; + callback(&mut self.data); + + // Push any new events into the queue. Do this before emitting the end log line, + // so that it contains the correct new total number of events. + self.queue_received_events(); + let end_json = + serde_json::to_string(&EventEndLogOutput { total_events: self.next_event_index }) .unwrap(); - info!(target: "test_loop", "TEST_LOOP_EVENT_END {}", end_json); - return; - } - } - panic!("Unhandled event: {:?}", event.event); + tracing::info!(target: "test_loop", "TEST_LOOP_EVENT_END {}", end_json); } /// Runs the test loop for the given duration. This function may be called @@ -423,9 +383,13 @@ impl TestLoop { /// /// To maximize logical consistency, the condition is only checked before the clock would /// advance. If it returns true, execution stops before advancing the clock. - pub fn run_until(&mut self, condition: impl Fn(&mut Data) -> bool, maximum_duration: Duration) { + pub fn run_until( + &mut self, + condition: impl Fn(&mut TestLoopData) -> bool, + maximum_duration: Duration, + ) { let deadline = self.current_time + maximum_duration; - let decider = |next_time, data: &mut Data| { + let decider = |next_time, data: &mut TestLoopData| { if condition(data) { return AdvanceDecision::Stop; } @@ -441,10 +405,6 @@ impl TestLoop { } } - /// Used to finish off remaining events that are still in the loop. This can be necessary if the - /// destructor of some components wait for certain condition to become true. Otherwise, the - /// destructors may end up waiting forever. This also helps avoid a panic when destructing - /// TestLoop itself, as it asserts that all events have been handled. pub fn shutdown_and_drain_remaining_events(mut self, maximum_duration: Duration) { self.shutting_down.store(true, Ordering::Relaxed); self.run_for(maximum_duration); @@ -454,23 +414,16 @@ impl TestLoop { pub fn run_instant(&mut self) { self.run_for(Duration::ZERO); } - - pub fn future_spawner(&self) -> TestLoopFutureSpawner - where - Event: From>, - { - self.sender().narrow() - } } -impl Drop for TestLoop { +impl Drop for TestLoopV2 { fn drop(&mut self) { self.queue_received_events(); if let Some(event) = self.events.pop() { panic!( - "Event scheduled at {} is not handled at the end of the test: {:?}. + "Event scheduled at {} is not handled at the end of the test: {}. Consider calling `test.shutdown_and_drain_remaining_events(...)`.", - event.due, event.event + event.due, event.event.description ); } } @@ -485,38 +438,23 @@ enum AdvanceDecision { #[cfg(test)] mod tests { use crate::futures::FutureSpawnerExt; - use crate::test_loop::futures::{drive_futures, TestLoopTask}; - use crate::test_loop::TestLoopBuilder; - use derive_enum_from_into::{EnumFrom, EnumTryInto}; - use derive_more::AsMut; + use crate::test_loop::TestLoopV2; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use time::Duration; - #[derive(Debug, EnumFrom, EnumTryInto)] - enum TestEvent { - Task(Arc), - } - - #[derive(AsMut)] - struct TestData { - dummy: (), - } - // Tests that the TestLoop correctly handles futures that sleep on the fake clock. #[test] fn test_futures() { - let builder = TestLoopBuilder::::new(); - let clock = builder.clock(); - let mut test = builder.build::(TestData { dummy: () }); - test.register_handler(drive_futures().widen()); + let mut test_loop = TestLoopV2::new(); + let clock = test_loop.clock(); let start_time = clock.now(); let finished = Arc::new(AtomicUsize::new(0)); let clock1 = clock.clone(); let finished1 = finished.clone(); - test.sender().into_future_spawner().spawn("test1", async move { + test_loop.future_spawner().spawn("test1", async move { assert_eq!(clock1.now(), start_time); clock1.sleep(Duration::seconds(10)).await; assert_eq!(clock1.now(), start_time + Duration::seconds(10)); @@ -525,11 +463,11 @@ mod tests { finished1.fetch_add(1, Ordering::Relaxed); }); - test.run_for(Duration::seconds(2)); + test_loop.run_for(Duration::seconds(2)); let clock2 = clock; let finished2 = finished.clone(); - test.sender().into_future_spawner().spawn("test2", async move { + test_loop.future_spawner().spawn("test2", async move { assert_eq!(clock2.now(), start_time + Duration::seconds(2)); clock2.sleep(Duration::seconds(3)).await; assert_eq!(clock2.now(), start_time + Duration::seconds(5)); @@ -540,7 +478,7 @@ mod tests { // During these 30 virtual seconds, the TestLoop should've automatically advanced the clock // to wake each future as they become ready to run again. The code inside the futures // assert that the fake clock does indeed have the expected times. - test.run_for(Duration::seconds(30)); + test_loop.run_for(Duration::seconds(30)); assert_eq!(finished.load(Ordering::Relaxed), 2); } } diff --git a/core/async/src/test_loop/adhoc.rs b/core/async/src/test_loop/adhoc.rs deleted file mode 100644 index 29a3847d645..00000000000 --- a/core/async/src/test_loop/adhoc.rs +++ /dev/null @@ -1,58 +0,0 @@ -use super::{delay_sender::DelaySender, event_handler::LoopEventHandler}; -use crate::messaging::CanSend; -use crate::time; -use std::fmt::Debug; - -/// Any arbitrary logic that runs as part of the test loop. -/// -/// This is not necessary (since one can just take the data and perform -/// arbitrary logic on it), but this is good for documentation and allows -/// the logs emitted as part of this function's execution to be segmented -/// in the TestLoop visualizer. -pub struct AdhocEvent { - pub description: String, - pub handler: Box, -} - -impl Debug for AdhocEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.description) - } -} - -/// Allows DelaySender to be used to send or schedule adhoc events. -pub trait AdhocEventSender { - fn send_adhoc_event(&self, description: &str, f: impl FnOnce(&mut Data) + Send + 'static); - fn schedule_adhoc_event( - &self, - description: &str, - f: impl FnOnce(&mut Data) + Send + 'static, - delay: time::Duration, - ); -} - -impl> + 'static> AdhocEventSender - for DelaySender -{ - fn send_adhoc_event(&self, description: &str, f: impl FnOnce(&mut Data) + Send + 'static) { - self.send(AdhocEvent { description: description.to_string(), handler: Box::new(f) }) - } - fn schedule_adhoc_event( - &self, - description: &str, - f: impl FnOnce(&mut Data) + Send + 'static, - delay: time::Duration, - ) { - self.send_with_delay( - AdhocEvent { description: description.to_string(), handler: Box::new(f) }.into(), - delay, - ) - } -} - -/// Handler to handle adhoc events. -pub fn handle_adhoc_events() -> LoopEventHandler> { - LoopEventHandler::new_simple(|event: AdhocEvent, data| { - (event.handler)(data); - }) -} diff --git a/core/async/src/test_loop/data.rs b/core/async/src/test_loop/data.rs new file mode 100644 index 00000000000..b13a7113a43 --- /dev/null +++ b/core/async/src/test_loop/data.rs @@ -0,0 +1,168 @@ +use std::any::{type_name, Any}; +use std::marker::PhantomData; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use crate::messaging::{Actor, LateBoundSender}; + +use super::sender::TestLoopSender; +use super::PendingEventsSender; + +/// TestLoopData is the container for all data that is stored and accessed by the test loop. +/// +/// TestLoopData is used to mainly register actors, which can be accessed using a handle during +/// the execution of the TestLoop. +/// +/// ```rust, ignore +/// let mut data = TestLoopData::new(pending_events_sender, shutting_down); +/// +/// let actor = TestActor::new(); +/// let adapter = LateBoundSender::new(); +/// +/// let sender: TestLoopSender = data.register_actor(actor, Some(adapter)); +/// +/// // We can now send messages to the actor using the sender and adapter. +/// sender.send(TestMessage {}); +/// adapter.send(TestMessage {}); +/// ``` +/// +/// We have the ability to register data of any type, and then access it using a handle. This is +/// useful if we would like to have some arbitrary callback event in testloop to access this data. +/// +/// ```rust, ignore +/// let mut data = TestLoopData::new(pending_events_sender, shutting_down); +/// let handle: TestLoopDataHandle = data.register_data(42); +/// assert_eq!(data.get(&handle), 42); +/// ``` +/// +/// Note that the handler from one TestLoopData cannot be used to access data from another. +/// +pub struct TestLoopData { + // Container of the data. We store it as a vec of Any so that we can store any type of data. + data: Vec>, + // Sender to send events to the test loop. Used mainly for registering actors. + pending_events_sender: PendingEventsSender, + // Atomic bool to check if the test loop is shutting down. Used mainly for registering actors. + shutting_down: Arc, +} + +impl TestLoopData { + pub fn new(pending_events_sender: PendingEventsSender, shutting_down: Arc) -> Self { + Self { data: Vec::new(), pending_events_sender, shutting_down } + } + + /// Function to register data of any type in the TestLoopData. + /// Returns a handler to the data that can be used to access the data later. + pub fn register_data(&mut self, data: T) -> TestLoopDataHandle { + let id = self.data.len(); + self.data.push(Box::new(data)); + TestLoopDataHandle::new(id) + } + + /// Function to register an actor in the TestLoopData. + /// Additionally schedules the start event for the actor on testloop. + /// Returns a TestLoopSender that can be used to send messages to the actor. + pub fn register_actor_for_index( + &mut self, + index: usize, + actor: A, + adapter: Option>>>, + ) -> TestLoopSender + where + A: Actor + 'static, + { + let actor_handle = self.register_data(actor); + let sender = TestLoopSender::new( + actor_handle, + self.pending_events_sender.clone().for_index(index), + self.shutting_down.clone(), + ); + self.queue_start_actor_event(sender.clone()); + if let Some(adapter) = adapter { + adapter.bind(sender.clone()); + } + sender + } + + // Helper function to queue the start actor event on the test loop while registering an actor. + fn queue_start_actor_event(&self, mut sender: TestLoopSender) + where + A: Actor + 'static, + { + let callback = move |data: &mut TestLoopData| { + let actor = data.get_mut(&sender.actor_handle()); + actor.start_actor(&mut sender); + }; + self.pending_events_sender + .send(format!("StartActor({:?})", type_name::()), Box::new(callback)); + } + + /// Function to get reference to the data stored in TestLoopData. + pub fn get(&self, handle: &TestLoopDataHandle) -> &T { + self.data + .get(handle.id) + .expect("Handle id out of bounds. Does handle belong to this TestLoopData?") + .downcast_ref() + .expect("Handle type mismatched. Does handle belong to this TestLoopData?") + } + + /// Function to get mutable reference to the data stored in TestLoopData. + pub fn get_mut(&mut self, handle: &TestLoopDataHandle) -> &mut T { + self.data + .get_mut(handle.id) + .expect("Handle id out of bounds. Does handle belong to this TestLoopData?") + .downcast_mut() + .expect("Handle type mismatched. Does handle belong to this TestLoopData?") + } +} + +/// This is a handle to the data stored in TestLoopData. +/// test_loop_data.get(&handle) will return the data stored in TestLoopData. +/// test_loop_data.get_mut(&handle) will return a mutable reference to the data stored in TestLoopData. +pub struct TestLoopDataHandle +where + T: 'static, +{ + // This is an index into the data vector in TestLoopData. + id: usize, + // Saving the type info here as fn(T) to implicitly implement Send + Sync. + _phantom: PhantomData, +} + +impl Clone for TestLoopDataHandle { + fn clone(&self) -> Self { + Self { id: self.id, _phantom: PhantomData } + } +} + +impl TestLoopDataHandle { + fn new(id: usize) -> Self { + Self { id, _phantom: PhantomData } + } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::AtomicBool; + use std::sync::Arc; + + use crate::test_loop::data::TestLoopData; + use crate::test_loop::PendingEventsSender; + + #[derive(Debug, PartialEq)] + struct TestData { + pub value: usize, + } + + #[test] + fn test_register_data() { + let mut data = + TestLoopData::new(PendingEventsSender::new(|_| {}), Arc::new(AtomicBool::new(false))); + let test_data = TestData { value: 42 }; + let handle = data.register_data(test_data); + assert_eq!(data.get(&handle), &TestData { value: 42 }); + + data.get_mut(&handle).value = 43; + assert_eq!(data.get(&handle), &TestData { value: 43 }); + } +} diff --git a/core/async/src/test_loop/delay_sender.rs b/core/async/src/test_loop/delay_sender.rs deleted file mode 100644 index a3d0dd918b4..00000000000 --- a/core/async/src/test_loop/delay_sender.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::break_apart::BreakApart; -use crate::messaging; -use crate::messaging::{IntoMultiSender, IntoSender}; -use crate::test_loop::futures::{ - TestLoopAsyncComputationEvent, TestLoopAsyncComputationSpawner, TestLoopDelayedActionEvent, - TestLoopDelayedActionRunner, -}; -use crate::time; -use crate::time::Duration; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -use super::futures::{TestLoopFutureSpawner, TestLoopTask}; - -/// Interface to send an event with a delay (in virtual time). It can be -/// converted to a Sender for any message type that can be converted into -/// the event type, so that a DelaySender given by the test loop may be passed -/// to production code that expects a Sender. -pub struct DelaySender(Arc); - -impl + 'static> messaging::CanSend for DelaySender { - fn send(&self, message: Message) { - self.send_with_delay(message.into(), time::Duration::ZERO); - } -} - -impl DelaySender { - pub fn new(inner: impl Fn(Event, time::Duration) + Send + Sync + 'static) -> Self { - Self(Arc::new(inner)) - } - - pub fn send_with_delay(&self, event: Event, delay: time::Duration) { - self.0(event, delay); - } - - pub fn with_additional_delay(&self, delay: time::Duration) -> DelaySender - where - Event: 'static, - { - let f = self.0.clone(); - Self(Arc::new(move |event, other_delay| f(event, delay + other_delay))) - } - - pub fn narrow(self) -> DelaySender - where - Event: From + 'static, - { - DelaySender::::new(move |event, delay| { - self.send_with_delay(event.into(), delay) - }) - } - - /// A shortcut for a common use case, where we use an enum message to - /// represent all the possible messages that a multisender may be used to - /// send. - /// - /// This assumes that S is a multisender with the derive - /// `#[derive(MultiSendMessage, ...)]`, which creates the enum - /// `MyMultiSenderMessage` (where `MyMultiSender` is the name of the struct - /// being derived from). - /// - /// To use, first include in the test loop event enum a case for - /// `MyMultiSenderMessage`. Then, call this function to get a multisender, - /// like - /// `builder.wrapped_multi_sender()`. - pub fn into_wrapped_multi_sender(self) -> S - where - Self: IntoSender, - BreakApart: IntoMultiSender, - { - self.into_sender().break_apart().into_multi_sender() - } - - pub fn into_delayed_action_runner( - self, - shutting_down: Arc, - ) -> TestLoopDelayedActionRunner - where - Event: From> + 'static, - { - TestLoopDelayedActionRunner { sender: self.narrow(), shutting_down } - } - - /// Returns a FutureSpawner that can be used to spawn futures into the loop. - pub fn into_future_spawner(self) -> TestLoopFutureSpawner - where - Event: From> + 'static, - { - self.narrow() - } - - /// Returns an AsyncComputationSpawner that can be used to spawn async computation into the - /// loop. The `artificial_delay` allows the test to determine an artificial delay that the - /// computation should take, based on the name of the computation. - pub fn into_async_computation_spawner( - self, - artificial_delay: impl Fn(&str) -> Duration + Send + Sync + 'static, - ) -> TestLoopAsyncComputationSpawner - where - Event: From + 'static, - { - TestLoopAsyncComputationSpawner { - sender: self.narrow(), - artificial_delay: Box::new(artificial_delay), - } - } -} - -impl DelaySender<(usize, Event)> { - /// Converts a multi-instance sender to a single-instance sender. - pub fn for_index(self, index: usize) -> DelaySender { - DelaySender::new(move |event, delay| { - self.send_with_delay((index, event), delay); - }) - } -} - -/// Custom implementation because #derive wouldn't work if Event does not Clone. -impl Clone for DelaySender { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} diff --git a/core/async/src/test_loop/event_handler.rs b/core/async/src/test_loop/event_handler.rs deleted file mode 100644 index e9479d02744..00000000000 --- a/core/async/src/test_loop/event_handler.rs +++ /dev/null @@ -1,86 +0,0 @@ -/// An event handler registered on a test loop. Each event handler usually -/// handles only some events, so we will usually have multiple event handlers -/// registered to cover all event types. -pub struct LoopEventHandler( - Box Result<(), Event>>, -); - -impl LoopEventHandler { - /// Creates a handler from the handling logic function. The function is - /// called on each event. It should return Ok(()) if the event was handled, - /// or Err(event) if the event was not handled (which will cause it to be - /// passed to the next handler). - pub fn new(handler: impl FnMut(Event, &mut Data) -> Result<(), Event> + 'static) -> Self { - Self(Box::new(handler)) - } - - /// Like new(), but the handler is not given the ability to reject the event. - pub fn new_simple(mut handler: impl FnMut(Event, &mut Data) + 'static) -> Self { - Self::new(move |event, data| { - handler(event, data); - Ok(()) - }) - } - - /// Adapts this handler to a handler whose data is a superset of our data - /// and whose event is a superset of our event. - /// For data, A is a superset of B if A implements AsRef and AsMut. - /// For event, A is a superset of B if A implements From and - /// TryIntoOrSelf. - pub fn widen< - OuterData: AsMut, - OuterEvent: TryIntoOrSelf + From + 'static, - >( - mut self, - ) -> LoopEventHandler { - LoopEventHandler(Box::new(move |event, data| { - let mut inner_data = data.as_mut(); - let inner_event = event.try_into_or_self()?; - self.0(inner_event, &mut inner_data)?; - Ok(()) - })) - } - - /// Adapts this handler to a handler whose data is a vector of our data, - /// and whose event is a is the tuple (index, our event), for a specific - /// index. - pub fn for_index(mut self, index: usize) -> LoopEventHandler, (usize, Event)> { - LoopEventHandler(Box::new(move |event, data| { - if event.0 == index { - self.0(event.1, &mut data[index]).map_err(|event| (index, event)) - } else { - Err(event) - } - })) - } - - pub(crate) fn handle(&mut self, event: Event, data: &mut Data) -> Result<(), Event> { - self.0(event, data) - } -} - -/// A convenient trait to TryInto, or else return the original object. It's useful -/// for implementing event handlers. -pub trait TryIntoOrSelf: Sized { - fn try_into_or_self(self) -> Result; -} - -impl> TryIntoOrSelf for T { - fn try_into_or_self(self) -> Result { - self.try_into() - } -} - -/// An event handler that puts the event into a vector in the Data, as long as -/// the Data contains a Vec. (Use widen() right after). -/// -/// This is used on output events so that after the test loop finishes running -/// we can assert on those events. -pub fn capture_events() -> LoopEventHandler, Event> { - LoopEventHandler::new_simple(|event, data: &mut Vec| data.push(event)) -} - -/// An event handler that ignores all events. -pub fn ignore_events() -> LoopEventHandler<(), Event> { - LoopEventHandler::new_simple(|_, _| {}) -} diff --git a/core/async/src/test_loop/futures.rs b/core/async/src/test_loop/futures.rs index ccf49414943..ff8e122e7cd 100644 --- a/core/async/src/test_loop/futures.rs +++ b/core/async/src/test_loop/futures.rs @@ -1,223 +1,112 @@ -use super::{delay_sender::DelaySender, event_handler::LoopEventHandler, TestLoop}; -use crate::futures::{AsyncComputationSpawner, DelayedActionRunner}; -use crate::test_loop::event_handler::TryIntoOrSelf; -use crate::time::Duration; -use crate::{futures::FutureSpawner, messaging::CanSend}; -use futures::future::BoxFuture; -use futures::task::{waker_ref, ArcWake}; -use std::fmt::Debug; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::task::Context; -// Support for futures in TestLoop. -// -// There are two key features this file provides for TestLoop: -// -// 1. A general way to spawn futures and have the TestLoop drive the futures. -// To support this, add () to the Data, add Arc as an Event, -// and add DriveFutures as a handler. Finally, pass -// DelaySender> as the &dyn FutureSpawner to any -// component that needs to spawn futures. -// -// This causes any futures spawned during the test to end up as an event -// (an Arc) in the test loop. The event will eventually be -// executed by the DriveFutures handler, which will drive the future -// until it is either suspended or completed. If suspended, then the waker -// of the future (called when the future is ready to resume) will place -// the Arc event back into the test loop to be executed -// again. -// -// 2. A way to send a message to the TestLoop and expect a response as a -// future, which will resolve whenever the TestLoop handles the message. -// To support this, use MessageWithCallback as the -// event type, and in the handler, call (event.responder)(result) -// (possibly asynchronously) to complete the future. -// -// This is needed to support the AsyncSender interface, which is required -// by some components as they expect a response to each message. The way -// this is implemented is by implementing a conversion from -// DelaySender> to -// AsyncSender. - -/// A message, plus a response callback. This should be used as the event type -/// when testing an Actix component that's expected to return a result. -/// -/// The response is used to complete the future that is returned by -/// our `AsyncSender::send_async` implementation. - -pub struct TestLoopTask { - future: Mutex>>, - sender: DelaySender>, - description: String, -} +use futures::future::BoxFuture; +use futures::task::{waker_ref, ArcWake}; +use near_time::Duration; -impl ArcWake for TestLoopTask { - fn wake_by_ref(arc_self: &Arc) { - let clone = arc_self.clone(); - arc_self.sender.send(clone); - } -} +use crate::futures::{AsyncComputationSpawner, FutureSpawner}; -impl Debug for TestLoopTask { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Task").field(&self.description).finish() - } -} +use super::data::TestLoopData; +use super::PendingEventsSender; -/// Drives any Arc events (futures spawned by our implementation -/// of FutureSpawner) that are remaining in the loop. -pub fn drive_futures() -> LoopEventHandler<(), Arc> { - LoopEventHandler::new_simple(|task: Arc, _| { - // The following is copied from the Rust async book. - // Take the future, and if it has not yet completed (is still Some), - // poll it in an attempt to complete it. - let mut future_slot = task.future.lock().unwrap(); - if let Some(mut future) = future_slot.take() { - let waker = waker_ref(&task); - let context = &mut Context::from_waker(&*waker); - if future.as_mut().poll(context).is_pending() { - // We're still not done processing the future, so put it - // back in its task to be run again in the future. - *future_slot = Some(future); - } - } - }) -} +/// Support for futures in TestLoop. +/// +/// There are two key features this file provides for TestLoop: +/// +/// 1. A general way to spawn futures and have the TestLoop drive the futures. +/// To support this, pass test_loop.future_spawner() the &dyn FutureSpawner +/// to any component that needs to spawn futures. +/// +/// This causes any futures spawned during the test to end up as an callback in the +/// test loop. The event will eventually be executed by the drive_futures function, +/// which will drive the future until it is either suspended or completed. If suspended, +/// then the waker of the future (called when the future is ready to resume) will place +/// the event back into the test loop to be executed again. +/// +/// 2. A way to send a message to the TestLoop and expect a response as a +/// future, which will resolve whenever the TestLoop handles the message. +/// To support this, use MessageWithCallback as the +/// event type, and in the handler, call (event.responder)(result) +/// (possibly asynchronously) to complete the future. +/// +/// This is needed to support the AsyncSender interface, which is required +/// by some components as they expect a response to each message. The way +/// this is implemented is by implementing a conversion from +/// DelaySender> to +/// AsyncSender. -/// A DelaySender> is a FutureSpawner that can be used to +/// A DelaySender is a FutureSpawner that can be used to /// spawn futures into the test loop. We give it a convenient alias. -pub type TestLoopFutureSpawner = DelaySender>; +pub type TestLoopFututeSpawner = PendingEventsSender; -impl FutureSpawner for TestLoopFutureSpawner { +impl FutureSpawner for TestLoopFututeSpawner { fn spawn_boxed(&self, description: &str, f: BoxFuture<'static, ()>) { - let task = Arc::new(TestLoopTask { + let task = Arc::new(FutureTask { future: Mutex::new(Some(f)), sender: self.clone(), description: description.to_string(), }); - self.send(task); - } -} - -/// Represents an action that was scheduled to run later, by using -/// `DelayedActionRunner::run_later`. -pub struct TestLoopDelayedActionEvent { - name: String, - action: Box) + Send + 'static>, -} - -impl Debug for TestLoopDelayedActionEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("DelayedAction").field(&self.name).finish() - } -} - -/// An event handler that handles only `TestLoopDelayedActionEvent`s, by -/// running the action encapsulated in the event. -pub fn drive_delayed_action_runners( - sender: DelaySender>, - shutting_down: Arc, -) -> LoopEventHandler> { - LoopEventHandler::new_simple(move |event: TestLoopDelayedActionEvent, data: &mut T| { - let mut runner = TestLoopDelayedActionRunner { - sender: sender.clone(), - shutting_down: shutting_down.clone(), + let callback = move |_: &mut TestLoopData| { + drive_futures(&task); }; - (event.action)(data, &mut runner); - }) -} - -/// `DelayedActionRunner` that schedules the action to be run later by the -/// TestLoop event loop. -pub struct TestLoopDelayedActionRunner { - pub(crate) sender: DelaySender>, - pub(crate) shutting_down: Arc, -} - -impl DelayedActionRunner for TestLoopDelayedActionRunner { - fn run_later_boxed( - &mut self, - name: &str, - dur: Duration, - action: Box) + Send + 'static>, - ) { - if self.shutting_down.load(Ordering::Relaxed) { - return; - } - self.sender.send_with_delay( - TestLoopDelayedActionEvent { name: name.to_string(), action }, - dur.try_into().unwrap(), - ); + self.send(format!("FutureSpawn({})", description), Box::new(callback)); } } -impl TestLoop { - /// Shorthand for registering this frequently used handler. - pub fn register_delayed_action_handler(&mut self) - where - T: 'static, - Data: AsMut, - Event: TryIntoOrSelf> - + From> - + 'static, - { - self.register_handler( - drive_delayed_action_runners::(self.sender().narrow(), self.shutting_down()).widen(), - ); - } +struct FutureTask { + future: Mutex>>, + sender: PendingEventsSender, + description: String, } -impl TestLoop, (usize, Event)> { - /// Shorthand for registering this frequently used handler for a multi-instance test. - pub fn register_delayed_action_handler_for_index(&mut self, idx: usize) - where - T: 'static, - Data: AsMut, - Event: TryIntoOrSelf> - + From> - + 'static, - { - self.register_handler( - drive_delayed_action_runners::( - self.sender().for_index(idx).narrow(), - self.shutting_down(), - ) - .widen() - .for_index(idx), +impl ArcWake for FutureTask { + fn wake_by_ref(arc_self: &Arc) { + let clone = arc_self.clone(); + arc_self.sender.send( + format!("FutureTask({})", arc_self.description), + Box::new(move |_: &mut TestLoopData| drive_futures(&clone)), ); } } -/// An event that represents async computation. See async_computation_spawner() in DelaySender. -pub struct TestLoopAsyncComputationEvent { - name: String, - f: Box, -} - -impl Debug for TestLoopAsyncComputationEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("AsyncComputation").field(&self.name).finish() +fn drive_futures(task: &Arc) { + // The following is copied from the Rust async book. + // Take the future, and if it has not yet completed (is still Some), + // poll it in an attempt to complete it. + let mut future_slot = task.future.lock().unwrap(); + if let Some(mut future) = future_slot.take() { + let waker = waker_ref(&task); + let context = &mut Context::from_waker(&*waker); + if future.as_mut().poll(context).is_pending() { + // We're still not done processing the future, so put it + // back in its task to be run again in the future. + *future_slot = Some(future); + } } } /// AsyncComputationSpawner that spawns the computation in the TestLoop. pub struct TestLoopAsyncComputationSpawner { - pub(crate) sender: DelaySender, - pub(crate) artificial_delay: Box Duration + Send + Sync>, + sender: PendingEventsSender, + artificial_delay: Box Duration + Send + Sync>, +} + +impl TestLoopAsyncComputationSpawner { + pub fn new( + sender: PendingEventsSender, + artificial_delay: impl Fn(&str) -> Duration + Send + Sync + 'static, + ) -> Self { + Self { sender, artificial_delay: Box::new(artificial_delay) } + } } impl AsyncComputationSpawner for TestLoopAsyncComputationSpawner { fn spawn_boxed(&self, name: &str, f: Box) { self.sender.send_with_delay( - TestLoopAsyncComputationEvent { name: name.to_string(), f }, + format!("AsyncComputation({})", name), + Box::new(move |_| f()), (self.artificial_delay)(name), ); } } - -pub fn drive_async_computations() -> LoopEventHandler<(), TestLoopAsyncComputationEvent> { - LoopEventHandler::new_simple(|event: TestLoopAsyncComputationEvent, _data: &mut ()| { - (event.f)(); - }) -} diff --git a/core/async/src/test_loop/pending_events_sender.rs b/core/async/src/test_loop/pending_events_sender.rs new file mode 100644 index 00000000000..9c758ee85bd --- /dev/null +++ b/core/async/src/test_loop/pending_events_sender.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use near_time::Duration; + +use super::data::TestLoopData; + +type TestLoopCallback = Box; + +/// Interface to send an event with a delay (in virtual time). +#[derive(Clone)] +pub struct PendingEventsSender { + client_index: usize, + sender: Arc, +} + +impl PendingEventsSender { + pub(crate) fn new(f: impl Fn(CallbackEvent) + Send + Sync + 'static) -> Self { + Self { client_index: 0, sender: Arc::new(f) } + } + + pub(crate) fn set_index(&mut self, index: usize) { + self.client_index = index; + } + + /// Set the index of the actor that is sending the event. + /// This is purely for debug purposes and does not affect the execution of the event. + pub fn for_index(mut self, index: usize) -> Self { + self.set_index(index); + self + } + + /// Schedule a callback to be executed. TestLoop follows the fifo order of executing events. + pub fn send(&self, description: String, callback: TestLoopCallback) { + self.send_with_delay(description, callback, Duration::ZERO); + } + + /// Schedule a callback to be executed after a delay. + pub fn send_with_delay( + &self, + description: String, + callback: TestLoopCallback, + delay: Duration, + ) { + let description = format!("({},{})", self.client_index, description); + (self.sender)(CallbackEvent { description, callback, delay }); + } +} + +/// CallbackEvent for testloop is a simple event with a single callback which gets executed. This takes in +/// testloop data as a parameter which can be used alongside with data handlers to access data. +/// This is very versatile and we can potentially have anything as a callback. For example, for the case of Senders +/// and Handlers, we can have a simple implementation of the CanSend function as a callback calling the Handle function +/// of the actor. +pub(crate) struct CallbackEvent { + pub(crate) callback: TestLoopCallback, + pub(crate) delay: Duration, + pub(crate) description: String, +} diff --git a/core/async/src/test_loop/sender.rs b/core/async/src/test_loop/sender.rs new file mode 100644 index 00000000000..e1df6a5ccc5 --- /dev/null +++ b/core/async/src/test_loop/sender.rs @@ -0,0 +1,149 @@ +use std::any::type_name; +use std::fmt::Debug; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use crate::futures::DelayedActionRunner; +use crate::messaging::{Actor, CanSend, HandlerWithContext, MessageWithCallback}; +use crate::time::Duration; + +use super::data::{TestLoopData, TestLoopDataHandle}; +use super::PendingEventsSender; + +/// TestLoopSender implements the CanSend methods for an actor that can Handle them. This is +/// similar to our pattern of having an ActixWarpper around an actor to send messages to it. +/// +/// ```rust, ignore +/// let actor = TestActor::new(); +/// let adapter = LateBoundSender::new(); +/// +/// let sender: TestLoopSender = data.register_actor(actor, Some(adapter)); +/// +/// // We can now send messages to the actor using the sender and adapter. +/// sender.send(TestMessage {}); +/// adapter.send(TestMessage {}); +/// ``` +/// +/// For the purposes of testloop, we keep a copy of the delay sender that is used to schedule +/// callbacks on the testloop to execute either the actor.handle() function or the +/// DelayedActionRunner.run_later_boxed() function. +pub struct TestLoopSender +where + A: 'static, +{ + actor_handle: TestLoopDataHandle, + pending_events_sender: PendingEventsSender, + shutting_down: Arc, + sender_delay: Duration, +} + +impl Clone for TestLoopSender { + fn clone(&self) -> Self { + Self { + actor_handle: self.actor_handle.clone(), + pending_events_sender: self.pending_events_sender.clone(), + shutting_down: self.shutting_down.clone(), + sender_delay: self.sender_delay, + } + } +} + +/// `DelayedActionRunner` that schedules the action to be run later by the TestLoop event loop. +impl DelayedActionRunner for TestLoopSender +where + A: 'static, +{ + fn run_later_boxed( + &mut self, + name: &str, + dur: Duration, + f: Box) + Send + 'static>, + ) { + if self.shutting_down.load(Ordering::Relaxed) { + return; + } + + let mut this = self.clone(); + let callback = move |data: &mut TestLoopData| { + let actor = data.get_mut(&this.actor_handle); + f(actor, &mut this); + }; + self.pending_events_sender.send_with_delay( + format!("DelayedAction {}({:?})", pretty_type_name::(), name), + Box::new(callback), + dur, + ); + } +} + +impl CanSend for TestLoopSender +where + M: actix::Message + Debug + Send + 'static, + A: Actor + HandlerWithContext + 'static, +{ + fn send(&self, msg: M) { + let mut this = self.clone(); + let callback = move |data: &mut TestLoopData| { + tracing::debug!(target: "test_loop", "Handling message: {:?}", msg); + let actor = data.get_mut(&this.actor_handle); + actor.handle(msg, &mut this); + }; + self.pending_events_sender.send_with_delay( + format!("{}({})", pretty_type_name::(), pretty_type_name::()), + Box::new(callback), + self.sender_delay, + ); + } +} + +impl CanSend> for TestLoopSender +where + M: actix::Message + Debug + Send + 'static, + A: Actor + HandlerWithContext + 'static, + R: 'static, +{ + fn send(&self, msg: MessageWithCallback) { + let mut this = self.clone(); + let callback = move |data: &mut TestLoopData| { + tracing::debug!(target: "test_loop", "Handling message: {:?}", msg); + let MessageWithCallback { message: msg, callback } = msg; + let actor = data.get_mut(&this.actor_handle); + let result = actor.handle(msg, &mut this); + callback(Ok(result)); + }; + self.pending_events_sender.send_with_delay( + format!("{}({})", pretty_type_name::(), pretty_type_name::()), + Box::new(callback), + self.sender_delay, + ); + } +} + +impl TestLoopSender +where + A: Actor + 'static, +{ + pub(crate) fn new( + actor_handle: TestLoopDataHandle, + pending_events_sender: PendingEventsSender, + shutting_down: Arc, + ) -> Self { + Self { actor_handle, pending_events_sender, shutting_down, sender_delay: Duration::ZERO } + } + + /// Returns a new TestLoopSender which sends messages with the given delay. + pub fn with_delay(self, delay: Duration) -> Self { + Self { sender_delay: delay, ..self } + } + + pub fn actor_handle(&self) -> TestLoopDataHandle { + self.actor_handle.clone() + } +} + +// Quick and dirty way of getting the type name without the module path. +// Does not work for more complex types like std::sync::Arc> +// example near_chunks::shards_manager_actor::ShardsManagerActor -> ShardsManagerActor +fn pretty_type_name() -> &'static str { + type_name::().split("::").last().unwrap() +} diff --git a/core/async/src/time.rs b/core/async/src/time.rs deleted file mode 100644 index a742f77ca48..00000000000 --- a/core/async/src/time.rs +++ /dev/null @@ -1,485 +0,0 @@ -//! Time module provides a non-global clock, which should be passed -//! as an argument to functions which need to read the current time. -//! In particular try to avoid storing the clock instances in the objects. -//! Functions which use system clock directly are non-hermetic, which -//! makes them effectively non-deterministic and hard to test. -//! -//! Clock provides 2 types of time reads: -//! 1. now() (aka POSIX CLOCK_MONOTONIC, aka time::Instant) -//! time as perceived by the machine making the measurement. -//! The subsequent calls to now() are guaranteed to return monotonic -//! results. It should be used for measuring the latency of operations -//! as observed by the machine. The time::Instant itself doesn't -//! translate to any specific timestamp, so it is not meaningful for -//! anyone other than the machine doing the measurement. -//! 2. now_utc() (aka POSIX CLOCK_REALTIME, aka time::Utc) -//! expected to approximate the (global) UTC time. -//! There is NO guarantee that the subsequent reads will be monotonic, -//! as CLOCK_REALTIME it configurable in the OS settings, or can be updated -//! during NTP sync. Should be used whenever you need to communicate a timestamp -//! over the network, or store it for later use. Remember that clocks -//! of different machines are not perfectly synchronized, and in extreme -//! cases can be totally skewed. -use once_cell::sync::Lazy; -use std::cmp::Ordering; -use std::collections::BinaryHeap; -use std::sync::{Arc, Mutex}; -pub use time::error; -use time::ext::InstantExt; - -// TODO: consider wrapping these types to prevent interactions -// with other time libraries, especially to prevent the direct access -// to the realtime (i.e. not through the Clock). -pub type Instant = std::time::Instant; -// TODO: OffsetDateTime stores the timestamp in a decomposed form of -// (year,month,day,hour,...). If we find it inefficient, we should -// probably migrate to a pure UNIX timestamp and convert is to datetime -// only when needed. -pub type Utc = time::OffsetDateTime; -pub type Duration = time::Duration; - -// Instant doesn't have a deterministic constructor, -// however since Instant is not convertible to an unix timestamp, -// we can snapshot Instant::now() once and treat it as a constant. -// All observable effects will be then deterministic. -static FAKE_CLOCK_MONO_START: Lazy = Lazy::new(Instant::now); - -// An arbitrary non-trivial deterministic Utc timestamp. -const FAKE_CLOCK_UTC_START: Lazy = Lazy::new(|| Utc::from_unix_timestamp(89108233).unwrap()); - -// By the definition of derive(PartialEq), Finite(...) < Infinite. -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -pub enum Deadline { - Finite(Instant), - Infinite, -} - -impl From for Deadline { - fn from(t: Instant) -> Deadline { - Deadline::Finite(t) - } -} - -#[derive(Clone)] -enum ClockInner { - Real, - Fake(FakeClock), -} - -/// Clock encapsulates a system clock, allowing to replace it -/// with a fake in tests. -/// Since system clock is a source of external information, -/// it has to be replaced with a fake double, if we want our -/// tests to be deterministic. -/// -/// TODO: add tests. -#[derive(Clone)] -pub struct Clock(ClockInner); - -impl Clock { - /// Constructor of the real clock. Use it in production code. - /// Preferably construct it directly in the main() function, - /// so that it can be faked out in every other function. - pub fn real() -> Clock { - Clock(ClockInner::Real) - } - - /// Current time according to the monotone clock. - pub fn now(&self) -> Instant { - match &self.0 { - ClockInner::Real => Instant::now(), - ClockInner::Fake(fake) => fake.now(), - } - } - - /// Current time according to the system/walltime clock. - pub fn now_utc(&self) -> Utc { - match &self.0 { - ClockInner::Real => Utc::now_utc(), - ClockInner::Fake(fake) => fake.now_utc(), - } - } - - /// Cancellable. - pub async fn sleep_until_deadline(&self, t: Deadline) { - match t { - Deadline::Infinite => std::future::pending().await, - Deadline::Finite(t) => self.sleep_until(t).await, - } - } - - /// Cancellable. - pub async fn sleep_until(&self, t: Instant) { - match &self.0 { - ClockInner::Real => tokio::time::sleep_until(t.into()).await, - ClockInner::Fake(fake) => fake.sleep_until(t).await, - } - } - - /// Cancellable. - pub async fn sleep(&self, d: Duration) { - match &self.0 { - ClockInner::Real => tokio::time::sleep(d.try_into().unwrap()).await, - ClockInner::Fake(fake) => fake.sleep(d).await, - } - } -} - -struct FakeClockInner { - utc: Utc, - instant: Instant, - waiters: BinaryHeap, -} - -/// Whenever a user of a FakeClock calls `sleep` for `sleep_until`, we create a -/// `ClockWaiterInHeap` so that the returned future can be completed when the -/// clock advances past the desired deadline. -struct ClockWaiterInHeap { - deadline: Instant, - waker: tokio::sync::oneshot::Sender<()>, -} - -impl PartialEq for ClockWaiterInHeap { - fn eq(&self, other: &Self) -> bool { - self.deadline == other.deadline - } -} - -impl PartialOrd for ClockWaiterInHeap { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Eq for ClockWaiterInHeap {} - -impl Ord for ClockWaiterInHeap { - fn cmp(&self, other: &Self) -> Ordering { - other.deadline.cmp(&self.deadline) - } -} - -impl FakeClockInner { - pub fn new(utc: Utc) -> Self { - Self { utc, instant: *FAKE_CLOCK_MONO_START, waiters: BinaryHeap::new() } - } - - pub fn now(&mut self) -> Instant { - self.instant - } - pub fn now_utc(&mut self) -> Utc { - self.utc - } - pub fn advance(&mut self, d: Duration) { - assert!(d >= Duration::ZERO); - if d == Duration::ZERO { - return; - } - self.instant += d; - self.utc += d; - while let Some(earliest_waiter) = self.waiters.peek() { - if earliest_waiter.deadline <= self.instant { - self.waiters.pop().unwrap().waker.send(()).ok(); - } else { - break; - } - } - } - pub fn advance_until(&mut self, t: Instant) { - let by = t.signed_duration_since(self.now()); - self.advance(by); - } -} - -/// TEST-ONLY -#[derive(Clone)] -pub struct FakeClock(Arc>); - -impl FakeClock { - /// Constructor of a fake clock. Use it in tests. - /// It supports manually moving time forward (via advance()). - /// You can also arbitrarily set the UTC time in runtime. - /// Use FakeClock::clock() when calling prod code from tests. - pub fn new(utc: Utc) -> Self { - Self(Arc::new(Mutex::new(FakeClockInner::new(utc)))) - } - pub fn now(&self) -> Instant { - self.0.lock().unwrap().now() - } - - pub fn now_utc(&self) -> Utc { - self.0.lock().unwrap().now_utc() - } - pub fn advance(&self, d: Duration) { - self.0.lock().unwrap().advance(d); - } - pub fn advance_until(&self, t: Instant) { - self.0.lock().unwrap().advance_until(t); - } - pub fn clock(&self) -> Clock { - Clock(ClockInner::Fake(self.clone())) - } - pub fn set_utc(&self, utc: Utc) { - self.0.lock().unwrap().utc = utc; - } - - /// Cancel-safe. - pub async fn sleep(&self, d: Duration) { - if d <= Duration::ZERO { - return; - } - let receiver = { - let mut inner = self.0.lock().unwrap(); - let (sender, receiver) = tokio::sync::oneshot::channel(); - let waiter = ClockWaiterInHeap { waker: sender, deadline: inner.now() + d }; - inner.waiters.push(waiter); - receiver - }; - receiver.await.unwrap(); - } - - /// Cancel-safe. - pub async fn sleep_until(&self, t: Instant) { - let receiver = { - let mut inner = self.0.lock().unwrap(); - if inner.now() >= t { - return; - } - let (sender, receiver) = tokio::sync::oneshot::channel(); - let waiter = ClockWaiterInHeap { waker: sender, deadline: t }; - inner.waiters.push(waiter); - receiver - }; - receiver.await.unwrap(); - } - - /// Returns the earliest waiter, or None if no one is waiting on the clock. - /// The returned instant is guaranteed to be <= any waiter that is currently - /// waiting on the clock to advance. - pub fn first_waiter(&self) -> Option { - let inner = self.0.lock().unwrap(); - inner.waiters.peek().map(|waiter| waiter.deadline) - } -} - -impl Default for FakeClock { - fn default() -> FakeClock { - Self::new(*FAKE_CLOCK_UTC_START) - } -} - -/// Interval equivalent to tokio::time::Interval with -/// MissedTickBehavior::Skip. -pub struct Interval { - next: Instant, - period: time::Duration, -} - -impl Interval { - pub fn new(next: Instant, period: time::Duration) -> Self { - Self { next, period } - } - - /// Cancel-safe. - pub async fn tick(&mut self, clock: &Clock) { - clock.sleep_until(self.next).await; - let now = clock.now(); - // Implementation of `tokio::time::MissedTickBehavior::Skip`. - // Please refer to https://docs.rs/tokio/latest/tokio/time/enum.MissedTickBehavior.html# - // for details. In essence, if more than `period` of time passes between consecutive - // calls to tick, then the second tick completes immediately and the next one will be - // aligned to the original schedule. - self.next = now.add_signed(self.period).sub_signed(Duration::nanoseconds( - ((now.signed_duration_since(self.next)).whole_nanoseconds() - % self.period.whole_nanoseconds()) - .try_into() - // This operation is practically guaranteed not to - // fail, as in order for it to fail, `period` would - // have to be longer than `now - timeout`, and both - // would have to be longer than 584 years. - // - // If it did fail, there's not a good way to pass - // the error along to the user, so we just panic. - .expect("too much time has elapsed since the interval was supposed to tick"), - )); - } -} - -/// Provides serialization of Duration as std::time::Duration. -pub mod serde_duration_as_std { - use crate::time::Duration; - use serde::Deserialize; - use serde::Serialize; - - pub fn serialize(dur: &Duration, s: S) -> Result - where - S: serde::Serializer, - { - let std: std::time::Duration = (*dur) - .try_into() - .map_err(|_| serde::ser::Error::custom("Duration conversion failed"))?; - std.serialize(s) - } - - pub fn deserialize<'de, D>(d: D) -> Result - where - D: serde::Deserializer<'de>, - { - let std: std::time::Duration = Deserialize::deserialize(d)?; - Ok(std.try_into().map_err(|_| serde::de::Error::custom("Duration conversion failed"))?) - } -} - -/// Provides serialization of Duration as std::time::Duration. -pub mod serde_opt_duration_as_std { - use crate::time::Duration; - use serde::Deserialize; - use serde::Serialize; - - pub fn serialize(dur: &Option, s: S) -> Result - where - S: serde::Serializer, - { - match dur { - Some(dur) => { - let std: std::time::Duration = (*dur) - .try_into() - .map_err(|_| serde::ser::Error::custom("Duration conversion failed"))?; - std.serialize(s) - } - None => s.serialize_none(), - } - } - - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - let std: Option = Deserialize::deserialize(d)?; - Ok(std - .map(|std| { - std.try_into().map_err(|_| serde::de::Error::custom("Duration conversion failed")) - }) - .transpose()?) - } -} - -pub mod serde_utc_as_iso { - use crate::time::Utc; - use serde::{Deserialize, Serialize}; - use time::format_description::well_known::Iso8601; - - pub fn serialize(utc: &Utc, s: S) -> Result - where - S: serde::Serializer, - { - utc.format(&Iso8601::DEFAULT).map_err(::custom)?.serialize(s) - } - - pub fn deserialize<'de, D>(d: D) -> Result - where - D: serde::Deserializer<'de>, - { - let str: String = Deserialize::deserialize(d)?; - Utc::parse(&str, &Iso8601::DEFAULT).map_err(::custom) - } -} - -pub mod serde_opt_utc_as_iso { - use crate::time::Utc; - use serde::{Deserialize, Serialize}; - use time::format_description::well_known::Iso8601; - - pub fn serialize(utc: &Option, s: S) -> Result - where - S: serde::Serializer, - { - match utc { - Some(utc) => utc - .format(&Iso8601::DEFAULT) - .map_err(::custom)? - .serialize(s), - None => s.serialize_none(), - } - } - - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - let str: Option = Deserialize::deserialize(d)?; - Ok(str - .map(|str| { - Utc::parse(&str, &Iso8601::DEFAULT).map_err(::custom) - }) - .transpose()?) - } -} - -#[cfg(test)] -mod tests { - use crate::time::Duration; - use serde_json; - - #[test] - fn test_serde_duration() { - #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] - struct Test(#[serde(with = "super::serde_duration_as_std")] Duration); - - let expected = Test(Duration::milliseconds(1234)); - let str = serde_json::to_string(&expected).unwrap(); - assert_eq!(str, r#"{"secs":1,"nanos":234000000}"#); - let actual: Test = serde_json::from_str(&str).unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn test_serde_opt_duration() { - #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] - struct Test(#[serde(with = "super::serde_opt_duration_as_std")] Option); - - let expected = Test(Some(Duration::milliseconds(1234))); - let str = serde_json::to_string(&expected).unwrap(); - assert_eq!(str, r#"{"secs":1,"nanos":234000000}"#); - let actual: Test = serde_json::from_str(&str).unwrap(); - assert_eq!(actual, expected); - - let expected = Test(None); - let str = serde_json::to_string(&expected).unwrap(); - assert_eq!(str, r#"null"#); - let actual: Test = serde_json::from_str(&str).unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn test_serde_utc() { - #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] - struct Test(#[serde(with = "super::serde_utc_as_iso")] super::Utc); - - let expected = - Test(super::Utc::from_unix_timestamp_nanos(1709582343123456789i128).unwrap()); - let str = serde_json::to_string(&expected).unwrap(); - assert_eq!(str, r#""2024-03-04T19:59:03.123456789Z""#); - let actual: Test = serde_json::from_str(&str).unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn test_serde_opt_utc() { - #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] - struct Test(#[serde(with = "super::serde_opt_utc_as_iso")] Option); - - let expected = - Test(Some(super::Utc::from_unix_timestamp_nanos(1709582343123456789i128).unwrap())); - let str = serde_json::to_string(&expected).unwrap(); - assert_eq!(str, r#""2024-03-04T19:59:03.123456789Z""#); - let actual: Test = serde_json::from_str(&str).unwrap(); - assert_eq!(actual, expected); - - let expected = Test(None); - let str = serde_json::to_string(&expected).unwrap(); - assert_eq!(str, r#"null"#); - let actual: Test = serde_json::from_str(&str).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/core/chain-configs/Cargo.toml b/core/chain-configs/Cargo.toml index 30495c6411f..9a34387c69d 100644 --- a/core/chain-configs/Cargo.toml +++ b/core/chain-configs/Cargo.toml @@ -25,7 +25,7 @@ smart-default.workspace = true time.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-crypto.workspace = true near-o11y = { workspace = true, optional = true } near-parameters.workspace = true @@ -35,18 +35,18 @@ near-config-utils.workspace = true [features] protocol_feature_nonrefundable_transfer_nep491 = [] nightly_protocol = [ - "near-async/nightly_protocol", "near-o11y/nightly_protocol", "near-parameters/nightly_protocol", "near-primitives/nightly_protocol", ] nightly = [ - "near-async/nightly", "near-o11y/nightly", "near-parameters/nightly", "near-primitives/nightly", "nightly_protocol", "protocol_feature_nonrefundable_transfer_nep491", ] +test_genesis = ["near-primitives/rand"] +test_utils = ["near-primitives/rand"] default = [] metrics = ["near-o11y"] diff --git a/core/chain-configs/src/client_config.rs b/core/chain-configs/src/client_config.rs index b6a0d10cba0..ab5eb2c998b 100644 --- a/core/chain-configs/src/client_config.rs +++ b/core/chain-configs/src/client_config.rs @@ -2,11 +2,11 @@ use crate::ExternalStorageLocation::GCS; use crate::MutableConfigValue; use bytesize::ByteSize; -use near_async::time::Duration; use near_primitives::types::{ AccountId, BlockHeight, BlockHeightDelta, Gas, NumBlocks, NumSeats, ShardId, }; use near_primitives::version::Version; +use near_time::Duration; use std::cmp::{max, min}; use std::path::PathBuf; use std::sync::atomic::AtomicBool; @@ -48,7 +48,7 @@ pub struct GCConfig { pub gc_num_epochs_to_keep: u64, /// How often gc should be run - #[serde(with = "near_async::time::serde_duration_as_std")] + #[serde(with = "near_time::serde_duration_as_std")] pub gc_step_period: Duration, } @@ -120,7 +120,7 @@ pub struct DumpConfig { /// Feel free to set to `None`, defaults are sensible. #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] - #[serde(with = "near_async::time::serde_opt_duration_as_std")] + #[serde(with = "near_time::serde_opt_duration_as_std")] pub iteration_delay: Option, /// Location of a json file with credentials allowing write access to the bucket. #[serde(skip_serializing_if = "Option::is_none")] @@ -192,23 +192,23 @@ pub struct ReshardingConfig { /// The delay between writing batches to the db. The batch delay can be /// increased if resharding is consuming too many resources and interfering /// with regular node operation. - #[serde(with = "near_async::time::serde_duration_as_std")] + #[serde(with = "near_time::serde_duration_as_std")] pub batch_delay: Duration, /// The delay between attempts to start resharding while waiting for the /// state snapshot to become available. - #[serde(with = "near_async::time::serde_duration_as_std")] + #[serde(with = "near_time::serde_duration_as_std")] pub retry_delay: Duration, /// The delay between the resharding request is received and when the actor /// actually starts working on it. This delay should only be used in tests. - #[serde(with = "near_async::time::serde_duration_as_std")] + #[serde(with = "near_time::serde_duration_as_std")] pub initial_delay: Duration, /// The maximum time that the actor will wait for the snapshot to be ready, /// before starting resharding. Do not wait indefinitely since we want to /// report error early enough for the node maintainer to have time to recover. - #[serde(with = "near_async::time::serde_duration_as_std")] + #[serde(with = "near_time::serde_duration_as_std")] pub max_poll_time: Duration, } @@ -410,6 +410,8 @@ pub struct ClientConfig { pub gc: GCConfig, /// Accounts that this client tracks. pub tracked_accounts: Vec, + /// Track shards that should be tracked by given validator. + pub tracked_shadow_validator: Option, /// Shards that this client tracks. pub tracked_shards: Vec, /// Rotate between these sets of tracked shards. @@ -479,7 +481,7 @@ pub struct ClientConfig { pub orphan_state_witness_max_size: ByteSize, /// Save observed instances of ChunkStateWitness to the database in DBCol::LatestChunkStateWitnesses. /// Saving the latest witnesses is useful for analysis and debugging. - /// When this option is enabled, the node will save ALL witnesses it oberves, even invalid ones, + /// When this option is enabled, the node will save ALL witnesses it observes, even invalid ones, /// which can cause extra load on the database. This option is not recommended for production use, /// as a large number of incoming witnesses could cause denial of service. pub save_latest_witnesses: bool, @@ -539,6 +541,7 @@ impl ClientConfig { block_header_fetch_horizon: 50, gc: GCConfig { gc_blocks_limit: 100, ..GCConfig::default() }, tracked_accounts: vec![], + tracked_shadow_validator: None, tracked_shards: vec![], tracked_shard_schedule: vec![], archive, diff --git a/core/chain-configs/src/genesis_config.rs b/core/chain-configs/src/genesis_config.rs index 25ed36a520a..810de152981 100644 --- a/core/chain-configs/src/genesis_config.rs +++ b/core/chain-configs/src/genesis_config.rs @@ -44,6 +44,14 @@ fn default_online_max_threshold() -> Rational32 { Rational32::new(99, 100) } +fn default_chunk_validator_only_kickout_threshold() -> u8 { + 80 +} + +fn default_target_validator_mandates_per_shard() -> NumSeats { + 68 +} + fn default_minimum_stake_divisor() -> u64 { 10 } @@ -92,12 +100,17 @@ fn default_max_kickout_stake_threshold() -> u8 { 100 } +fn default_genesis_time() -> DateTime { + let time = near_time::Utc::now_utc(); + DateTime::from_timestamp(time.unix_timestamp(), time.nanosecond()).unwrap_or_default() +} + #[derive(Debug, Clone, SmartDefault, serde::Serialize, serde::Deserialize)] pub struct GenesisConfig { /// Protocol version that this genesis works with. pub protocol_version: ProtocolVersion, /// Official time of blockchain start. - #[default(Utc::now())] + #[default(default_genesis_time())] pub genesis_time: DateTime, /// ID of the blockchain. This must be unique for every blockchain. /// If your testnet blockchains do not have unique chain IDs, you will have a bad time. @@ -128,10 +141,17 @@ pub struct GenesisConfig { #[serde(with = "dec_format")] #[default(MAX_GAS_PRICE)] pub max_gas_price: Balance, - /// Criterion for kicking out block producers (this is a number between 0 and 100) + /// Threshold for kicking out block producers, between 0 and 100. pub block_producer_kickout_threshold: u8, - /// Criterion for kicking out chunk producers (this is a number between 0 and 100) + /// Threshold for kicking out chunk producers, between 0 and 100. pub chunk_producer_kickout_threshold: u8, + /// Threshold for kicking out nodes which are only chunk validators, between 0 and 100. + #[serde(default = "default_chunk_validator_only_kickout_threshold")] + pub chunk_validator_only_kickout_threshold: u8, + /// Number of target chunk validator mandates for each shard. + #[serde(default = "default_target_validator_mandates_per_shard")] + #[default(68)] + pub target_validator_mandates_per_shard: NumSeats, /// Online minimum threshold below which validator doesn't receive reward. #[serde(default = "default_online_min_threshold")] #[default(Rational32::new(90, 100))] @@ -237,6 +257,8 @@ impl From<&GenesisConfig> for EpochConfig { .clone(), block_producer_kickout_threshold: config.block_producer_kickout_threshold, chunk_producer_kickout_threshold: config.chunk_producer_kickout_threshold, + chunk_validator_only_kickout_threshold: config.chunk_validator_only_kickout_threshold, + target_validator_mandates_per_shard: config.target_validator_mandates_per_shard, fishermen_threshold: config.fishermen_threshold, online_min_threshold: config.online_min_threshold, online_max_threshold: config.online_max_threshold, @@ -791,10 +813,14 @@ pub struct ProtocolConfigView { /// Maximum gas price. #[serde(with = "dec_format")] pub max_gas_price: Balance, - /// Criterion for kicking out block producers (this is a number between 0 and 100) + /// Threshold for kicking out block producers, between 0 and 100. pub block_producer_kickout_threshold: u8, - /// Criterion for kicking out chunk producers (this is a number between 0 and 100) + /// Threshold for kicking out chunk producers, between 0 and 100. pub chunk_producer_kickout_threshold: u8, + /// Threshold for kicking out nodes which are only chunk validators, between 0 and 100. + pub chunk_validator_only_kickout_threshold: u8, + /// Number of target chunk validator mandates for each shard. + pub target_validator_mandates_per_shard: NumSeats, /// Online minimum threshold below which validator doesn't receive reward. pub online_min_threshold: Rational32, /// Online maximum threshold above which validator gets full reward. @@ -862,6 +888,9 @@ impl From for ProtocolConfigView { max_gas_price: genesis_config.max_gas_price, block_producer_kickout_threshold: genesis_config.block_producer_kickout_threshold, chunk_producer_kickout_threshold: genesis_config.chunk_producer_kickout_threshold, + chunk_validator_only_kickout_threshold: genesis_config + .chunk_validator_only_kickout_threshold, + target_validator_mandates_per_shard: genesis_config.target_validator_mandates_per_shard, online_min_threshold: genesis_config.online_min_threshold, online_max_threshold: genesis_config.online_max_threshold, gas_price_adjustment_rate: genesis_config.gas_price_adjustment_rate, @@ -1074,6 +1103,8 @@ mod test { "max_gas_price": "10000000000000000000000", "block_producer_kickout_threshold": 90, "chunk_producer_kickout_threshold": 90, + "chunk_validator_only_kickout_threshold": 80, + "target_validator_mandates_per_shard": 68, "online_min_threshold": [ 9, 10 @@ -1199,6 +1230,8 @@ mod test { "max_gas_price": "10000000000000000000000", "block_producer_kickout_threshold": 90, "chunk_producer_kickout_threshold": 90, + "chunk_validator_only_kickout_threshold": 80, + "target_validator_mandates_per_shard": 68, "online_min_threshold": [ 9, 10 diff --git a/core/chain-configs/src/lib.rs b/core/chain-configs/src/lib.rs index 07e594adf33..9565e53be2e 100644 --- a/core/chain-configs/src/lib.rs +++ b/core/chain-configs/src/lib.rs @@ -3,7 +3,9 @@ mod genesis_config; pub mod genesis_validate; #[cfg(feature = "metrics")] mod metrics; +#[cfg(feature = "test_genesis")] pub mod test_genesis; +#[cfg(feature = "test_utils")] pub mod test_utils; mod updateable_config; @@ -30,7 +32,7 @@ pub use genesis_config::{ }; use near_primitives::types::{Balance, BlockHeightDelta, Gas, NumBlocks, NumSeats}; use num_rational::Rational32; -pub use updateable_config::{MutableConfigValue, UpdateableClientConfig}; +pub use updateable_config::{MutableConfigValue, MutableValidatorSigner, UpdateableClientConfig}; pub const GENESIS_CONFIG_FILENAME: &str = "genesis.json"; @@ -52,6 +54,9 @@ pub const BLOCK_PRODUCER_KICKOUT_THRESHOLD: u8 = 90; /// Criterion for kicking out chunk producers. pub const CHUNK_PRODUCER_KICKOUT_THRESHOLD: u8 = 90; +/// Criterion for kicking out chunk validators. +pub const CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD: u8 = 80; + /// Fishermen stake threshold. pub const FISHERMEN_THRESHOLD: Balance = 10 * NEAR_BASE; diff --git a/core/chain-configs/src/test_genesis.rs b/core/chain-configs/src/test_genesis.rs index 9ae7a80cc2d..65856252ea5 100644 --- a/core/chain-configs/src/test_genesis.rs +++ b/core/chain-configs/src/test_genesis.rs @@ -1,7 +1,6 @@ use std::collections::{HashMap, HashSet}; -use near_async::time::Clock; -use near_crypto::{PublicKey, Signer}; +use near_crypto::PublicKey; use near_primitives::account::{AccessKey, Account}; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardLayout; @@ -13,6 +12,7 @@ use near_primitives::types::{ }; use near_primitives::utils::from_timestamp; use near_primitives::version::PROTOCOL_VERSION; +use near_time::Clock; use num_rational::Rational32; use crate::{Genesis, GenesisConfig, GenesisContents, GenesisRecords}; @@ -41,6 +41,7 @@ pub struct TestGenesisBuilder { transaction_validity_period: Option, validators: Option, minimum_validators_per_shard: Option, + target_validator_mandates_per_shard: Option, protocol_treasury_account: Option, shuffle_shard_assignment_for_chunk_producers: Option, kickouts_config: Option, @@ -50,13 +51,12 @@ pub struct TestGenesisBuilder { #[derive(Debug, Clone)] enum ValidatorsSpec { DesiredRoles { - block_producers: Vec, - chunk_only_producers: Vec, + block_and_chunk_producers: Vec, + chunk_validators_only: Vec, }, Raw { validators: Vec, num_block_producer_seats: NumSeats, - num_chunk_only_producer_seats: NumSeats, num_chunk_producer_seats: NumSeats, num_chunk_validator_seats: NumSeats, }, @@ -66,6 +66,7 @@ enum ValidatorsSpec { struct KickoutsConfig { block_producer_kickout_threshold: u8, chunk_producer_kickout_threshold: u8, + chunk_validator_only_kickout_threshold: u8, } #[derive(Debug, Clone)] @@ -167,12 +168,15 @@ impl TestGenesisBuilder { /// validators are selected as specified. pub fn validators_desired_roles( &mut self, - block_producers: &[&str], - chunk_only_producers: &[&str], + block_and_chunk_producers: &[&str], + chunk_validators_only: &[&str], ) -> &mut Self { self.validators = Some(ValidatorsSpec::DesiredRoles { - block_producers: block_producers.iter().map(|s| s.to_string()).collect(), - chunk_only_producers: chunk_only_producers.iter().map(|s| s.to_string()).collect(), + block_and_chunk_producers: block_and_chunk_producers + .iter() + .map(|s| s.to_string()) + .collect(), + chunk_validators_only: chunk_validators_only.iter().map(|s| s.to_string()).collect(), }); self } @@ -183,15 +187,15 @@ impl TestGenesisBuilder { pub fn validators_raw( &mut self, validators: Vec, - num_block_producer_seats: NumSeats, - num_chunk_only_producer_seats: NumSeats, + num_block_and_chunk_producer_seats: NumSeats, + num_chunk_validator_only_seats: NumSeats, ) -> &mut Self { self.validators = Some(ValidatorsSpec::Raw { validators, - num_block_producer_seats, - num_chunk_only_producer_seats, - num_chunk_producer_seats: num_block_producer_seats, - num_chunk_validator_seats: num_block_producer_seats + num_chunk_only_producer_seats, + num_block_producer_seats: num_block_and_chunk_producer_seats, + num_chunk_producer_seats: num_block_and_chunk_producer_seats, + num_chunk_validator_seats: num_block_and_chunk_producer_seats + + num_chunk_validator_only_seats, }); self } @@ -204,6 +208,14 @@ impl TestGenesisBuilder { self } + pub fn target_validator_mandates_per_shard( + &mut self, + target_validator_mandates_per_shard: NumSeats, + ) -> &mut Self { + self.target_validator_mandates_per_shard = Some(target_validator_mandates_per_shard); + self + } + /// Specifies the protocol treasury account. If not specified, this will /// pick an arbitrary account name and ensure that it is included in the /// genesis records. @@ -221,26 +233,28 @@ impl TestGenesisBuilder { self.kickouts_config = Some(KickoutsConfig { block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, }); self } - pub fn kickouts_standard_90_percent(&mut self) -> &mut Self { + /// Validators with performance below 80% are kicked out, similarly to + /// mainnet as of 28 Jun 2024. + pub fn kickouts_standard_80_percent(&mut self) -> &mut Self { self.kickouts_config = Some(KickoutsConfig { - block_producer_kickout_threshold: 90, - chunk_producer_kickout_threshold: 90, + block_producer_kickout_threshold: 80, + chunk_producer_kickout_threshold: 80, + chunk_validator_only_kickout_threshold: 80, }); self } - pub fn kickouts( - &mut self, - block_producer_kickout_threshold: u8, - chunk_producer_kickout_threshold: u8, - ) -> &mut Self { + /// Only chunk validator-only nodes can be kicked out. + pub fn kickouts_for_chunk_validators_only(&mut self) -> &mut Self { self.kickouts_config = Some(KickoutsConfig { - block_producer_kickout_threshold, - chunk_producer_kickout_threshold, + block_producer_kickout_threshold: 0, + chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 50, }); self } @@ -316,8 +330,8 @@ impl TestGenesisBuilder { }); let validator_specs = self.validators.clone().unwrap_or_else(|| { let default = ValidatorsSpec::DesiredRoles { - block_producers: vec!["validator0".to_string()], - chunk_only_producers: vec![], + block_and_chunk_producers: vec!["validator0".to_string()], + chunk_validators_only: vec![], }; tracing::warn!( "Genesis validators not explicitly set, defaulting to a single validator setup {:?}.", @@ -334,6 +348,15 @@ impl TestGenesisBuilder { ); default }); + let target_validator_mandates_per_shard = + self.target_validator_mandates_per_shard.unwrap_or_else(|| { + let default = 68; + tracing::warn!( + "Genesis minimum_validators_per_shard not explicitly set, defaulting to {:?}.", + default + ); + default + }); let protocol_treasury_account: AccountId = self .protocol_treasury_account .clone() @@ -361,6 +384,7 @@ impl TestGenesisBuilder { let default = KickoutsConfig { block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, }; tracing::warn!( "Genesis kickouts_config not explicitly set, defaulting to disabling kickouts.", @@ -452,6 +476,9 @@ impl TestGenesisBuilder { fishermen_threshold: 0, block_producer_kickout_threshold: kickouts_config.block_producer_kickout_threshold, chunk_producer_kickout_threshold: kickouts_config.chunk_producer_kickout_threshold, + chunk_validator_only_kickout_threshold: kickouts_config + .chunk_validator_only_kickout_threshold, + target_validator_mandates_per_shard, transaction_validity_period, protocol_version, protocol_treasury_account, @@ -464,7 +491,7 @@ impl TestGenesisBuilder { max_kickout_stake_perc: 100, validators: derived_validator_setup.validators, num_block_producer_seats: derived_validator_setup.num_block_producer_seats, - num_chunk_only_producer_seats: derived_validator_setup.num_chunk_only_producer_seats, + num_chunk_only_producer_seats: 0, minimum_stake_ratio: derived_validator_setup.minimum_stake_ratio, minimum_validators_per_shard, minimum_stake_divisor: 10, @@ -493,7 +520,6 @@ impl TestGenesisBuilder { struct DerivedValidatorSetup { validators: Vec, num_block_producer_seats: NumSeats, - num_chunk_only_producer_seats: NumSeats, num_chunk_producer_seats: NumSeats, num_chunk_validator_seats: NumSeats, minimum_stake_ratio: Rational32, @@ -503,15 +529,15 @@ const ONE_NEAR: Balance = 1_000_000_000_000_000_000_000_000; fn derive_validator_setup(specs: ValidatorsSpec) -> DerivedValidatorSetup { match specs { - ValidatorsSpec::DesiredRoles { block_producers, chunk_only_producers } => { - let num_block_producer_seats = block_producers.len() as NumSeats; - let num_chunk_only_producer_seats = chunk_only_producers.len() as NumSeats; + ValidatorsSpec::DesiredRoles { block_and_chunk_producers, chunk_validators_only } => { + let num_block_and_chunk_producer_seats = block_and_chunk_producers.len() as NumSeats; + let num_chunk_validator_only_seats = chunk_validators_only.len() as NumSeats; // Set minimum stake ratio to zero; that way, we don't have to worry about // chunk producers not having enough stake to be selected as desired. let minimum_stake_ratio = Rational32::new(0, 1); let mut validators = Vec::new(); - for i in 0..num_block_producer_seats as usize { - let account_id: AccountId = block_producers[i].parse().unwrap(); + for i in 0..num_block_and_chunk_producer_seats as usize { + let account_id: AccountId = block_and_chunk_producers[i].parse().unwrap(); let account_info = AccountInfo { public_key: create_test_signer(account_id.as_str()).public_key(), account_id, @@ -519,34 +545,33 @@ fn derive_validator_setup(specs: ValidatorsSpec) -> DerivedValidatorSetup { }; validators.push(account_info); } - for i in 0..num_chunk_only_producer_seats as usize { - let account_id: AccountId = chunk_only_producers[i].parse().unwrap(); + for i in 0..num_chunk_validator_only_seats as usize { + let account_id: AccountId = chunk_validators_only[i].parse().unwrap(); let account_info = AccountInfo { public_key: create_test_signer(account_id.as_str()).public_key(), account_id, - amount: ONE_NEAR * (10000 - i as Balance - num_block_producer_seats as Balance), + amount: ONE_NEAR + * (10000 - i as Balance - num_block_and_chunk_producer_seats as Balance), }; validators.push(account_info); } DerivedValidatorSetup { validators, - num_block_producer_seats, - num_chunk_only_producer_seats, - num_chunk_producer_seats: num_block_producer_seats, - num_chunk_validator_seats: num_block_producer_seats + num_chunk_only_producer_seats, + num_block_producer_seats: num_block_and_chunk_producer_seats, + num_chunk_producer_seats: num_block_and_chunk_producer_seats, + num_chunk_validator_seats: num_block_and_chunk_producer_seats + + num_chunk_validator_only_seats, minimum_stake_ratio, } } ValidatorsSpec::Raw { validators, num_block_producer_seats, - num_chunk_only_producer_seats, num_chunk_producer_seats, num_chunk_validator_seats, } => DerivedValidatorSetup { validators, num_block_producer_seats, - num_chunk_only_producer_seats, num_chunk_producer_seats, num_chunk_validator_seats, minimum_stake_ratio: Rational32::new(160, 1000000), diff --git a/core/chain-configs/src/test_utils.rs b/core/chain-configs/src/test_utils.rs index cf1e0fa8f3e..8df1fd78eb6 100644 --- a/core/chain-configs/src/test_utils.rs +++ b/core/chain-configs/src/test_utils.rs @@ -1,4 +1,3 @@ -use near_async::time::Clock; use near_crypto::{InMemorySigner, KeyType, PublicKey}; use near_primitives::account::{AccessKey, Account}; use near_primitives::hash::CryptoHash; @@ -9,13 +8,15 @@ use near_primitives::types::{ }; use near_primitives::utils::{from_timestamp, generate_random_string}; use near_primitives::version::PROTOCOL_VERSION; +use near_time::Clock; use num_rational::Ratio; use crate::{ Genesis, GenesisConfig, BLOCK_PRODUCER_KICKOUT_THRESHOLD, CHUNK_PRODUCER_KICKOUT_THRESHOLD, - FISHERMEN_THRESHOLD, GAS_PRICE_ADJUSTMENT_RATE, INITIAL_GAS_LIMIT, MAX_INFLATION_RATE, - MIN_GAS_PRICE, NEAR_BASE, NUM_BLOCKS_PER_YEAR, PROTOCOL_REWARD_RATE, PROTOCOL_TREASURY_ACCOUNT, - PROTOCOL_UPGRADE_STAKE_THRESHOLD, TRANSACTION_VALIDITY_PERIOD, + CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD, FISHERMEN_THRESHOLD, GAS_PRICE_ADJUSTMENT_RATE, + INITIAL_GAS_LIMIT, MAX_INFLATION_RATE, MIN_GAS_PRICE, NEAR_BASE, NUM_BLOCKS_PER_YEAR, + PROTOCOL_REWARD_RATE, PROTOCOL_TREASURY_ACCOUNT, PROTOCOL_UPGRADE_STAKE_THRESHOLD, + TRANSACTION_VALIDITY_PERIOD, }; /// Initial balance used in tests. @@ -90,6 +91,8 @@ impl Genesis { gas_limit: INITIAL_GAS_LIMIT, gas_price_adjustment_rate: GAS_PRICE_ADJUSTMENT_RATE, block_producer_kickout_threshold: BLOCK_PRODUCER_KICKOUT_THRESHOLD, + chunk_producer_kickout_threshold: CHUNK_PRODUCER_KICKOUT_THRESHOLD, + chunk_validator_only_kickout_threshold: CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD, validators, protocol_reward_rate: PROTOCOL_REWARD_RATE, total_supply: get_initial_supply(&records), @@ -97,7 +100,6 @@ impl Genesis { num_blocks_per_year: NUM_BLOCKS_PER_YEAR, protocol_treasury_account: PROTOCOL_TREASURY_ACCOUNT.parse().unwrap(), transaction_validity_period: TRANSACTION_VALIDITY_PERIOD, - chunk_producer_kickout_threshold: CHUNK_PRODUCER_KICKOUT_THRESHOLD, fishermen_threshold: FISHERMEN_THRESHOLD, min_gas_price: MIN_GAS_PRICE, shard_layout, diff --git a/core/chain-configs/src/updateable_config.rs b/core/chain-configs/src/updateable_config.rs index cd8367a6eaa..ebace65d3ec 100644 --- a/core/chain-configs/src/updateable_config.rs +++ b/core/chain-configs/src/updateable_config.rs @@ -1,9 +1,13 @@ -use near_async::time::Clock; use near_primitives::types::BlockHeight; +use near_primitives::validator_signer::ValidatorSigner; +#[cfg(feature = "metrics")] +use near_time::Clock; use serde::{Deserialize, Serialize, Serializer}; use std::fmt::Debug; use std::sync::{Arc, Mutex}; -use time::{Duration, OffsetDateTime as Utc}; +use time::Duration; +#[cfg(feature = "metrics")] +use time::OffsetDateTime as Utc; use crate::ReshardingConfig; @@ -37,12 +41,12 @@ impl Serialize for MutableConfigValue { } } -impl MutableConfigValue { +impl MutableConfigValue { /// Initializes a value. /// `field_name` is needed to export the config value as a prometheus metric. pub fn new(val: T, field_name: &str) -> Self { let res = Self { - value: Arc::new(Mutex::new(val)), + value: Arc::new(Mutex::new(val.clone())), field_name: field_name.to_string(), #[cfg(feature = "metrics")] last_update: Clock::real().now_utc(), @@ -52,18 +56,21 @@ impl MutableConfigValue { } pub fn get(&self) -> T { - *self.value.lock().unwrap() + self.value.lock().unwrap().clone() } - pub fn update(&self, val: T) { + /// Attempts to update the value and returns whether the value changed. + pub fn update(&self, val: T) -> bool { let mut lock = self.value.lock().unwrap(); if *lock != val { tracing::info!(target: "config", "Updated config field '{}' from {:?} to {:?}", self.field_name, *lock, val); - self.set_metric_value(*lock, 0); - *lock = val; + self.set_metric_value(lock.clone(), 0); + *lock = val.clone(); self.set_metric_value(val, 1); + true } else { tracing::info!(target: "config", "Mutable config field '{}' remains the same: {:?}", self.field_name, val); + false } } @@ -99,6 +106,8 @@ pub struct UpdateableClientConfig { /// Time limit for adding transactions in produce_chunk() #[serde(default)] - #[serde(with = "near_async::time::serde_opt_duration_as_std")] + #[serde(with = "near_time::serde_opt_duration_as_std")] pub produce_chunk_add_transactions_time_limit: Option, } + +pub type MutableValidatorSigner = MutableConfigValue>>; diff --git a/core/crypto/Cargo.toml b/core/crypto/Cargo.toml index f75b5658f43..9b80ff62419 100644 --- a/core/crypto/Cargo.toml +++ b/core/crypto/Cargo.toml @@ -15,14 +15,17 @@ workspace = true blake2.workspace = true borsh.workspace = true bs58.workspace = true -curve25519-dalek.workspace = true +curve25519-dalek = { workspace = true, features = [ + "precomputed-tables", + "alloc", +] } derive_more.workspace = true -ed25519-dalek.workspace = true +ed25519-dalek = { workspace = true, features = ["hazmat"] } hex.workspace = true near-account-id.workspace = true once_cell.workspace = true primitive-types.workspace = true -secp256k1.workspace = true +secp256k1 = { workspace = true, features = ["recovery", "alloc"] } serde.workspace = true serde_json.workspace = true stdx.workspace = true @@ -35,3 +38,8 @@ bolero.workspace = true hex-literal.workspace = true sha2.workspace = true tempfile.workspace = true +curve25519-dalek = { workspace = true, features = ["rand_core"] } + +[features] +default = ["rand"] +rand = ["secp256k1/rand", "ed25519-dalek/rand_core"] diff --git a/core/crypto/src/hash.rs b/core/crypto/src/hash.rs index 13f15b0cea6..200259b3420 100644 --- a/core/crypto/src/hash.rs +++ b/core/crypto/src/hash.rs @@ -1,42 +1,32 @@ use crate::util::{Packable, Point, Scalar}; use blake2::digest::generic_array::{typenum::U32, GenericArray}; -use blake2::digest::{BlockInput, FixedOutput, Reset, Update, VariableOutput}; -use blake2::VarBlake2b; +use blake2::digest::{FixedOutput, OutputSizeUser, Reset, Update, VariableOutput}; +use blake2::Blake2bVar; -pub use blake2::Blake2b as Hash512; +pub use blake2::Blake2b512 as Hash512; #[derive(Clone)] -pub struct Hash256(VarBlake2b); +pub struct Hash256(Blake2bVar); impl Default for Hash256 { fn default() -> Self { - Hash256(VarBlake2b::new(32).unwrap()) + Hash256(Blake2bVar::new(32).unwrap()) } } impl Update for Hash256 { - fn update(&mut self, data: impl AsRef<[u8]>) { + fn update(&mut self, data: &[u8]) { self.0.update(data); } } -impl BlockInput for Hash256 { - type BlockSize = ::BlockSize; +impl OutputSizeUser for Hash256 { + type OutputSize = U32; } impl FixedOutput for Hash256 { - type OutputSize = U32; - fn finalize_into(self, out: &mut GenericArray) { - self.0.finalize_variable(|s| { - out.copy_from_slice(&s[0..32]); - }); - } - - fn finalize_into_reset(&mut self, out: &mut GenericArray) { - self.0.finalize_variable_reset(|s| { - out.copy_from_slice(&s[0..32]); - }); + self.0.finalize_variable(out).expect("hash output size is correct") } } diff --git a/core/crypto/src/signature.rs b/core/crypto/src/signature.rs index 2693e1709a9..79c0685cf45 100644 --- a/core/crypto/src/signature.rs +++ b/core/crypto/src/signature.rs @@ -2,7 +2,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use ed25519_dalek::ed25519::signature::{Signer, Verifier}; use once_cell::sync::Lazy; use primitive_types::U256; -use secp256k1::rand::rngs::OsRng; use secp256k1::Message; use std::convert::AsRef; use std::fmt::{Debug, Display, Formatter}; @@ -312,7 +311,10 @@ impl SecretKey { } } + #[cfg(feature = "rand")] pub fn from_random(key_type: KeyType) -> SecretKey { + use secp256k1::rand::rngs::OsRng; + match key_type { KeyType::ED25519 => { let keypair = ed25519_dalek::SigningKey::generate(&mut OsRng); diff --git a/core/crypto/src/signer.rs b/core/crypto/src/signer.rs index 25175594aa3..96bf45854a6 100644 --- a/core/crypto/src/signer.rs +++ b/core/crypto/src/signer.rs @@ -2,41 +2,83 @@ use crate::key_conversion::convert_secret_key; use crate::key_file::KeyFile; use crate::{KeyType, PublicKey, SecretKey, Signature}; use near_account_id::AccountId; +use std::fmt::{self, Debug}; use std::io; use std::path::Path; use std::sync::Arc; -/// Generic signer trait, that can sign with some subset of supported curves. -pub trait Signer: Sync + Send { - fn public_key(&self) -> PublicKey; - fn sign(&self, data: &[u8]) -> Signature; +/// Enum for Signer, that can sign with some subset of supported curves. +#[derive(Debug, PartialEq)] +pub enum Signer { + /// Dummy signer, does not hold a key. Use for tests only! + Empty(EmptySigner), + /// Default signer that holds data in memory. + InMemory(InMemorySigner), +} + +/// Enum for Signer, that can sign with some subset of supported curves. +impl Signer { + pub fn public_key(&self) -> PublicKey { + match self { + Signer::Empty(signer) => signer.public_key(), + Signer::InMemory(signer) => signer.public_key(), + } + } + + pub fn sign(&self, data: &[u8]) -> Signature { + match self { + Signer::Empty(signer) => signer.sign(data), + Signer::InMemory(signer) => signer.sign(data), + } + } - fn verify(&self, data: &[u8], signature: &Signature) -> bool { + pub fn verify(&self, data: &[u8], signature: &Signature) -> bool { signature.verify(data, &self.public_key()) } - fn compute_vrf_with_proof(&self, _data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof); + pub fn compute_vrf_with_proof(&self, data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof) { + match self { + Signer::Empty(_) => unimplemented!(), + Signer::InMemory(signer) => signer.compute_vrf_with_proof(data), + } + } /// Used by test infrastructure, only implement if make sense for testing otherwise raise `unimplemented`. - fn write_to_file(&self, _path: &Path) -> io::Result<()> { - unimplemented!(); + pub fn write_to_file(&self, path: &Path) -> io::Result<()> { + match self { + Signer::Empty(_) => unimplemented!(), + Signer::InMemory(signer) => signer.write_to_file(path), + } + } +} + +impl From for Signer { + fn from(signer: EmptySigner) -> Self { + Signer::Empty(signer) + } +} + +impl From for Signer { + fn from(signer: InMemorySigner) -> Self { + Signer::InMemory(signer) } } // Signer that returns empty signature. Used for transaction testing. +#[derive(Debug, PartialEq)] pub struct EmptySigner {} -impl Signer for EmptySigner { - fn public_key(&self) -> PublicKey { - PublicKey::empty(KeyType::ED25519) +impl EmptySigner { + pub fn new() -> Self { + Self {} } - fn sign(&self, _data: &[u8]) -> Signature { - Signature::empty(KeyType::ED25519) + pub fn public_key(&self) -> PublicKey { + PublicKey::empty(KeyType::ED25519) } - fn compute_vrf_with_proof(&self, _data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof) { - unimplemented!() + pub fn sign(&self, _data: &[u8]) -> Signature { + Signature::empty(KeyType::ED25519) } } @@ -49,6 +91,7 @@ pub struct InMemorySigner { } impl InMemorySigner { + #[cfg(feature = "rand")] pub fn from_seed(account_id: AccountId, key_type: KeyType, seed: &str) -> Self { let secret_key = SecretKey::from_seed(key_type, seed); Self { account_id, public_key: secret_key.public_key(), secret_key } @@ -61,27 +104,35 @@ impl InMemorySigner { pub fn from_file(path: &Path) -> io::Result { KeyFile::from_file(path).map(Self::from) } -} -impl Signer for InMemorySigner { - fn public_key(&self) -> PublicKey { + pub fn public_key(&self) -> PublicKey { self.public_key.clone() } - fn sign(&self, data: &[u8]) -> Signature { + pub fn sign(&self, data: &[u8]) -> Signature { self.secret_key.sign(data) } - fn compute_vrf_with_proof(&self, data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof) { + pub fn compute_vrf_with_proof(&self, data: &[u8]) -> (crate::vrf::Value, crate::vrf::Proof) { let secret_key = convert_secret_key(self.secret_key.unwrap_as_ed25519()); secret_key.compute_vrf_with_proof(&data) } - fn write_to_file(&self, path: &Path) -> io::Result<()> { + pub fn write_to_file(&self, path: &Path) -> io::Result<()> { KeyFile::from(self).write_to_file(path) } } +impl fmt::Debug for InMemorySigner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "InMemorySigner(account_id: {}, public_key: {})", + self.account_id, self.public_key + ) + } +} + impl From for InMemorySigner { fn from(key_file: KeyFile) -> Self { Self { diff --git a/core/crypto/src/test_utils.rs b/core/crypto/src/test_utils.rs index 174b58b23d4..856b992f90d 100644 --- a/core/crypto/src/test_utils.rs +++ b/core/crypto/src/test_utils.rs @@ -1,9 +1,7 @@ -use secp256k1::rand::SeedableRng; - -use crate::signature::{ED25519PublicKey, ED25519SecretKey, KeyType, PublicKey, SecretKey}; +use crate::signature::{KeyType, PublicKey, SecretKey}; use crate::{InMemorySigner, Signature}; -use near_account_id::AccountId; +#[cfg(feature = "rand")] fn ed25519_key_pair_from_seed(seed: &str) -> ed25519_dalek::SigningKey { let seed_bytes = seed.as_bytes(); let len = std::cmp::min(ed25519_dalek::SECRET_KEY_LENGTH, seed_bytes.len()); @@ -12,7 +10,10 @@ fn ed25519_key_pair_from_seed(seed: &str) -> ed25519_dalek::SigningKey { ed25519_dalek::SigningKey::from_bytes(&seed) } +#[cfg(feature = "rand")] fn secp256k1_secret_key_from_seed(seed: &str) -> secp256k1::SecretKey { + use secp256k1::rand::SeedableRng; + let seed_bytes = seed.as_bytes(); let len = std::cmp::min(32, seed_bytes.len()); let mut seed: [u8; 32] = [b' '; 32]; @@ -22,11 +23,14 @@ fn secp256k1_secret_key_from_seed(seed: &str) -> secp256k1::SecretKey { } impl PublicKey { + #[cfg(feature = "rand")] pub fn from_seed(key_type: KeyType, seed: &str) -> Self { match key_type { KeyType::ED25519 => { let keypair = ed25519_key_pair_from_seed(seed); - PublicKey::ED25519(ED25519PublicKey(keypair.verifying_key().to_bytes())) + PublicKey::ED25519(crate::signature::ED25519PublicKey( + keypair.verifying_key().to_bytes(), + )) } KeyType::SECP256K1 => { let secret_key = SecretKey::SECP256K1(secp256k1_secret_key_from_seed(seed)); @@ -37,11 +41,12 @@ impl PublicKey { } impl SecretKey { + #[cfg(feature = "rand")] pub fn from_seed(key_type: KeyType, seed: &str) -> Self { match key_type { KeyType::ED25519 => { let keypair = ed25519_key_pair_from_seed(seed); - SecretKey::ED25519(ED25519SecretKey(keypair.to_keypair_bytes())) + SecretKey::ED25519(crate::signature::ED25519SecretKey(keypair.to_keypair_bytes())) } KeyType::SECP256K1 => SecretKey::SECP256K1(secp256k1_secret_key_from_seed(seed)), } @@ -61,7 +66,8 @@ impl Signature { } impl InMemorySigner { - pub fn from_random(account_id: AccountId, key_type: KeyType) -> Self { + #[cfg(feature = "rand")] + pub fn from_random(account_id: near_account_id::AccountId, key_type: KeyType) -> Self { let secret_key = SecretKey::from_random(key_type); Self { account_id, public_key: secret_key.public_key(), secret_key } } diff --git a/core/dyn-configs/Cargo.toml b/core/dyn-configs/Cargo.toml index c2b311b0964..ffdbff591e6 100644 --- a/core/dyn-configs/Cargo.toml +++ b/core/dyn-configs/Cargo.toml @@ -21,21 +21,20 @@ thiserror.workspace = true tokio.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-chain-configs.workspace = true +near-crypto.workspace = true near-o11y.workspace = true near-primitives.workspace = true [features] nightly = [ - "near-async/nightly", "near-chain-configs/nightly", "near-o11y/nightly", "near-primitives/nightly", "nightly_protocol", ] nightly_protocol = [ - "near-async/nightly_protocol", "near-chain-configs/nightly_protocol", "near-o11y/nightly_protocol", "near-primitives/nightly_protocol", diff --git a/core/dyn-configs/src/lib.rs b/core/dyn-configs/src/lib.rs index 17330e21676..320e44dec8c 100644 --- a/core/dyn-configs/src/lib.rs +++ b/core/dyn-configs/src/lib.rs @@ -1,22 +1,24 @@ #![doc = include_str!("../README.md")] -use near_async::time::Clock; use near_chain_configs::UpdateableClientConfig; use near_o11y::log_config::LogConfig; -use serde::{Deserialize, Serialize}; +use near_primitives::validator_signer::ValidatorSigner; +use near_time::Clock; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::broadcast::Sender; mod metrics; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Clone, Default)] /// Contains the latest state of configs which can be updated at runtime. pub struct UpdateableConfigs { /// Contents of the file LOG_CONFIG_FILENAME. pub log_config: Option, /// Contents of the `config.json` corresponding to the mutable fields of `ClientConfig`. pub client_config: Option, + /// Validator key hot loaded from file. + pub validator_signer: Option>, } /// Pushes the updates to listeners. @@ -35,6 +37,8 @@ pub enum UpdateableConfigLoaderError { OpenAndRead { file: PathBuf, err: std::io::Error }, #[error("Can't open or read the config file {file:?}: {err:?}")] ConfigFileError { file: PathBuf, err: anyhow::Error }, + #[error("Can't open or read the validator key file {file:?}: {err:?}")] + ValidatorKeyFileError { file: PathBuf, err: anyhow::Error }, #[error("One or multiple dynamic config files reload errors {0:?}")] Errors(Vec), #[error("No home dir set")] diff --git a/core/o11y/Cargo.toml b/core/o11y/Cargo.toml index 2bbcc417a59..c5027af61dc 100644 --- a/core/o11y/Cargo.toml +++ b/core/o11y/Cargo.toml @@ -51,6 +51,7 @@ nightly = [ "nightly_protocol", ] io_trace = [ "near-fmt", "strum" ] +sandbox = [] [[bench]] name = "metrics" diff --git a/core/parameters/Cargo.toml b/core/parameters/Cargo.toml index 62bc4f4e25c..1c8ab735150 100644 --- a/core/parameters/Cargo.toml +++ b/core/parameters/Cargo.toml @@ -35,6 +35,7 @@ insta.workspace = true nightly = [ "near-primitives-core/nightly", "nightly_protocol", + "protocol_feature_bls12381", ] nightly_protocol = [ "near-primitives-core/nightly_protocol", @@ -43,3 +44,4 @@ statelessnet_protocol = [ "near-primitives-core/statelessnet_protocol", ] calimero_zero_storage = [] +protocol_feature_bls12381 = [] diff --git a/core/parameters/res/runtime_configs/141.yaml b/core/parameters/res/runtime_configs/141.yaml new file mode 100644 index 00000000000..caec7d37418 --- /dev/null +++ b/core/parameters/res/runtime_configs/141.yaml @@ -0,0 +1,18 @@ +wasm_bls12381_p1_sum_base: { old: 300_000_000_000_000, new: 16_500_000_000 } +wasm_bls12381_p1_sum_element: { old: 300_000_000_000_000, new: 6_000_000_000 } +wasm_bls12381_p2_sum_base: { old: 300_000_000_000_000, new: 18_600_000_000 } +wasm_bls12381_p2_sum_element: { old: 300_000_000_000_000, new: 15_000_000_000 } +wasm_bls12381_g1_multiexp_base: { old: 300_000_000_000_000, new: 16_500_000_000 } +wasm_bls12381_g1_multiexp_element: { old: 300_000_000_000_000, new: 930_000_000_000 } +wasm_bls12381_g2_multiexp_base: { old: 300_000_000_000_000, new: 18_600_000_000 } +wasm_bls12381_g2_multiexp_element: { old: 300_000_000_000_000, new: 1_995_000_000_000 } +wasm_bls12381_map_fp_to_g1_base: { old: 300_000_000_000_000, new: 1_500_000_000 } +wasm_bls12381_map_fp_to_g1_element: { old: 300_000_000_000_000, new: 252_000_000_000 } +wasm_bls12381_map_fp2_to_g2_base: { old: 300_000_000_000_000, new: 1_500_000_000 } +wasm_bls12381_map_fp2_to_g2_element: { old: 300_000_000_000_000, new: 900_000_000_000 } +wasm_bls12381_pairing_base: { old: 300_000_000_000_000, new: 2_130_000_000_000 } +wasm_bls12381_pairing_element: { old: 300_000_000_000_000, new: 2_130_000_000_000 } +wasm_bls12381_p1_decompress_base: { old: 300_000_000_000_000, new: 15_000_000_000 } +wasm_bls12381_p1_decompress_element: { old: 300_000_000_000_000, new: 81_000_000_000 } +wasm_bls12381_p2_decompress_base: { old: 300_000_000_000_000, new: 15_000_000_000 } +wasm_bls12381_p2_decompress_element: { old: 300_000_000_000_000, new: 165_000_000_000 } diff --git a/core/parameters/res/runtime_configs/143.yaml b/core/parameters/res/runtime_configs/143.yaml new file mode 100644 index 00000000000..f02081e50ae --- /dev/null +++ b/core/parameters/res/runtime_configs/143.yaml @@ -0,0 +1 @@ +discard_custom_sections: { old: false, new: true } diff --git a/core/parameters/res/runtime_configs/142.yaml b/core/parameters/res/runtime_configs/68.yaml similarity index 89% rename from core/parameters/res/runtime_configs/142.yaml rename to core/parameters/res/runtime_configs/68.yaml index a68da7226fa..8332af172cf 100644 --- a/core/parameters/res/runtime_configs/142.yaml +++ b/core/parameters/res/runtime_configs/68.yaml @@ -1,3 +1,5 @@ +# Congestion Control + # i64::MAX == 9_223_372_036_854_775_807 # PGAS == 1_000_000_000_000_000 GAS # TGAS == 1_000_000_000_000 GAS @@ -12,20 +14,20 @@ max_congestion_incoming_gas: { old : 9_223_372_036_854_775_807, new : 20_000_000_000_000_000, } -# 2 PGAS +# 10 PGAS max_congestion_outgoing_gas: { old : 9_223_372_036_854_775_807, - new : 2_000_000_000_000_000, + new : 10_000_000_000_000_000, } # 1000 MB max_congestion_memory_consumption: { old : 9_223_372_036_854_775_807, new : 1_000_000_000, } -# 10 missed chunks +# 5 missed chunks max_congestion_missed_chunks: { old : 9_223_372_036_854_775_807, - new : 10, + new : 5, } # 300 PGAS @@ -55,8 +57,8 @@ min_tx_gas: { new: 20_000_000_000_000 } -# 0.25 +# 0.5 reject_tx_congestion_threshold: { old : { numerator: 1, denominator: 1 }, - new : { numerator: 25, denominator: 100 } -} + new : { numerator: 50, denominator: 100 } +} \ No newline at end of file diff --git a/core/parameters/res/runtime_configs/69.yaml b/core/parameters/res/runtime_configs/69.yaml new file mode 100644 index 00000000000..16b426fd9b4 --- /dev/null +++ b/core/parameters/res/runtime_configs/69.yaml @@ -0,0 +1,73 @@ +# State Witness size limits. + +max_transaction_size: {old: 4_194_304, new: 1_572_864} + +per_receipt_storage_proof_size_limit: {old: 999_999_999_999_999, new: 4_000_000} +main_storage_proof_size_soft_limit: {old: 999_999_999_999_999, new: 3_000_000} + +max_receipt_size: {old: 999_999_999_999_999, new: 4_194_304} +new_transactions_validation_state_size_soft_limit: {old: 999_999_999_999_999, new: 572_864} + +# 100 kiB +outgoing_receipts_usual_size_limit: {old: 999_999_999_999_999, new: 102_400} + +# 4.5 MiB +outgoing_receipts_big_size_limit: {old: 999_999_999_999_999, new: 4_718_592} + +combined_transactions_size_limit: {old: 999_999_999_999_999, new: 4_194_304} + + +# Change the cost of sending receipt to another account to 50 TGas / MiB + +action_deploy_contract_per_byte: { + old: { + send_sir: 6_812_999, + send_not_sir: 6_812_999, + execution: 64_572_944, + }, + new: { + send_sir: 6_812_999, + send_not_sir: 47_683_715, + execution: 64_572_944, + } +} +action_function_call_per_byte: { + old: { + send_sir: 2_235_934, + send_not_sir: 2_235_934, + execution: 2_235_934, + }, + new: { + send_sir: 2_235_934, + send_not_sir: 47_683_715, + execution: 2_235_934, + } +} +action_add_function_call_key_per_byte: { + old: { + send_sir: 1_925_331, + send_not_sir: 1_925_331, + execution: 1_925_331, + }, + new: { + send_sir: 1_925_331, + send_not_sir: 47_683_715, + execution: 1_925_331, + } +} +data_receipt_creation_per_byte: { + old: { + send_sir: 17_212_011, + send_not_sir: 17_212_011, + execution: 17_212_011, + }, + new: { + send_sir: 17_212_011, + send_not_sir: 47_683_715, + execution: 17_212_011, + } +} +wasm_yield_resume_byte: { + old: 17_212_011, + new: 47_683_715 +} \ No newline at end of file diff --git a/core/parameters/res/runtime_configs/83.yaml b/core/parameters/res/runtime_configs/83.yaml deleted file mode 100644 index a38e7147055..00000000000 --- a/core/parameters/res/runtime_configs/83.yaml +++ /dev/null @@ -1 +0,0 @@ -main_storage_proof_size_soft_limit: {old: 999_999_999_999_999, new: 16_000_000} \ No newline at end of file diff --git a/core/parameters/res/runtime_configs/85.yaml b/core/parameters/res/runtime_configs/85.yaml deleted file mode 100644 index 62cf672bd54..00000000000 --- a/core/parameters/res/runtime_configs/85.yaml +++ /dev/null @@ -1,2 +0,0 @@ -per_receipt_storage_proof_size_limit: {old: 999_999_999_999_999, new: 4_000_000} -main_storage_proof_size_soft_limit: {old: 16_000_000, new: 3_000_000} \ No newline at end of file diff --git a/core/parameters/res/runtime_configs/87.yaml b/core/parameters/res/runtime_configs/87.yaml deleted file mode 100644 index 371da175240..00000000000 --- a/core/parameters/res/runtime_configs/87.yaml +++ /dev/null @@ -1,3 +0,0 @@ -max_transaction_size: {old: 4_194_304, new: 1_572_864} -combined_transactions_size_limit: {old: 999_999_999_999_999, new: 2_097_152} -new_transactions_validation_state_size_soft_limit: {old: 999_999_999_999_999, new: 572_864} diff --git a/core/parameters/res/runtime_configs/parameters.snap b/core/parameters/res/runtime_configs/parameters.snap index 22bf854c843..1c8f6676728 100644 --- a/core/parameters/res/runtime_configs/parameters.snap +++ b/core/parameters/res/runtime_configs/parameters.snap @@ -4,10 +4,12 @@ description: THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. --- burnt_gas_reward 3 / 10 pessimistic_gas_price_inflation 103 / 100 -main_storage_proof_size_soft_limit 999_999_999_999_999 -per_receipt_storage_proof_size_limit 999_999_999_999_999 -new_transactions_validation_state_size_soft_limit 999_999_999_999_999 -combined_transactions_size_limit 999_999_999_999_999 +main_storage_proof_size_soft_limit 3_000_000 +per_receipt_storage_proof_size_limit 4_000_000 +new_transactions_validation_state_size_soft_limit 572_864 +combined_transactions_size_limit 4_194_304 +outgoing_receipts_usual_size_limit 102_400 +outgoing_receipts_big_size_limit 4_718_592 min_allowed_top_level_account_length 65 registrar_account_id registrar storage_amount_per_byte 10000000000000000000 @@ -23,7 +25,7 @@ data_receipt_creation_base - execution: 36_486_732_312 data_receipt_creation_per_byte - send_sir: 17_212_011 -- send_not_sir: 17_212_011 +- send_not_sir: 47_683_715 - execution: 17_212_011 action_create_account - send_sir: 3_850_000_000_000 @@ -39,7 +41,7 @@ action_deploy_contract - execution: 184_765_750_000 action_deploy_contract_per_byte - send_sir: 6_812_999 -- send_not_sir: 6_812_999 +- send_not_sir: 47_683_715 - execution: 64_572_944 action_function_call - send_sir: 200_000_000_000 @@ -47,7 +49,7 @@ action_function_call - execution: 780_000_000_000 action_function_call_per_byte - send_sir: 2_235_934 -- send_not_sir: 2_235_934 +- send_not_sir: 47_683_715 - execution: 2_235_934 action_transfer - send_sir: 115_123_062_500 @@ -67,7 +69,7 @@ action_add_function_call_key - execution: 102_217_625_000 action_add_function_call_key_per_byte - send_sir: 1_925_331 -- send_not_sir: 1_925_331 +- send_not_sir: 47_683_715 - execution: 1_925_331 action_delete_key - send_sir: 94_946_625_000 @@ -143,7 +145,25 @@ wasm_alt_bn128_g1_sum_element 5_000_000_000 wasm_yield_create_base 153_411_779_276 wasm_yield_create_byte 15_643_988 wasm_yield_resume_base 1_195_627_285_210 -wasm_yield_resume_byte 17_212_011 +wasm_yield_resume_byte 47_683_715 +wasm_bls12381_p1_sum_base 300_000_000_000_000 +wasm_bls12381_p1_sum_element 300_000_000_000_000 +wasm_bls12381_p2_sum_base 300_000_000_000_000 +wasm_bls12381_p2_sum_element 300_000_000_000_000 +wasm_bls12381_g1_multiexp_base 300_000_000_000_000 +wasm_bls12381_g1_multiexp_element 300_000_000_000_000 +wasm_bls12381_g2_multiexp_base 300_000_000_000_000 +wasm_bls12381_g2_multiexp_element 300_000_000_000_000 +wasm_bls12381_map_fp_to_g1_base 300_000_000_000_000 +wasm_bls12381_map_fp_to_g1_element 300_000_000_000_000 +wasm_bls12381_map_fp2_to_g2_base 300_000_000_000_000 +wasm_bls12381_map_fp2_to_g2_element 300_000_000_000_000 +wasm_bls12381_pairing_base 300_000_000_000_000 +wasm_bls12381_pairing_element 300_000_000_000_000 +wasm_bls12381_p1_decompress_base 300_000_000_000_000 +wasm_bls12381_p1_decompress_element 300_000_000_000_000 +wasm_bls12381_p2_decompress_base 300_000_000_000_000 +wasm_bls12381_p2_decompress_element 300_000_000_000_000 max_gas_burnt 300_000_000_000_000 max_gas_burnt_view 300_000_000_000_000 max_stack_height 262_144 @@ -162,7 +182,8 @@ max_length_method_name 256 max_arguments_length 4_194_304 max_length_returned_data 4_194_304 max_contract_size 4_194_304 -max_transaction_size 4_194_304 +max_transaction_size 1_572_864 +max_receipt_size 4_194_304 max_length_storage_key 2_048 max_length_storage_value 4_194_304 max_promises_per_function_call_action 1_024 @@ -184,13 +205,14 @@ function_call_weight true vm_kind NearVm eth_implicit_accounts false yield_resume true -max_congestion_incoming_gas 9_223_372_036_854_775_807 -max_congestion_outgoing_gas 9_223_372_036_854_775_807 -max_congestion_memory_consumption 9_223_372_036_854_775_807 -max_congestion_missed_chunks 9_223_372_036_854_775_807 -max_outgoing_gas 9_223_372_036_854_775_807 -min_outgoing_gas 9_223_372_036_854_775_807 -allowed_shard_outgoing_gas 9_223_372_036_854_775_807 -max_tx_gas 9_223_372_036_854_775_807 -min_tx_gas 9_223_372_036_854_775_807 -reject_tx_congestion_threshold 1 / 1 +discard_custom_sections false +max_congestion_incoming_gas 20_000_000_000_000_000 +max_congestion_outgoing_gas 10_000_000_000_000_000 +max_congestion_memory_consumption 1_000_000_000 +max_congestion_missed_chunks 5 +max_outgoing_gas 300_000_000_000_000_000 +min_outgoing_gas 1_000_000_000_000_000 +allowed_shard_outgoing_gas 1_000_000_000_000_000 +max_tx_gas 500_000_000_000_000 +min_tx_gas 20_000_000_000_000 +reject_tx_congestion_threshold 50 / 100 diff --git a/core/parameters/res/runtime_configs/parameters.yaml b/core/parameters/res/runtime_configs/parameters.yaml index ecc9a76d3f1..0f005066eaf 100644 --- a/core/parameters/res/runtime_configs/parameters.yaml +++ b/core/parameters/res/runtime_configs/parameters.yaml @@ -17,6 +17,8 @@ main_storage_proof_size_soft_limit: 999_999_999_999_999 per_receipt_storage_proof_size_limit: 999_999_999_999_999 combined_transactions_size_limit: 999_999_999_999_999 new_transactions_validation_state_size_soft_limit: 999_999_999_999_999 +outgoing_receipts_usual_size_limit: 999_999_999_999_999 +outgoing_receipts_big_size_limit: 999_999_999_999_999 # Account creation config min_allowed_top_level_account_length: 32 @@ -176,6 +178,25 @@ wasm_alt_bn128_pairing_check_base: 9_686_000_000_000 wasm_alt_bn128_pairing_check_element: 5_102_000_000_000 wasm_alt_bn128_g1_sum_base: 3_000_000_000 wasm_alt_bn128_g1_sum_element: 5_000_000_000 +wasm_bls12381_p1_sum_base: 300_000_000_000_000 +wasm_bls12381_p1_sum_element: 300_000_000_000_000 +wasm_bls12381_p2_sum_base: 300_000_000_000_000 +wasm_bls12381_p2_sum_element: 300_000_000_000_000 +wasm_bls12381_g1_multiexp_base: 300_000_000_000_000 +wasm_bls12381_g1_multiexp_element: 300_000_000_000_000 +wasm_bls12381_g2_multiexp_base: 300_000_000_000_000 +wasm_bls12381_g2_multiexp_element: 300_000_000_000_000 +wasm_bls12381_map_fp_to_g1_base: 300_000_000_000_000 +wasm_bls12381_map_fp_to_g1_element: 300_000_000_000_000 +wasm_bls12381_map_fp2_to_g2_base: 300_000_000_000_000 +wasm_bls12381_map_fp2_to_g2_element: 300_000_000_000_000 +wasm_bls12381_pairing_base: 300_000_000_000_000 +wasm_bls12381_pairing_element: 300_000_000_000_000 +wasm_bls12381_p1_decompress_base: 300_000_000_000_000 +wasm_bls12381_p1_decompress_element: 300_000_000_000_000 +wasm_bls12381_p2_decompress_base: 300_000_000_000_000 +wasm_bls12381_p2_decompress_element: 300_000_000_000_000 + wasm_yield_create_base: 300_000_000_000_000 wasm_yield_create_byte: 300_000_000_000_000 wasm_yield_resume_base: 300_000_000_000_000 @@ -201,6 +222,7 @@ max_arguments_length: 4_194_304 max_length_returned_data: 4_194_304 max_contract_size: 4_194_304 max_transaction_size: 4_194_304 +max_receipt_size: 999_999_999_999_999 max_length_storage_key: 4_194_304 max_length_storage_value: 4_194_304 max_promises_per_function_call_action: 1_024 @@ -221,6 +243,7 @@ function_call_weight: false vm_kind: Wasmer0 eth_implicit_accounts: false yield_resume: false +discard_custom_sections: false # Congestion Control configuration diff --git a/core/parameters/res/runtime_configs/parameters_testnet.yaml b/core/parameters/res/runtime_configs/parameters_testnet.yaml index 9415eb3b836..7ff426ac24d 100644 --- a/core/parameters/res/runtime_configs/parameters_testnet.yaml +++ b/core/parameters/res/runtime_configs/parameters_testnet.yaml @@ -13,6 +13,8 @@ main_storage_proof_size_soft_limit: 999_999_999_999_999 per_receipt_storage_proof_size_limit: 999_999_999_999_999 combined_transactions_size_limit: 999_999_999_999_999 new_transactions_validation_state_size_soft_limit: 999_999_999_999_999 +outgoing_receipts_usual_size_limit: 999_999_999_999_999 +outgoing_receipts_big_size_limit: 999_999_999_999_999 # Account creation config min_allowed_top_level_account_length: 0 @@ -173,6 +175,25 @@ wasm_alt_bn128_pairing_check_base: 9_685_508_901_000 wasm_alt_bn128_pairing_check_element: 26_575_188_546 wasm_alt_bn128_g1_sum_base: 3_175_314_375 wasm_alt_bn128_g1_sum_element: 76_218_543 +wasm_bls12381_p1_sum_base: 300_000_000_000_000 +wasm_bls12381_p1_sum_element: 300_000_000_000_000 +wasm_bls12381_p2_sum_base: 300_000_000_000_000 +wasm_bls12381_p2_sum_element: 300_000_000_000_000 +wasm_bls12381_g1_multiexp_base: 300_000_000_000_000 +wasm_bls12381_g1_multiexp_element: 300_000_000_000_000 +wasm_bls12381_g2_multiexp_base: 300_000_000_000_000 +wasm_bls12381_g2_multiexp_element: 300_000_000_000_000 +wasm_bls12381_map_fp_to_g1_base: 300_000_000_000_000 +wasm_bls12381_map_fp_to_g1_element: 300_000_000_000_000 +wasm_bls12381_map_fp2_to_g2_base: 300_000_000_000_000 +wasm_bls12381_map_fp2_to_g2_element: 300_000_000_000_000 +wasm_bls12381_pairing_base: 300_000_000_000_000 +wasm_bls12381_pairing_element: 300_000_000_000_000 +wasm_bls12381_p1_decompress_base: 300_000_000_000_000 +wasm_bls12381_p1_decompress_element: 300_000_000_000_000 +wasm_bls12381_p2_decompress_base: 300_000_000_000_000 +wasm_bls12381_p2_decompress_element: 300_000_000_000_000 + wasm_yield_create_base: 300_000_000_000_000 wasm_yield_create_byte: 300_000_000_000_000 wasm_yield_resume_base: 300_000_000_000_000 @@ -198,6 +219,7 @@ max_arguments_length: 4_194_304 max_length_returned_data: 4_194_304 max_contract_size: 4_194_304 max_transaction_size: 4_194_304 +max_receipt_size: 999_999_999_999_999 max_length_storage_key: 4_194_304 max_length_storage_value: 4_194_304 max_promises_per_function_call_action: 1_024 @@ -216,8 +238,9 @@ function_call_weight: false vm_kind: Wasmer0 eth_implicit_accounts: false yield_resume: false +discard_custom_sections: false -# TODO What should be the config for testnet? +# TODO What should be the config for testnet? max_congestion_incoming_gas: 9_223_372_036_854_775_807 max_congestion_outgoing_gas: 9_223_372_036_854_775_807 diff --git a/core/parameters/src/config.rs b/core/parameters/src/config.rs index 6149583d4a1..584b361d8ac 100644 --- a/core/parameters/src/config.rs +++ b/core/parameters/src/config.rs @@ -1,12 +1,12 @@ //! Settings of the parameters of the runtime. +use super::parameter_table::InvalidConfigError; use crate::config_store::INITIAL_TESTNET_CONFIG; use crate::cost::RuntimeFeesConfig; use crate::parameter_table::ParameterTable; use near_account_id::AccountId; use near_primitives_core::types::{Balance, Gas}; use near_primitives_core::version::PROTOCOL_VERSION; - -use super::parameter_table::InvalidConfigError; +use std::sync::Arc; // Lowered promise yield timeout length used in integration tests. // The resharding tests for yield timeouts take too long to run otherwise. @@ -19,12 +19,12 @@ pub struct RuntimeConfig { /// /// This contains parameters that are required by the WASM runtime and the /// transaction runtime. - pub fees: RuntimeFeesConfig, + pub fees: Arc, /// Config of wasm operations, also includes wasm gas costs. /// /// This contains all the configuration parameters that are only required by /// the WASM runtime. - pub wasm_config: crate::vm::Config, + pub wasm_config: Arc, /// Config that defines rules for account creation. pub account_creation_config: AccountCreationConfig, /// The configuration for congestion control. @@ -54,8 +54,8 @@ impl RuntimeConfig { wasm_config.limit_config.yield_timeout_length_in_blocks = TEST_CONFIG_YIELD_TIMEOUT_LENGTH; RuntimeConfig { - fees: RuntimeFeesConfig::test(), - wasm_config, + fees: Arc::new(RuntimeFeesConfig::test()), + wasm_config: Arc::new(wasm_config), account_creation_config: AccountCreationConfig::default(), congestion_control_config: runtime_config.congestion_control_config, witness_config: runtime_config.witness_config, @@ -70,8 +70,8 @@ impl RuntimeConfig { wasm_config.make_free(); Self { - fees: RuntimeFeesConfig::free(), - wasm_config, + fees: Arc::new(RuntimeFeesConfig::free()), + wasm_config: Arc::new(wasm_config), account_creation_config: AccountCreationConfig::default(), congestion_control_config: runtime_config.congestion_control_config, witness_config: runtime_config.witness_config, @@ -130,21 +130,22 @@ pub struct CongestionControlConfig { pub max_congestion_memory_consumption: u64, /// How many missed chunks in a row in a shard is considered 100% congested. - /// TODO(congestion_control) - find a good limit for missed chunks. pub max_congestion_missed_chunks: u64, /// The maximum amount of gas attached to receipts a shard can forward to /// another shard per chunk. /// /// The actual gas forwarding allowance is a linear interpolation between - /// [`MIN_OUTGOING_GAS`] and [`MAX_OUTGOING_GAS`], or 0 if the receiver is + /// [MIN_OUTGOING_GAS](CongestionControlConfig::min_outgoing_gas) and + /// [MAX_OUTGOING_GAS](CongestionControlConfig::max_outgoing_gas), or 0 if the receiver is /// fully congested. pub max_outgoing_gas: Gas, /// The minimum gas each shard can send to a shard that is not fully congested. /// /// The actual gas forwarding allowance is a linear interpolation between - /// [`MIN_OUTGOING_GAS`] and [`MAX_OUTGOING_GAS`], or 0 if the receiver is + /// [MIN_OUTGOING_GAS](CongestionControlConfig::min_outgoing_gas) and + /// [MAX_OUTGOING_GAS](CongestionControlConfig::max_outgoing_gas), or 0 if the receiver is /// fully congested. pub min_outgoing_gas: Gas, @@ -161,25 +162,41 @@ pub struct CongestionControlConfig { /// receipts. /// /// The actual gas forwarding allowance is a linear interpolation between - /// [`MIN_TX_GAS`] and [`MAX_TX_GAS`], based on the incoming congestion of the - /// local shard. Additionally, transactions can be rejected if the receiving - /// remote shard is congested more than [`REJECT_TX_CONGESTION_THRESHOLD`] based - /// on their general congestion level. + /// [MIN_OUTGOING_GAS](CongestionControlConfig::min_outgoing_gas) and + /// [MAX_OUTGOING_GAS](CongestionControlConfig::max_outgoing_gas), + /// based on the incoming congestion of the local shard. + /// Additionally, transactions can be rejected if the receiving + /// remote shard is congested more than + /// [REJECT_TX_CONGESTION_THRESHOLD](CongestionControlConfig::reject_tx_congestion_threshold) + /// based on their general congestion level. pub max_tx_gas: Gas, /// The minimum amount of gas in a chunk spent on converting new transactions /// to receipts, as long as the receiving shard is not congested. /// /// The actual gas forwarding allowance is a linear interpolation between - /// [`MIN_TX_GAS`] and [`MAX_TX_GAS`], based on the incoming congestion of the - /// local shard. Additionally, transactions can be rejected if the receiving - /// remote shard is congested more than [`REJECT_TX_CONGESTION_THRESHOLD`] based - /// on their general congestion level. + /// [MIN_OUTGOING_GAS](CongestionControlConfig::min_outgoing_gas) and + /// [MAX_OUTGOING_GAS](CongestionControlConfig::max_outgoing_gas), + /// based on the incoming congestion of the local shard. + /// Additionally, transactions can be rejected if the receiving + /// remote shard is congested more than + /// [REJECT_TX_CONGESTION_THRESHOLD](CongestionControlConfig::reject_tx_congestion_threshold) + /// based on their general congestion level. pub min_tx_gas: Gas, /// How much congestion a shard can tolerate before it stops all shards from /// accepting new transactions with the receiver set to the congested shard. pub reject_tx_congestion_threshold: f64, + + /// The standard size limit for outgoing receipts aimed at a single shard. + /// This limit is pretty small to keep the size of source_receipt_proofs under control. + /// It limits the total sum of outgoing receipts, not individual receipts. + pub outgoing_receipts_usual_size_limit: u64, + + /// Large size limit for outgoing receipts to a shard, used when it's safe + /// to send a lot of receipts without making the state witness too large. + /// It limits the total sum of outgoing receipts, not individual receipts. + pub outgoing_receipts_big_size_limit: u64, } // The Eq cannot be automatically derived for this class because it contains a @@ -204,7 +221,9 @@ impl CongestionControlConfig { allowed_shard_outgoing_gas: max_value, max_tx_gas: max_value, min_tx_gas: max_value, - reject_tx_congestion_threshold: 1.0, + reject_tx_congestion_threshold: 2.0, + outgoing_receipts_usual_size_limit: max_value, + outgoing_receipts_big_size_limit: max_value, } } } diff --git a/core/parameters/src/config_store.rs b/core/parameters/src/config_store.rs index 1ae2d50c2e8..ad19114bae5 100644 --- a/core/parameters/src/config_store.rs +++ b/core/parameters/src/config_store.rs @@ -39,14 +39,15 @@ static CONFIG_DIFFS: &[(ProtocolVersion, &str)] = &[ (64, include_config!("64.yaml")), (66, include_config!("66.yaml")), (67, include_config!("67.yaml")), - (83, include_config!("83.yaml")), - (85, include_config!("85.yaml")), - (87, include_config!("87.yaml")), + // Congestion Control. + (68, include_config!("68.yaml")), + // Stateless Validation. + (69, include_config!("69.yaml")), (129, include_config!("129.yaml")), // Introduce ETH-implicit accounts. (138, include_config!("138.yaml")), - // Congestion Control - (142, include_config!("142.yaml")), + (141, include_config!("141.yaml")), + (143, include_config!("143.yaml")), ]; /// Testnet parameters for versions <= 29, which (incorrectly) differed from mainnet parameters @@ -83,7 +84,8 @@ impl RuntimeConfigStore { #[cfg(feature = "calimero_zero_storage")] { let mut initial_config = RuntimeConfig::new(¶ms).unwrap_or_else(|err| panic!("Failed generating `RuntimeConfig` from parameters for base parameter file. Error: {err}")); - initial_config.fees.storage_usage_config.storage_amount_per_byte = 0; + let fees = Arc::make_mut(&mut initial_config.fees); + fees.storage_usage_config.storage_amount_per_byte = 0; store.insert(0, Arc::new(initial_config)); } @@ -98,17 +100,26 @@ impl RuntimeConfigStore { #[cfg(feature = "calimero_zero_storage")] { let mut runtime_config = RuntimeConfig::new(¶ms).unwrap_or_else(|err| panic!("Failed generating `RuntimeConfig` from parameters for version {protocol_version}. Error: {err}")); - runtime_config.fees.storage_usage_config.storage_amount_per_byte = 0; + let fees = Arc::make_mut(&mut runtime_config.fees); + fees.storage_usage_config.storage_amount_per_byte = 0; store.insert(*protocol_version, Arc::new(runtime_config)); } } if let Some(runtime_config) = genesis_runtime_config { - let mut config = runtime_config.clone(); - store.insert(0, Arc::new(config.clone())); - - config.fees.storage_usage_config.storage_amount_per_byte = 10u128.pow(19); - store.insert(42, Arc::new(config)); + let mut fees = crate::RuntimeFeesConfig::clone(&runtime_config.fees); + fees.storage_usage_config.storage_amount_per_byte = 10u128.pow(19); + store.insert( + 42, + Arc::new(RuntimeConfig { + fees: Arc::new(fees), + wasm_config: Arc::clone(&runtime_config.wasm_config), + account_creation_config: runtime_config.account_creation_config.clone(), + congestion_control_config: runtime_config.congestion_control_config, + witness_config: runtime_config.witness_config, + }), + ); + store.insert(0, Arc::new(runtime_config.clone())); } Self { store } @@ -331,10 +342,14 @@ mod tests { ); } - /// Use snapshot testing to check that the JSON representation of the - /// configurations of each version is unchanged. - /// If tests fail after an intended change, run `cargo insta review` accept - /// the new snapshot if it looks right. + /// Use snapshot testing to check that the JSON representation of the configurations of each version is unchanged. + /// If tests fail after an intended change, follow the steps below to update the config files: + /// 1) Run the following to run tests with cargo insta so it generates all the file differences: + /// cargo insta test -p near-parameters -- tests::test_json_unchanged + /// 2) Run the following to examine the diffs at each file and see if the changes make sense: + /// cargo insta review + /// If the changes make sense, accept the changes per file (by responding to the prompts from the command). + /// Alternatively, add --accept to the first command so that it automatically does step 2. #[test] #[cfg(not(feature = "nightly"))] #[cfg(not(feature = "statelessnet_protocol"))] diff --git a/core/parameters/src/cost.rs b/core/parameters/src/cost.rs index 4bed1bdf833..a5769982730 100644 --- a/core/parameters/src/cost.rs +++ b/core/parameters/src/cost.rs @@ -130,6 +130,42 @@ impl ExtCostsConfig { ExtCosts::alt_bn128_pairing_check_element => 5_102_000_000_000, ExtCosts::alt_bn128_g1_sum_base => 3_000_000_000, ExtCosts::alt_bn128_g1_sum_element => 5_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_base => SAFETY_MULTIPLIER * 5_500_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_element => SAFETY_MULTIPLIER * 2_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_base => SAFETY_MULTIPLIER * 6_200_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_element => SAFETY_MULTIPLIER * 5_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_base => SAFETY_MULTIPLIER * 5_500_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_element => SAFETY_MULTIPLIER * 310_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_base => SAFETY_MULTIPLIER * 6_200_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_element => SAFETY_MULTIPLIER * 665_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_base => SAFETY_MULTIPLIER * 500_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_element => SAFETY_MULTIPLIER * 84_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_base => SAFETY_MULTIPLIER * 500_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_element => SAFETY_MULTIPLIER * 300_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_base => SAFETY_MULTIPLIER * 710_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_element => SAFETY_MULTIPLIER * 710_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_base => SAFETY_MULTIPLIER * 500_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_element => SAFETY_MULTIPLIER * 27_000_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_base => SAFETY_MULTIPLIER * 500_000_000, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_element => SAFETY_MULTIPLIER * 55_000_000_000, // TODO(yield/resume): replicate fees here after estimation ExtCosts::yield_create_base => 300_000_000_000_000, ExtCosts::yield_create_byte => 300_000_000_000_000, @@ -230,6 +266,42 @@ pub enum ExtCosts { yield_create_byte = 62, yield_resume_base = 63, yield_resume_byte = 64, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_sum_base = 65, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_sum_element = 66, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_sum_base = 67, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_sum_element = 68, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g1_multiexp_base = 69, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g1_multiexp_element = 70, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g2_multiexp_base = 71, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g2_multiexp_element = 72, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp_to_g1_base = 73, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp_to_g1_element = 74, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp2_to_g2_base = 75, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp2_to_g2_element = 76, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_pairing_base = 77, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_pairing_element = 78, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_decompress_base = 79, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_decompress_element = 80, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_decompress_base = 81, + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_decompress_element = 82, } // Type of an action, used in fees logic. @@ -342,6 +414,42 @@ impl ExtCosts { ExtCosts::yield_create_byte => Parameter::WasmYieldCreateByte, ExtCosts::yield_resume_base => Parameter::WasmYieldResumeBase, ExtCosts::yield_resume_byte => Parameter::WasmYieldResumeBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_base => Parameter::WasmBls12381P1SumBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_element => Parameter::WasmBls12381P1SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_base => Parameter::WasmBls12381P2SumBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_element => Parameter::WasmBls12381P2SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_base => Parameter::WasmBls12381G1MultiexpBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_element => Parameter::WasmBls12381G1MultiexpElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_base => Parameter::WasmBls12381G2MultiexpBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_element => Parameter::WasmBls12381G2MultiexpElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_base => Parameter::WasmBls12381MapFpToG1Base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_element => Parameter::WasmBls12381MapFpToG1Element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_base => Parameter::WasmBls12381MapFp2ToG2Base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_element => Parameter::WasmBls12381MapFp2ToG2Element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_base => Parameter::WasmBls12381PairingBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_element => Parameter::WasmBls12381PairingElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_base => Parameter::WasmBls12381P1DecompressBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_element => Parameter::WasmBls12381P1DecompressElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_base => Parameter::WasmBls12381P2DecompressBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_element => Parameter::WasmBls12381P2DecompressElement, } } } diff --git a/core/parameters/src/parameter.rs b/core/parameters/src/parameter.rs index 87e37cbdffe..ee8e75f5e69 100644 --- a/core/parameters/src/parameter.rs +++ b/core/parameters/src/parameter.rs @@ -32,6 +32,14 @@ pub enum Parameter { /// A witness contains transactions from both the previous chunk and the current one. /// This parameter limits the sum of sizes of transactions from both of those chunks. CombinedTransactionsSizeLimit, + /// The standard size limit for outgoing receipts aimed at a single shard. + /// This limit is pretty small to keep the size of source_receipt_proofs under control. + /// It limits the total sum of outgoing receipts, not individual receipts. + OutgoingReceiptsUsualSizeLimit, + /// Large size limit for outgoing receipts to a shard, used when it's safe + /// to send a lot of receipts without making the state witness too large. + /// It limits the total sum of outgoing receipts, not individual receipts. + OutgoingReceiptsBigSizeLimit, // Account creation config MinAllowedTopLevelAccountLength, @@ -132,6 +140,24 @@ pub enum Parameter { WasmYieldCreateByte, WasmYieldResumeBase, WasmYieldResumeByte, + WasmBls12381P1SumBase, + WasmBls12381P1SumElement, + WasmBls12381P2SumBase, + WasmBls12381P2SumElement, + WasmBls12381G1MultiexpBase, + WasmBls12381G1MultiexpElement, + WasmBls12381G2MultiexpBase, + WasmBls12381G2MultiexpElement, + WasmBls12381MapFpToG1Base, + WasmBls12381MapFpToG1Element, + WasmBls12381MapFp2ToG2Base, + WasmBls12381MapFp2ToG2Element, + WasmBls12381PairingBase, + WasmBls12381PairingElement, + WasmBls12381P1DecompressBase, + WasmBls12381P1DecompressElement, + WasmBls12381P2DecompressBase, + WasmBls12381P2DecompressElement, // Smart contract limits MaxGasBurnt, @@ -153,6 +179,7 @@ pub enum Parameter { MaxLengthReturnedData, MaxContractSize, MaxTransactionSize, + MaxReceiptSize, MaxLengthStorageKey, MaxLengthStorageValue, MaxPromisesPerFunctionCallAction, @@ -177,6 +204,7 @@ pub enum Parameter { VmKind, EthImplicitAccounts, YieldResume, + DiscardCustomSections, // Congestion Control MaxCongestionIncomingGas, @@ -247,6 +275,7 @@ impl Parameter { Parameter::MaxLengthReturnedData, Parameter::MaxContractSize, Parameter::MaxTransactionSize, + Parameter::MaxReceiptSize, Parameter::MaxLengthStorageKey, Parameter::MaxLengthStorageValue, Parameter::MaxPromisesPerFunctionCallAction, diff --git a/core/parameters/src/parameter_table.rs b/core/parameters/src/parameter_table.rs index e0adf1c7f3a..5de6067106d 100644 --- a/core/parameters/src/parameter_table.rs +++ b/core/parameters/src/parameter_table.rs @@ -10,6 +10,7 @@ use near_primitives_core::account::id::ParseAccountError; use near_primitives_core::types::AccountId; use num_rational::Rational32; use std::collections::BTreeMap; +use std::sync::Arc; /// Represents values supported by parameter config. #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)] @@ -290,7 +291,7 @@ impl TryFrom<&ParameterTable> for RuntimeConfig { fn try_from(params: &ParameterTable) -> Result { Ok(RuntimeConfig { - fees: RuntimeFeesConfig { + fees: Arc::new(RuntimeFeesConfig { action_fees: enum_map::enum_map! { action_cost => params.get_fee(action_cost)? }, @@ -302,8 +303,8 @@ impl TryFrom<&ParameterTable> for RuntimeConfig { num_bytes_account: params.get(Parameter::StorageNumBytesAccount)?, num_extra_bytes_record: params.get(Parameter::StorageNumExtraBytesRecord)?, }, - }, - wasm_config: Config { + }), + wasm_config: Arc::new(Config { ext_costs: ExtCostsConfig { costs: enum_map::enum_map! { cost => params.get(cost.param())? @@ -313,6 +314,7 @@ impl TryFrom<&ParameterTable> for RuntimeConfig { grow_mem_cost: params.get(Parameter::WasmGrowMemCost)?, regular_op_cost: params.get(Parameter::WasmRegularOpCost)?, disable_9393_fix: params.get(Parameter::Disable9393Fix)?, + discard_custom_sections: params.get(Parameter::DiscardCustomSections)?, limit_config: serde_yaml::from_value(params.yaml_map(Parameter::vm_limits())) .map_err(InvalidConfigError::InvalidYaml)?, fix_contract_loading_cost: params.get(Parameter::FixContractLoadingCost)?, @@ -327,7 +329,7 @@ impl TryFrom<&ParameterTable> for RuntimeConfig { function_call_weight: params.get(Parameter::FunctionCallWeight)?, eth_implicit_accounts: params.get(Parameter::EthImplicitAccounts)?, yield_resume_host_functions: params.get(Parameter::YieldResume)?, - }, + }), account_creation_config: AccountCreationConfig { min_allowed_top_level_account_length: params .get(Parameter::MinAllowedTopLevelAccountLength)?, @@ -363,6 +365,9 @@ fn get_congestion_control_config( let rational: Rational32 = params.get(Parameter::RejectTxCongestionThreshold)?; *rational.numer() as f64 / *rational.denom() as f64 }, + outgoing_receipts_usual_size_limit: params + .get(Parameter::OutgoingReceiptsUsualSizeLimit)?, + outgoing_receipts_big_size_limit: params.get(Parameter::OutgoingReceiptsBigSizeLimit)?, }; Ok(congestion_control_config) } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__0.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__0.json.snap index 98961be9369..5d8a5b7bea8 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__0.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__0.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": false, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__129.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__129.json.snap index 53261bddf79..a3508b4e598 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__129.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__129.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": true, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 2097152, + "combined_transactions_size_limit": 4194304, "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__138.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__138.json.snap index 156ed6e8718..bb09a42dc7f 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__138.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__138.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": true, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 2097152, + "combined_transactions_size_limit": 4194304, "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__141.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__141.json.snap new file mode 100644 index 00000000000..bb09a42dc7f --- /dev/null +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__141.json.snap @@ -0,0 +1,247 @@ +--- +source: core/parameters/src/config_store.rs +expression: config_view +--- +{ + "storage_amount_per_byte": "10000000000000000000", + "transaction_costs": { + "action_receipt_creation_config": { + "send_sir": 108059500000, + "send_not_sir": 108059500000, + "execution": 108059500000 + }, + "data_receipt_creation_config": { + "base_cost": { + "send_sir": 36486732312, + "send_not_sir": 36486732312, + "execution": 36486732312 + }, + "cost_per_byte": { + "send_sir": 17212011, + "send_not_sir": 47683715, + "execution": 17212011 + } + }, + "action_creation_config": { + "create_account_cost": { + "send_sir": 3850000000000, + "send_not_sir": 3850000000000, + "execution": 3850000000000 + }, + "deploy_contract_cost": { + "send_sir": 184765750000, + "send_not_sir": 184765750000, + "execution": 184765750000 + }, + "deploy_contract_cost_per_byte": { + "send_sir": 6812999, + "send_not_sir": 47683715, + "execution": 64572944 + }, + "function_call_cost": { + "send_sir": 200000000000, + "send_not_sir": 200000000000, + "execution": 780000000000 + }, + "function_call_cost_per_byte": { + "send_sir": 2235934, + "send_not_sir": 47683715, + "execution": 2235934 + }, + "transfer_cost": { + "send_sir": 115123062500, + "send_not_sir": 115123062500, + "execution": 115123062500 + }, + "stake_cost": { + "send_sir": 141715687500, + "send_not_sir": 141715687500, + "execution": 102217625000 + }, + "add_key_cost": { + "full_access_cost": { + "send_sir": 101765125000, + "send_not_sir": 101765125000, + "execution": 101765125000 + }, + "function_call_cost": { + "send_sir": 102217625000, + "send_not_sir": 102217625000, + "execution": 102217625000 + }, + "function_call_cost_per_byte": { + "send_sir": 1925331, + "send_not_sir": 47683715, + "execution": 1925331 + } + }, + "delete_key_cost": { + "send_sir": 94946625000, + "send_not_sir": 94946625000, + "execution": 94946625000 + }, + "delete_account_cost": { + "send_sir": 147489000000, + "send_not_sir": 147489000000, + "execution": 147489000000 + }, + "delegate_cost": { + "send_sir": 200000000000, + "send_not_sir": 200000000000, + "execution": 200000000000 + } + }, + "storage_usage_config": { + "num_bytes_account": 100, + "num_extra_bytes_record": 40 + }, + "burnt_gas_reward": [ + 3, + 10 + ], + "pessimistic_gas_price_inflation_ratio": [ + 103, + 100 + ] + }, + "wasm_config": { + "ext_costs": { + "base": 264768111, + "contract_loading_base": 35445963, + "contract_loading_bytes": 1089295, + "read_memory_base": 2609863200, + "read_memory_byte": 3801333, + "write_memory_base": 2803794861, + "write_memory_byte": 2723772, + "read_register_base": 2517165186, + "read_register_byte": 98562, + "write_register_base": 2865522486, + "write_register_byte": 3801564, + "utf8_decoding_base": 3111779061, + "utf8_decoding_byte": 291580479, + "utf16_decoding_base": 3543313050, + "utf16_decoding_byte": 163577493, + "sha256_base": 4540970250, + "sha256_byte": 24117351, + "keccak256_base": 5879491275, + "keccak256_byte": 21471105, + "keccak512_base": 5811388236, + "keccak512_byte": 36649701, + "ripemd160_base": 853675086, + "ripemd160_block": 680107584, + "ed25519_verify_base": 210000000000, + "ed25519_verify_byte": 9000000, + "ecrecover_base": 278821988457, + "log_base": 3543313050, + "log_byte": 13198791, + "storage_write_base": 64196736000, + "storage_write_key_byte": 70482867, + "storage_write_value_byte": 31018539, + "storage_write_evicted_byte": 32117307, + "storage_read_base": 56356845750, + "storage_read_key_byte": 30952533, + "storage_read_value_byte": 5611005, + "storage_remove_base": 53473030500, + "storage_remove_key_byte": 38220384, + "storage_remove_ret_value_byte": 11531556, + "storage_has_key_base": 54039896625, + "storage_has_key_byte": 30790845, + "storage_iter_create_prefix_base": 0, + "storage_iter_create_prefix_byte": 0, + "storage_iter_create_range_base": 0, + "storage_iter_create_from_byte": 0, + "storage_iter_create_to_byte": 0, + "storage_iter_next_base": 0, + "storage_iter_next_key_byte": 0, + "storage_iter_next_value_byte": 0, + "touching_trie_node": 16101955926, + "read_cached_trie_node": 2280000000, + "promise_and_base": 1465013400, + "promise_and_per_promise": 5452176, + "promise_return": 560152386, + "validator_stake_base": 911834726400, + "validator_total_stake_base": 911834726400, + "contract_compile_base": 0, + "contract_compile_bytes": 0, + "alt_bn128_g1_multiexp_base": 713000000000, + "alt_bn128_g1_multiexp_element": 320000000000, + "alt_bn128_g1_sum_base": 3000000000, + "alt_bn128_g1_sum_element": 5000000000, + "alt_bn128_pairing_check_base": 9686000000000, + "alt_bn128_pairing_check_element": 5102000000000, + "yield_create_base": 153411779276, + "yield_create_byte": 15643988, + "yield_resume_base": 1195627285210, + "yield_resume_byte": 1195627285210 + }, + "grow_mem_cost": 1, + "regular_op_cost": 822756, + "vm_kind": "", + "disable_9393_fix": false, + "discard_custom_sections": false, + "storage_get_mode": "FlatStorage", + "fix_contract_loading_cost": true, + "implicit_account_creation": true, + "math_extension": true, + "ed25519_verify": true, + "alt_bn128": true, + "function_call_weight": true, + "eth_implicit_accounts": true, + "yield_resume_host_functions": true, + "limit_config": { + "max_gas_burnt": 300000000000000, + "max_stack_height": 262144, + "contract_prepare_version": 2, + "initial_memory_pages": 1024, + "max_memory_pages": 2048, + "registers_memory_limit": 1073741824, + "max_register_size": 104857600, + "max_number_registers": 100, + "max_number_logs": 100, + "max_total_log_length": 16384, + "max_total_prepaid_gas": 300000000000000, + "max_actions_per_receipt": 100, + "max_number_bytes_method_names": 2000, + "max_length_method_name": 256, + "max_arguments_length": 4194304, + "max_length_returned_data": 4194304, + "max_contract_size": 4194304, + "max_transaction_size": 1572864, + "max_receipt_size": 4194304, + "max_length_storage_key": 2048, + "max_length_storage_value": 4194304, + "max_promises_per_function_call_action": 1024, + "max_number_input_data_dependencies": 128, + "max_functions_number_per_contract": 10000, + "wasmer2_stack_limit": 204800, + "max_locals_per_contract": 1000000, + "account_id_validity_rules_version": 1, + "yield_timeout_length_in_blocks": 200, + "max_yield_payload_size": 1024, + "per_receipt_storage_proof_size_limit": 4000000 + } + }, + "account_creation_config": { + "min_allowed_top_level_account_length": 65, + "registrar_account_id": "registrar" + }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, + "witness_config": { + "main_storage_proof_size_soft_limit": 3000000, + "combined_transactions_size_limit": 4194304, + "new_transactions_validation_state_size_soft_limit": 572864 + } +} diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__85.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__143.json.snap similarity index 87% rename from core/parameters/src/snapshots/near_parameters__config_store__tests__85.json.snap rename to core/parameters/src/snapshots/near_parameters__config_store__tests__143.json.snap index 9f43f2f2168..95c758b474a 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__85.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__143.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,14 +178,15 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": true, "storage_get_mode": "FlatStorage", - "fix_contract_loading_cost": false, + "fix_contract_loading_cost": true, "implicit_account_creation": true, "math_extension": true, "ed25519_verify": true, "alt_bn128": true, "function_call_weight": true, - "eth_implicit_accounts": false, + "eth_implicit_accounts": true, "yield_resume_host_functions": true, "limit_config": { "max_gas_burnt": 300000000000000, @@ -205,7 +206,8 @@ expression: config_view "max_arguments_length": 4194304, "max_length_returned_data": 4194304, "max_contract_size": 4194304, - "max_transaction_size": 4194304, + "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 999999999999999, - "new_transactions_validation_state_size_soft_limit": 999999999999999 + "combined_transactions_size_limit": 4194304, + "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__35.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__35.json.snap index e5d9a1f9a26..d9d62de95c5 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__35.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__35.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__42.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__42.json.snap index 105a414dcd6..d567eff8240 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__42.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__42.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__46.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__46.json.snap index 80f699ead57..82ced427f72 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__46.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__46.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__48.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__48.json.snap index 4a8e3c270a1..efce85d058f 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__48.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__48.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 2207874, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__49.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__49.json.snap index 5075e5f38e4..1980e66c625 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__49.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__49.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -222,6 +224,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__50.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__50.json.snap index 8a9285e51fd..ac2e65f3b25 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__50.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__50.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -222,6 +224,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__52.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__52.json.snap index b1abdab3fae..98eceb06c16 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__52.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__52.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -222,6 +224,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__53.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__53.json.snap index 4101dfe863e..7f0700671bc 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__53.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__53.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__55.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__55.json.snap index e6da32a2a96..df56cc68edd 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__55.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__55.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__57.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__57.json.snap index 51d09d69eb7..df08bd74aaa 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__57.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__57.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__59.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__59.json.snap index 72a1fe84822..5973eac4430 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__59.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__59.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__61.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__61.json.snap index 170e148403b..dc38f33c3c3 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__61.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__61.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__62.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__62.json.snap index eec8c5a7c2b..71f5052d7ef 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__62.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__62.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": true, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__63.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__63.json.snap index 961e67e7198..9d9bcf1cc6d 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__63.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__63.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__64.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__64.json.snap index e709f702a8d..92c11f5bc86 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__64.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__64.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__66.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__66.json.snap index 04bb62f7e1c..74a9b04e788 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__66.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__66.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__67.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__67.json.snap index ead0b9830ff..a07a9038777 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__67.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__67.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_83.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__68.json.snap similarity index 90% rename from core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_83.json.snap rename to core/parameters/src/snapshots/near_parameters__config_store__tests__68.json.snap index b7d4d565e0e..5ada6643ee4 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_83.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__68.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,8 +225,22 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { - "main_storage_proof_size_soft_limit": 16000000, + "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, "new_transactions_validation_state_size_soft_limit": 999999999999999 } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__87.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__69.json.snap similarity index 89% rename from core/parameters/src/snapshots/near_parameters__config_store__tests__87.json.snap rename to core/parameters/src/snapshots/near_parameters__config_store__tests__69.json.snap index 8c90815cf02..b366f1c5540 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__87.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__69.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 2097152, + "combined_transactions_size_limit": 4194304, "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_0.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_0.json.snap index 98961be9369..5d8a5b7bea8 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_0.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_0.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": false, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_129.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_129.json.snap index 53261bddf79..a3508b4e598 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_129.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_129.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": true, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 2097152, + "combined_transactions_size_limit": 4194304, "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_138.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_138.json.snap index 156ed6e8718..bb09a42dc7f 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_138.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_138.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": true, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 2097152, + "combined_transactions_size_limit": 4194304, "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_141.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_141.json.snap new file mode 100644 index 00000000000..bb09a42dc7f --- /dev/null +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_141.json.snap @@ -0,0 +1,247 @@ +--- +source: core/parameters/src/config_store.rs +expression: config_view +--- +{ + "storage_amount_per_byte": "10000000000000000000", + "transaction_costs": { + "action_receipt_creation_config": { + "send_sir": 108059500000, + "send_not_sir": 108059500000, + "execution": 108059500000 + }, + "data_receipt_creation_config": { + "base_cost": { + "send_sir": 36486732312, + "send_not_sir": 36486732312, + "execution": 36486732312 + }, + "cost_per_byte": { + "send_sir": 17212011, + "send_not_sir": 47683715, + "execution": 17212011 + } + }, + "action_creation_config": { + "create_account_cost": { + "send_sir": 3850000000000, + "send_not_sir": 3850000000000, + "execution": 3850000000000 + }, + "deploy_contract_cost": { + "send_sir": 184765750000, + "send_not_sir": 184765750000, + "execution": 184765750000 + }, + "deploy_contract_cost_per_byte": { + "send_sir": 6812999, + "send_not_sir": 47683715, + "execution": 64572944 + }, + "function_call_cost": { + "send_sir": 200000000000, + "send_not_sir": 200000000000, + "execution": 780000000000 + }, + "function_call_cost_per_byte": { + "send_sir": 2235934, + "send_not_sir": 47683715, + "execution": 2235934 + }, + "transfer_cost": { + "send_sir": 115123062500, + "send_not_sir": 115123062500, + "execution": 115123062500 + }, + "stake_cost": { + "send_sir": 141715687500, + "send_not_sir": 141715687500, + "execution": 102217625000 + }, + "add_key_cost": { + "full_access_cost": { + "send_sir": 101765125000, + "send_not_sir": 101765125000, + "execution": 101765125000 + }, + "function_call_cost": { + "send_sir": 102217625000, + "send_not_sir": 102217625000, + "execution": 102217625000 + }, + "function_call_cost_per_byte": { + "send_sir": 1925331, + "send_not_sir": 47683715, + "execution": 1925331 + } + }, + "delete_key_cost": { + "send_sir": 94946625000, + "send_not_sir": 94946625000, + "execution": 94946625000 + }, + "delete_account_cost": { + "send_sir": 147489000000, + "send_not_sir": 147489000000, + "execution": 147489000000 + }, + "delegate_cost": { + "send_sir": 200000000000, + "send_not_sir": 200000000000, + "execution": 200000000000 + } + }, + "storage_usage_config": { + "num_bytes_account": 100, + "num_extra_bytes_record": 40 + }, + "burnt_gas_reward": [ + 3, + 10 + ], + "pessimistic_gas_price_inflation_ratio": [ + 103, + 100 + ] + }, + "wasm_config": { + "ext_costs": { + "base": 264768111, + "contract_loading_base": 35445963, + "contract_loading_bytes": 1089295, + "read_memory_base": 2609863200, + "read_memory_byte": 3801333, + "write_memory_base": 2803794861, + "write_memory_byte": 2723772, + "read_register_base": 2517165186, + "read_register_byte": 98562, + "write_register_base": 2865522486, + "write_register_byte": 3801564, + "utf8_decoding_base": 3111779061, + "utf8_decoding_byte": 291580479, + "utf16_decoding_base": 3543313050, + "utf16_decoding_byte": 163577493, + "sha256_base": 4540970250, + "sha256_byte": 24117351, + "keccak256_base": 5879491275, + "keccak256_byte": 21471105, + "keccak512_base": 5811388236, + "keccak512_byte": 36649701, + "ripemd160_base": 853675086, + "ripemd160_block": 680107584, + "ed25519_verify_base": 210000000000, + "ed25519_verify_byte": 9000000, + "ecrecover_base": 278821988457, + "log_base": 3543313050, + "log_byte": 13198791, + "storage_write_base": 64196736000, + "storage_write_key_byte": 70482867, + "storage_write_value_byte": 31018539, + "storage_write_evicted_byte": 32117307, + "storage_read_base": 56356845750, + "storage_read_key_byte": 30952533, + "storage_read_value_byte": 5611005, + "storage_remove_base": 53473030500, + "storage_remove_key_byte": 38220384, + "storage_remove_ret_value_byte": 11531556, + "storage_has_key_base": 54039896625, + "storage_has_key_byte": 30790845, + "storage_iter_create_prefix_base": 0, + "storage_iter_create_prefix_byte": 0, + "storage_iter_create_range_base": 0, + "storage_iter_create_from_byte": 0, + "storage_iter_create_to_byte": 0, + "storage_iter_next_base": 0, + "storage_iter_next_key_byte": 0, + "storage_iter_next_value_byte": 0, + "touching_trie_node": 16101955926, + "read_cached_trie_node": 2280000000, + "promise_and_base": 1465013400, + "promise_and_per_promise": 5452176, + "promise_return": 560152386, + "validator_stake_base": 911834726400, + "validator_total_stake_base": 911834726400, + "contract_compile_base": 0, + "contract_compile_bytes": 0, + "alt_bn128_g1_multiexp_base": 713000000000, + "alt_bn128_g1_multiexp_element": 320000000000, + "alt_bn128_g1_sum_base": 3000000000, + "alt_bn128_g1_sum_element": 5000000000, + "alt_bn128_pairing_check_base": 9686000000000, + "alt_bn128_pairing_check_element": 5102000000000, + "yield_create_base": 153411779276, + "yield_create_byte": 15643988, + "yield_resume_base": 1195627285210, + "yield_resume_byte": 1195627285210 + }, + "grow_mem_cost": 1, + "regular_op_cost": 822756, + "vm_kind": "", + "disable_9393_fix": false, + "discard_custom_sections": false, + "storage_get_mode": "FlatStorage", + "fix_contract_loading_cost": true, + "implicit_account_creation": true, + "math_extension": true, + "ed25519_verify": true, + "alt_bn128": true, + "function_call_weight": true, + "eth_implicit_accounts": true, + "yield_resume_host_functions": true, + "limit_config": { + "max_gas_burnt": 300000000000000, + "max_stack_height": 262144, + "contract_prepare_version": 2, + "initial_memory_pages": 1024, + "max_memory_pages": 2048, + "registers_memory_limit": 1073741824, + "max_register_size": 104857600, + "max_number_registers": 100, + "max_number_logs": 100, + "max_total_log_length": 16384, + "max_total_prepaid_gas": 300000000000000, + "max_actions_per_receipt": 100, + "max_number_bytes_method_names": 2000, + "max_length_method_name": 256, + "max_arguments_length": 4194304, + "max_length_returned_data": 4194304, + "max_contract_size": 4194304, + "max_transaction_size": 1572864, + "max_receipt_size": 4194304, + "max_length_storage_key": 2048, + "max_length_storage_value": 4194304, + "max_promises_per_function_call_action": 1024, + "max_number_input_data_dependencies": 128, + "max_functions_number_per_contract": 10000, + "wasmer2_stack_limit": 204800, + "max_locals_per_contract": 1000000, + "account_id_validity_rules_version": 1, + "yield_timeout_length_in_blocks": 200, + "max_yield_payload_size": 1024, + "per_receipt_storage_proof_size_limit": 4000000 + } + }, + "account_creation_config": { + "min_allowed_top_level_account_length": 65, + "registrar_account_id": "registrar" + }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, + "witness_config": { + "main_storage_proof_size_soft_limit": 3000000, + "combined_transactions_size_limit": 4194304, + "new_transactions_validation_state_size_soft_limit": 572864 + } +} diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_85.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_143.json.snap similarity index 87% rename from core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_85.json.snap rename to core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_143.json.snap index 9f43f2f2168..95c758b474a 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_85.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_143.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,14 +178,15 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": true, "storage_get_mode": "FlatStorage", - "fix_contract_loading_cost": false, + "fix_contract_loading_cost": true, "implicit_account_creation": true, "math_extension": true, "ed25519_verify": true, "alt_bn128": true, "function_call_weight": true, - "eth_implicit_accounts": false, + "eth_implicit_accounts": true, "yield_resume_host_functions": true, "limit_config": { "max_gas_burnt": 300000000000000, @@ -205,7 +206,8 @@ expression: config_view "max_arguments_length": 4194304, "max_length_returned_data": 4194304, "max_contract_size": 4194304, - "max_transaction_size": 4194304, + "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 999999999999999, - "new_transactions_validation_state_size_soft_limit": 999999999999999 + "combined_transactions_size_limit": 4194304, + "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_35.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_35.json.snap index e5d9a1f9a26..d9d62de95c5 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_35.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_35.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_42.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_42.json.snap index 105a414dcd6..d567eff8240 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_42.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_42.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_46.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_46.json.snap index 80f699ead57..82ced427f72 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_46.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_46.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 3856371, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_48.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_48.json.snap index 4a8e3c270a1..efce85d058f 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_48.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_48.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 2207874, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -221,6 +223,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_49.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_49.json.snap index 5075e5f38e4..1980e66c625 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_49.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_49.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -222,6 +224,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_50.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_50.json.snap index 8a9285e51fd..ac2e65f3b25 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_50.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_50.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -222,6 +224,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_52.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_52.json.snap index b1abdab3fae..98eceb06c16 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_52.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_52.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 4194304, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -222,6 +224,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_53.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_53.json.snap index 4101dfe863e..7f0700671bc 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_53.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_53.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_55.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_55.json.snap index e6da32a2a96..df56cc68edd 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_55.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_55.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_57.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_57.json.snap index 51d09d69eb7..df08bd74aaa 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_57.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_57.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_59.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_59.json.snap index 72a1fe84822..5973eac4430 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_59.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_59.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "Trie", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_61.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_61.json.snap index 170e148403b..dc38f33c3c3 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_61.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_61.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_62.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_62.json.snap index eec8c5a7c2b..71f5052d7ef 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_62.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_62.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": true, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_63.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_63.json.snap index 961e67e7198..9d9bcf1cc6d 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_63.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_63.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 32, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_64.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_64.json.snap index e709f702a8d..92c11f5bc86 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_64.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_64.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_66.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_66.json.snap index 04bb62f7e1c..74a9b04e788 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_66.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_66.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_67.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_67.json.snap index ead0b9830ff..a07a9038777 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_67.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_67.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,6 +225,20 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 9223372036854775807, + "max_congestion_outgoing_gas": 9223372036854775807, + "max_congestion_memory_consumption": 9223372036854775807, + "max_congestion_missed_chunks": 9223372036854775807, + "max_outgoing_gas": 9223372036854775807, + "min_outgoing_gas": 9223372036854775807, + "allowed_shard_outgoing_gas": 9223372036854775807, + "max_tx_gas": 9223372036854775807, + "min_tx_gas": 9223372036854775807, + "reject_tx_congestion_threshold": 1.0, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__83.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_68.json.snap similarity index 90% rename from core/parameters/src/snapshots/near_parameters__config_store__tests__83.json.snap rename to core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_68.json.snap index b7d4d565e0e..5ada6643ee4 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__83.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_68.json.snap @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 4194304, + "max_receipt_size": 999999999999999, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,8 +225,22 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 999999999999999, + "outgoing_receipts_big_size_limit": 999999999999999 + }, "witness_config": { - "main_storage_proof_size_soft_limit": 16000000, + "main_storage_proof_size_soft_limit": 999999999999999, "combined_transactions_size_limit": 999999999999999, "new_transactions_validation_state_size_soft_limit": 999999999999999 } diff --git a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_87.json.snap b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_69.json.snap similarity index 89% rename from core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_87.json.snap rename to core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_69.json.snap index 8c90815cf02..b366f1c5540 100644 --- a/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_87.json.snap +++ b/core/parameters/src/snapshots/near_parameters__config_store__tests__testnet_69.json.snap @@ -18,7 +18,7 @@ expression: config_view }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: config_view }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: config_view }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: config_view "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -206,6 +207,7 @@ expression: config_view "max_length_returned_data": 4194304, "max_contract_size": 4194304, "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -223,9 +225,23 @@ expression: config_view "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { "main_storage_proof_size_soft_limit": 3000000, - "combined_transactions_size_limit": 2097152, + "combined_transactions_size_limit": 4194304, "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/snapshots/near_parameters__view__tests__runtime_config_view.snap b/core/parameters/src/snapshots/near_parameters__view__tests__runtime_config_view.snap index 04334405093..3932ba0540f 100644 --- a/core/parameters/src/snapshots/near_parameters__view__tests__runtime_config_view.snap +++ b/core/parameters/src/snapshots/near_parameters__view__tests__runtime_config_view.snap @@ -18,7 +18,7 @@ expression: "&view" }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: "&view" }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: "&view" }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: "&view" }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: "&view" "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -205,7 +206,8 @@ expression: "&view" "max_arguments_length": 4194304, "max_length_returned_data": 4194304, "max_contract_size": 4194304, - "max_transaction_size": 4194304, + "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -216,16 +218,30 @@ expression: "&view" "account_id_validity_rules_version": 1, "yield_timeout_length_in_blocks": 200, "max_yield_payload_size": 1024, - "per_receipt_storage_proof_size_limit": 999999999999999 + "per_receipt_storage_proof_size_limit": 4000000 } }, "account_creation_config": { "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { - "main_storage_proof_size_soft_limit": 999999999999999, - "combined_transactions_size_limit": 999999999999999, - "new_transactions_validation_state_size_soft_limit": 999999999999999 + "main_storage_proof_size_soft_limit": 3000000, + "combined_transactions_size_limit": 4194304, + "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/parameters/src/view.rs b/core/parameters/src/view.rs index c723d050a3b..c0e960d8d1e 100644 --- a/core/parameters/src/view.rs +++ b/core/parameters/src/view.rs @@ -1,4 +1,4 @@ -use crate::config::WitnessConfig; +use crate::config::{CongestionControlConfig, WitnessConfig}; use crate::{ActionCosts, ExtCosts, Fee, ParameterCost}; use near_account_id::AccountId; use near_primitives_core::serialize::dec_format; @@ -19,6 +19,8 @@ pub struct RuntimeConfigView { pub wasm_config: VMConfigView, /// Config that defines rules for account creation. pub account_creation_config: AccountCreationConfigView, + /// The configuration for congestion control. + pub congestion_control_config: CongestionControlConfigView, /// Configuration specific to ChunkStateWitness. pub witness_config: WitnessConfigView, } @@ -183,13 +185,16 @@ impl From for RuntimeConfigView { .fees .pessimistic_gas_price_inflation_ratio, }, - wasm_config: VMConfigView::from(config.wasm_config), + wasm_config: VMConfigView::from(crate::vm::Config::clone(&config.wasm_config)), account_creation_config: AccountCreationConfigView { min_allowed_top_level_account_length: config .account_creation_config .min_allowed_top_level_account_length, registrar_account_id: config.account_creation_config.registrar_account_id, }, + congestion_control_config: CongestionControlConfigView::from( + config.congestion_control_config, + ), witness_config: WitnessConfigView::from(config.witness_config), } } @@ -205,27 +210,30 @@ pub struct VMConfigView { /// Gas cost of a regular operation. pub regular_op_cost: u32, - /// See [`VMConfig::vm_kind`]. + /// See [VMConfig::vm_kind](crate::vm::Config::vm_kind). pub vm_kind: crate::vm::VMKind, - /// See [`VMConfig::disable_9393_fix`]. + /// See [VMConfig::disable_9393_fix](crate::vm::Config::disable_9393_fix). pub disable_9393_fix: bool, - /// See [`VMConfig::flat_storage_reads`]. + /// See [VMConfig::discard_custom_sections](crate::vm::Config::discard_custom_sections). + pub discard_custom_sections: bool, + + /// See [VMConfig::storage_get_mode](crate::vm::Config::storage_get_mode). pub storage_get_mode: crate::vm::StorageGetMode, - /// See [`VMConfig::fix_contract_loading_cost`]. + /// See [VMConfig::fix_contract_loading_cost](crate::vm::Config::fix_contract_loading_cost). pub fix_contract_loading_cost: bool, - /// See [`VMConfig::implicit_account_creation`]. + /// See [VMConfig::implicit_account_creation](crate::vm::Config::implicit_account_creation). pub implicit_account_creation: bool, - /// See [`VMConfig::math_extension`]. + /// See [VMConfig::math_extension](crate::vm::Config::math_extension). pub math_extension: bool, - /// See [`VMConfig::ed25519_verify`]. + /// See [VMConfig::ed25519_verify](crate::vm::Config::ed25519_verify). pub ed25519_verify: bool, - /// See [`VMConfig::alt_bn128`]. + /// See [VMConfig::alt_bn128](crate::vm::Config::alt_bn128). pub alt_bn128: bool, - /// See [`VMConfig::function_call_weight`]. + /// See [VMConfig::function_call_weight](crate::vm::Config::function_call_weight). pub function_call_weight: bool, - /// See [`VMConfig::eth_implicit_accounts`]. + /// See [VMConfig::eth_implicit_accounts](crate::vm::Config::eth_implicit_accounts). pub eth_implicit_accounts: bool, - /// See [`VMConfig::yield_resume_host_functions`]. + /// See [VMConfig::yield_resume_host_functions](`crate::vm::Config::yield_resume_host_functions). pub yield_resume_host_functions: bool, /// Describes limits for VM and Runtime. @@ -242,6 +250,7 @@ impl From for VMConfigView { grow_mem_cost: config.grow_mem_cost, regular_op_cost: config.regular_op_cost, disable_9393_fix: config.disable_9393_fix, + discard_custom_sections: config.discard_custom_sections, limit_config: config.limit_config, storage_get_mode: config.storage_get_mode, fix_contract_loading_cost: config.fix_contract_loading_cost, @@ -264,6 +273,7 @@ impl From for crate::vm::Config { grow_mem_cost: view.grow_mem_cost, regular_op_cost: view.regular_op_cost, disable_9393_fix: view.disable_9393_fix, + discard_custom_sections: view.discard_custom_sections, limit_config: view.limit_config, storage_get_mode: view.storage_get_mode, fix_contract_loading_cost: view.fix_contract_loading_cost, @@ -446,7 +456,6 @@ pub struct ExtCostsConfigView { pub alt_bn128_pairing_check_base: Gas, /// Per element cost for pairing check pub alt_bn128_pairing_check_element: Gas, - /// Base cost for creating a yield promise. pub yield_create_base: Gas, /// Per byte cost of arguments and method name. @@ -455,6 +464,42 @@ pub struct ExtCostsConfigView { pub yield_resume_base: Gas, /// Per byte cost of resume payload. pub yield_resume_byte: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p1_sum_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p1_sum_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p2_sum_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p2_sum_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_g1_multiexp_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_g1_multiexp_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_g2_multiexp_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_g2_multiexp_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_map_fp_to_g1_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_map_fp_to_g1_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_map_fp2_to_g2_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_map_fp2_to_g2_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_pairing_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_pairing_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p1_decompress_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p1_decompress_element: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p2_decompress_base: Gas, + #[cfg(feature = "protocol_feature_bls12381")] + pub bls12381_p2_decompress_element: Gas, } impl From for ExtCostsConfigView { @@ -529,6 +574,45 @@ impl From for ExtCostsConfigView { yield_create_byte: config.gas_cost(ExtCosts::yield_create_byte), yield_resume_base: config.gas_cost(ExtCosts::yield_resume_base), yield_resume_byte: config.gas_cost(ExtCosts::yield_resume_byte), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_sum_base: config.gas_cost(ExtCosts::bls12381_p1_sum_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_sum_element: config.gas_cost(ExtCosts::bls12381_p1_sum_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_sum_base: config.gas_cost(ExtCosts::bls12381_p2_sum_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_sum_element: config.gas_cost(ExtCosts::bls12381_p2_sum_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g1_multiexp_base: config.gas_cost(ExtCosts::bls12381_g1_multiexp_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g1_multiexp_element: config.gas_cost(ExtCosts::bls12381_g1_multiexp_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g2_multiexp_base: config.gas_cost(ExtCosts::bls12381_g2_multiexp_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_g2_multiexp_element: config.gas_cost(ExtCosts::bls12381_g2_multiexp_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp_to_g1_base: config.gas_cost(ExtCosts::bls12381_map_fp_to_g1_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp_to_g1_element: config.gas_cost(ExtCosts::bls12381_map_fp_to_g1_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp2_to_g2_base: config.gas_cost(ExtCosts::bls12381_map_fp2_to_g2_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_map_fp2_to_g2_element: config + .gas_cost(ExtCosts::bls12381_map_fp2_to_g2_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_pairing_base: config.gas_cost(ExtCosts::bls12381_pairing_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_pairing_element: config.gas_cost(ExtCosts::bls12381_pairing_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_decompress_base: config.gas_cost(ExtCosts::bls12381_p1_decompress_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p1_decompress_element: config + .gas_cost(ExtCosts::bls12381_p1_decompress_element), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_decompress_base: config.gas_cost(ExtCosts::bls12381_p2_decompress_base), + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_p2_decompress_element: config + .gas_cost(ExtCosts::bls12381_p2_decompress_element), // removed parameters contract_compile_base: 0, contract_compile_bytes: 0, @@ -604,6 +688,42 @@ impl From for crate::ExtCostsConfig { ExtCosts::yield_create_byte => view.yield_create_byte, ExtCosts::yield_resume_base => view.yield_resume_base, ExtCosts::yield_resume_byte => view.yield_resume_byte, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_base => view.bls12381_p1_sum_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_element => view.bls12381_p1_sum_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_base => view.bls12381_p2_sum_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_element => view.bls12381_p2_sum_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_base => view.bls12381_g1_multiexp_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_element => view.bls12381_g1_multiexp_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_base => view.bls12381_g2_multiexp_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_element => view.bls12381_g2_multiexp_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_base => view.bls12381_map_fp_to_g1_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_element => view.bls12381_map_fp_to_g1_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_base => view.bls12381_map_fp2_to_g2_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_element => view.bls12381_map_fp2_to_g2_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_base => view.bls12381_pairing_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_element => view.bls12381_pairing_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_base => view.bls12381_p1_decompress_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_element => view.bls12381_p1_decompress_element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_base => view.bls12381_p2_decompress_base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_element => view.bls12381_p2_decompress_element, } .map(|_, value| ParameterCost { gas: value, compute: value }); Self { costs } @@ -635,6 +755,109 @@ impl From for WitnessConfigView { } } +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] +pub struct CongestionControlConfigView { + /// How much gas in delayed receipts of a shard is 100% incoming congestion. + /// + /// See [`CongestionControlConfig`] for more details. + pub max_congestion_incoming_gas: Gas, + + /// How much gas in outgoing buffered receipts of a shard is 100% congested. + /// + /// Outgoing congestion contributes to overall congestion, which reduces how + /// much other shards are allowed to forward to this shard. + pub max_congestion_outgoing_gas: Gas, + + /// How much memory space of all delayed and buffered receipts in a shard is + /// considered 100% congested. + /// + /// See [`CongestionControlConfig`] for more details. + pub max_congestion_memory_consumption: u64, + + /// How many missed chunks in a row in a shard is considered 100% congested. + pub max_congestion_missed_chunks: u64, + + /// The maximum amount of gas attached to receipts a shard can forward to + /// another shard per chunk. + /// + /// See [`CongestionControlConfig`] for more details. + pub max_outgoing_gas: Gas, + + /// The minimum gas each shard can send to a shard that is not fully congested. + /// + /// See [`CongestionControlConfig`] for more details. + pub min_outgoing_gas: Gas, + + /// How much gas the chosen allowed shard can send to a 100% congested shard. + /// + /// See [`CongestionControlConfig`] for more details. + pub allowed_shard_outgoing_gas: Gas, + + /// The maximum amount of gas in a chunk spent on converting new transactions to + /// receipts. + /// + /// See [`CongestionControlConfig`] for more details. + pub max_tx_gas: Gas, + + /// The minimum amount of gas in a chunk spent on converting new transactions + /// to receipts, as long as the receiving shard is not congested. + /// + /// See [`CongestionControlConfig`] for more details. + pub min_tx_gas: Gas, + + /// How much congestion a shard can tolerate before it stops all shards from + /// accepting new transactions with the receiver set to the congested shard. + pub reject_tx_congestion_threshold: f64, + + /// The standard size limit for outgoing receipts aimed at a single shard. + /// This limit is pretty small to keep the size of source_receipt_proofs under control. + /// It limits the total sum of outgoing receipts, not individual receipts. + pub outgoing_receipts_usual_size_limit: u64, + + /// Large size limit for outgoing receipts to a shard, used when it's safe + /// to send a lot of receipts without making the state witness too large. + /// It limits the total sum of outgoing receipts, not individual receipts. + pub outgoing_receipts_big_size_limit: u64, +} + +impl From for CongestionControlConfigView { + fn from(other: CongestionControlConfig) -> Self { + Self { + max_congestion_incoming_gas: other.max_congestion_incoming_gas, + max_congestion_outgoing_gas: other.max_congestion_outgoing_gas, + max_congestion_memory_consumption: other.max_congestion_memory_consumption, + max_congestion_missed_chunks: other.max_congestion_missed_chunks, + max_outgoing_gas: other.max_outgoing_gas, + min_outgoing_gas: other.min_outgoing_gas, + allowed_shard_outgoing_gas: other.allowed_shard_outgoing_gas, + max_tx_gas: other.max_tx_gas, + min_tx_gas: other.min_tx_gas, + reject_tx_congestion_threshold: other.reject_tx_congestion_threshold, + outgoing_receipts_usual_size_limit: other.outgoing_receipts_usual_size_limit, + outgoing_receipts_big_size_limit: other.outgoing_receipts_big_size_limit, + } + } +} + +impl From for CongestionControlConfig { + fn from(other: CongestionControlConfigView) -> Self { + Self { + max_congestion_incoming_gas: other.max_congestion_incoming_gas, + max_congestion_outgoing_gas: other.max_congestion_outgoing_gas, + max_congestion_memory_consumption: other.max_congestion_memory_consumption, + max_congestion_missed_chunks: other.max_congestion_missed_chunks, + max_outgoing_gas: other.max_outgoing_gas, + min_outgoing_gas: other.min_outgoing_gas, + allowed_shard_outgoing_gas: other.allowed_shard_outgoing_gas, + max_tx_gas: other.max_tx_gas, + min_tx_gas: other.min_tx_gas, + reject_tx_congestion_threshold: other.reject_tx_congestion_threshold, + outgoing_receipts_usual_size_limit: other.outgoing_receipts_usual_size_limit, + outgoing_receipts_big_size_limit: other.outgoing_receipts_big_size_limit, + } + } +} + #[cfg(test)] #[cfg(not(feature = "nightly"))] #[cfg(not(feature = "statelessnet_protocol"))] diff --git a/core/parameters/src/vm.rs b/core/parameters/src/vm.rs index 8c62246caf9..21689db3a1b 100644 --- a/core/parameters/src/vm.rs +++ b/core/parameters/src/vm.rs @@ -109,6 +109,8 @@ pub struct LimitConfig { pub max_contract_size: u64, /// Max transaction size pub max_transaction_size: u64, + /// Max receipt size + pub max_receipt_size: u64, /// Max storage key size pub max_length_storage_key: u64, /// Max storage value size @@ -192,6 +194,9 @@ pub struct Config { /// Enable the `promise_yield_create` and `promise_yield_resume` host functions. pub yield_resume_host_functions: bool, + /// Whether to discard custom sections. + pub discard_custom_sections: bool, + /// Describes limits for VM and Runtime. pub limit_config: LimitConfig, } diff --git a/core/primitives-core/Cargo.toml b/core/primitives-core/Cargo.toml index 2d15499b5d0..05a08b5419f 100644 --- a/core/primitives-core/Cargo.toml +++ b/core/primitives-core/Cargo.toml @@ -37,9 +37,11 @@ protocol_feature_fix_staking_threshold = [] protocol_feature_fix_contract_loading_cost = [] protocol_feature_reject_blocks_with_outdated_protocol_version = [] protocol_feature_nonrefundable_transfer_nep491 = [] +protocol_feature_bls12381 = [] nightly = [ "nightly_protocol", + "protocol_feature_bls12381", "protocol_feature_fix_contract_loading_cost", "protocol_feature_fix_staking_threshold", "protocol_feature_nonrefundable_transfer_nep491", diff --git a/core/primitives-core/src/version.rs b/core/primitives-core/src/version.rs index 9e079bb415b..6563d08cdd7 100644 --- a/core/primitives-core/src/version.rs +++ b/core/primitives-core/src/version.rs @@ -86,24 +86,24 @@ pub enum ProtocolFeature { MaxKickoutStake, /// Validate account id for function call access keys. AccountIdInFunctionCallPermission, - /// Zero Balance Account NEP 448: https://github.com/near/NEPs/pull/448 + /// Zero Balance Account NEP 448: ZeroBalanceAccount, /// Execute a set of actions on behalf of another account. /// - /// Meta Transaction NEP-366: https://github.com/near/NEPs/blob/master/neps/nep-0366.md + /// Meta Transaction NEP-366: DelegateAction, Ed25519Verify, /// Decouple compute and gas costs of operations to safely limit the compute time it takes to /// process the chunk. /// - /// Compute Costs NEP-455: https://github.com/near/NEPs/blob/master/neps/nep-0455.md + /// Compute Costs NEP-455: ComputeCosts, /// Decrease the cost of function call action. Only affects the execution cost. DecreaseFunctionCallBaseCost, /// Enable flat storage for reads, reducing number of DB accesses from `2 * key.len()` in /// the worst case to 2. /// - /// Flat Storage NEP-399: https://github.com/near/NEPs/blob/master/neps/nep-0399.md + /// Flat Storage NEP-399: FlatStorageReads, /// Enables preparation V2. Note that this setting is not supported in production settings /// without NearVmRuntime enabled alongside it, as the VM runner would be too slow. @@ -128,16 +128,19 @@ pub enum ProtocolFeature { #[cfg(feature = "protocol_feature_reject_blocks_with_outdated_protocol_version")] RejectBlocksWithOutdatedProtocolVersions, /// Allows creating an account with a non refundable balance to cover storage costs. - /// NEP: https://github.com/near/NEPs/pull/491 + /// NEP: #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] NonrefundableStorage, + // NEP: https://github.com/near/NEPs/pull/488 + #[cfg(feature = "protocol_feature_bls12381")] + BLS12381, RestrictTla, /// Increases the number of chunk producers. TestnetFewerBlockProducers, - /// Enables stateless validation which is introduced in https://github.com/near/NEPs/pull/509 + /// Enables stateless validation which is introduced in StatelessValidationV0, EthImplicitAccounts, - /// Enables yield execution which is introduced in https://github.com/near/NEPs/pull/519 + /// Enables yield execution which is introduced in YieldExecution, /// Protocol version reserved for use in resharding tests. @@ -155,12 +158,24 @@ pub enum ProtocolFeature { // Receipts which generate storage proofs larger than this limit will be rejected. // Protocol 85 also decreased the soft per-chunk storage proof limit to 3MB. PerReceiptHardStorageProofLimit, - /// Cross-shard congestion control according to https://github.com/near/NEPs/pull/539. + /// Cross-shard congestion control according to . CongestionControl, + /// Remove account with long storage key. + RemoveAccountWithLongStorageKey, // Stateless validation: Distribute state witness as reed solomon encoded parts PartialEncodedStateWitness, /// Size limits for transactions included in a ChunkStateWitness. WitnessTransactionLimits, + /// Size limit on outgoing receipts. + OutgoingReceiptsSizeLimit, + /// No chunk-only producers in stateless validation + NoChunkOnlyProducers, + /// Decrease the ratio of data parts in the Reed Solomon encoding for partial witness distribution. + ChangePartialWitnessDataPartsRequired, + /// Increase the `combined_transactions_size_limit` to 4MiB to allow higher throughput. + BiggerCombinedTransactionLimit, + /// Increase gas cost of sending receipt to another account to 50 TGas / MiB + HigherSendingCost, } impl ProtocolFeature { @@ -210,20 +225,28 @@ impl ProtocolFeature { ProtocolFeature::SimpleNightshadeV3 => 65, ProtocolFeature::DecreaseFunctionCallBaseCost => 66, ProtocolFeature::YieldExecution => 67, + ProtocolFeature::CongestionControl + | ProtocolFeature::RemoveAccountWithLongStorageKey => 68, + // Stateless validation features. + // TODO All of the stateless validation features should be collapsed + // into a single protocol feature. + ProtocolFeature::StatelessValidationV0 + | ProtocolFeature::LowerValidatorKickoutPercentForDebugging + | ProtocolFeature::SingleShardTracking + | ProtocolFeature::StateWitnessSizeLimit + | ProtocolFeature::PerReceiptHardStorageProofLimit + | ProtocolFeature::PartialEncodedStateWitness + | ProtocolFeature::WitnessTransactionLimits + | ProtocolFeature::OutgoingReceiptsSizeLimit + | ProtocolFeature::NoChunkOnlyProducers + | ProtocolFeature::ChangePartialWitnessDataPartsRequired + | ProtocolFeature::BiggerCombinedTransactionLimit + | ProtocolFeature::HigherSendingCost => 69, // This protocol version is reserved for use in resharding tests. An extra resharding // is simulated on top of the latest shard layout in production. Note that later // protocol versions will still have the production layout. - ProtocolFeature::SimpleNightshadeTestonly => 79, - - // StatelessNet features - ProtocolFeature::StatelessValidationV0 => 80, - ProtocolFeature::LowerValidatorKickoutPercentForDebugging => 81, - ProtocolFeature::SingleShardTracking => 82, - ProtocolFeature::StateWitnessSizeLimit => 83, - ProtocolFeature::PerReceiptHardStorageProofLimit => 85, - ProtocolFeature::PartialEncodedStateWitness => 86, - ProtocolFeature::WitnessTransactionLimits => 87, + ProtocolFeature::SimpleNightshadeTestonly => 100, // Nightly features #[cfg(feature = "protocol_feature_fix_staking_threshold")] @@ -235,7 +258,8 @@ impl ProtocolFeature { ProtocolFeature::EthImplicitAccounts => 138, #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] ProtocolFeature::NonrefundableStorage => 140, - ProtocolFeature::CongestionControl => 142, + #[cfg(feature = "protocol_feature_bls12381")] + ProtocolFeature::BLS12381 => 141, // TODO(#11201): When stabilizing this feature in mainnet, also remove the temporary code // that always enables this for mocknet (see config_mocknet function). ProtocolFeature::ShuffleShardAssignments => 143, @@ -250,12 +274,13 @@ impl ProtocolFeature { /// Current protocol version used on the mainnet. /// Some features (e. g. FixStorageUsage) require that there is at least one epoch with exactly /// the corresponding version -const STABLE_PROTOCOL_VERSION: ProtocolVersion = 67; +const STABLE_PROTOCOL_VERSION: ProtocolVersion = 69; /// Largest protocol version supported by the current binary. pub const PROTOCOL_VERSION: ProtocolVersion = if cfg!(feature = "statelessnet_protocol") { - // Current StatelessNet protocol version. - 87 + // Please note that congestion control and stateless validation are now + // stabilized but statelessnet should remain at its own version. + 82 } else if cfg!(feature = "nightly_protocol") { // On nightly, pick big enough version to support all features. 143 diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index 65f836a2a52..aa2d17e1db5 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -21,19 +21,18 @@ cfg-if.workspace = true chrono.workspace = true derive_more.workspace = true easy-ext.workspace = true -enum-map.workspace = true hex.workspace = true -itertools.workspace = true +itertools = { workspace = true, optional = true } num-rational.workspace = true once_cell.workspace = true +ordered-float.workspace = true primitive-types.workspace = true -rand.workspace = true -rand_chacha.workspace = true -reed-solomon-erasure.workspace = true +rand = { workspace = true, optional = true } +rand_chacha = { workspace = true, optional = true } +reed-solomon-erasure = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true serde_with.workspace = true -serde_yaml.workspace = true sha3.workspace = true smart-default.workspace = true stdx.workspace = true @@ -41,30 +40,40 @@ strum.workspace = true thiserror.workspace = true tracing.workspace = true zstd.workspace = true +enum-map.workspace = true -near-async.workspace = true +near-time = { workspace = true } near-crypto.workspace = true near-fmt.workspace = true near-primitives-core.workspace = true near-rpc-error-macro.workspace = true -near-vm-runner.workspace = true near-parameters.workspace = true [features] sandbox = [] -test_features = ["near-vm-runner/test_features"] +test_features = [] +solomon = ["reed-solomon-erasure", "itertools"] +rand = ["dep:rand", "rand_chacha", "near-crypto/rand", "itertools"] +clock = ["near-time/clock"] dump_errors_schema = ["near-rpc-error-macro/dump_errors_schema"] -protocol_feature_fix_staking_threshold = ["near-primitives-core/protocol_feature_fix_staking_threshold"] -protocol_feature_fix_contract_loading_cost = ["near-primitives-core/protocol_feature_fix_contract_loading_cost"] -protocol_feature_reject_blocks_with_outdated_protocol_version = ["near-primitives-core/protocol_feature_reject_blocks_with_outdated_protocol_version"] -protocol_feature_nonrefundable_transfer_nep491 = ["near-primitives-core/protocol_feature_nonrefundable_transfer_nep491"] +protocol_feature_fix_staking_threshold = [ + "near-primitives-core/protocol_feature_fix_staking_threshold", +] +protocol_feature_fix_contract_loading_cost = [ + "near-primitives-core/protocol_feature_fix_contract_loading_cost", +] +protocol_feature_reject_blocks_with_outdated_protocol_version = [ + "near-primitives-core/protocol_feature_reject_blocks_with_outdated_protocol_version", +] +protocol_feature_nonrefundable_transfer_nep491 = [ + "near-primitives-core/protocol_feature_nonrefundable_transfer_nep491", +] nightly = [ - "near-async/nightly", "near-fmt/nightly", "near-parameters/nightly", "near-primitives-core/nightly", - "near-vm-runner/nightly", + "near-primitives/nightly", "nightly_protocol", "protocol_feature_fix_contract_loading_cost", "protocol_feature_fix_staking_threshold", @@ -73,16 +82,13 @@ nightly = [ ] nightly_protocol = [ - "near-async/nightly_protocol", "near-fmt/nightly_protocol", "near-parameters/nightly_protocol", "near-primitives-core/nightly_protocol", - "near-vm-runner/nightly_protocol", + "near-primitives/nightly_protocol", ] -statelessnet_protocol = [ - "near-primitives-core/statelessnet_protocol", -] +statelessnet_protocol = ["near-primitives-core/statelessnet_protocol"] new_epoch_sync = [] @@ -90,11 +96,15 @@ new_epoch_sync = [] calimero_zero_storage = [] [dev-dependencies] +chrono = { workspace = true, features = ["clock"] } +near-primitives = { workspace = true, features = ["clock", "solomon", "rand"] } assert_matches.workspace = true bencher.workspace = true bolero.workspace = true insta.workspace = true expect-test.workspace = true +regex.workspace = true + [[bench]] name = "serialization" diff --git a/core/primitives/benches/serialization.rs b/core/primitives/benches/serialization.rs index 4ec41c1ccfd..81d5d0c38a6 100644 --- a/core/primitives/benches/serialization.rs +++ b/core/primitives/benches/serialization.rs @@ -2,9 +2,8 @@ extern crate bencher; use bencher::{black_box, Bencher}; -use borsh::BorshDeserialize; -use near_async::time::Clock; +use borsh::BorshDeserialize; use near_crypto::{KeyType, PublicKey, Signature}; use near_primitives::account::Account; use near_primitives::block::{genesis_chunks, Block}; @@ -18,6 +17,7 @@ use near_primitives::types::{EpochId, StateRoot}; use near_primitives::validator_signer::InMemoryValidatorSigner; use near_primitives::version::PROTOCOL_VERSION; use near_primitives_core::types::MerkleHash; +use near_time::Clock; use num_rational::Rational32; fn create_transaction() -> SignedTransaction { @@ -39,7 +39,15 @@ fn create_transaction() -> SignedTransaction { } fn create_block() -> Block { - let genesis_chunks = genesis_chunks(vec![StateRoot::new()], &[0], 1_000, 0, PROTOCOL_VERSION); + let shard_ids = vec![0]; + let genesis_chunks = genesis_chunks( + vec![StateRoot::new()], + vec![Default::default(); shard_ids.len()], + &shard_ids, + 1_000, + 0, + PROTOCOL_VERSION, + ); let genesis = Block::genesis( PROTOCOL_VERSION, genesis_chunks.into_iter().map(|chunk| chunk.take_header()).collect(), @@ -68,10 +76,11 @@ fn create_block() -> Block { Some(0), vec![], vec![], - &signer, + &signer.into(), CryptoHash::default(), CryptoHash::default(), - Clock::real().now_utc(), + Clock::real(), + None, ) } diff --git a/core/primitives/src/action/delegate.rs b/core/primitives/src/action/delegate.rs index 864c2f6076a..a25c267e66c 100644 --- a/core/primitives/src/action/delegate.rs +++ b/core/primitives/src/action/delegate.rs @@ -1,6 +1,6 @@ //! DelegateAction is a type of action to support meta transactions. //! -//! NEP: https://github.com/near/NEPs/pull/366 +//! NEP: //! This is the module containing the types introduced for delegate actions. pub use self::private_non_delegate_action::NonDelegateAction; diff --git a/core/primitives/src/block.rs b/core/primitives/src/block.rs index 5a254f75a96..433d060f7f9 100644 --- a/core/primitives/src/block.rs +++ b/core/primitives/src/block.rs @@ -4,25 +4,19 @@ use crate::block::BlockValidityError::{ }; use crate::block_body::{BlockBody, BlockBodyV1, ChunkEndorsementSignatures}; pub use crate::block_header::*; -use crate::challenge::{Challenges, ChallengesResult}; +use crate::challenge::Challenges; use crate::checked_feature; -use crate::congestion_info::{CongestionInfo, ExtendedCongestionInfo}; -use crate::hash::{hash, CryptoHash}; +use crate::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; +use crate::hash::CryptoHash; use crate::merkle::{merklize, verify_path, MerklePath}; use crate::num_rational::Rational32; -use crate::sharding::{ - ChunkHashHeight, EncodedShardChunk, ShardChunk, ShardChunkHeader, ShardChunkHeaderV1, -}; -use crate::types::{Balance, BlockHeight, EpochId, Gas, NumBlocks, StateRoot}; -use crate::validator_signer::{EmptyValidatorSigner, ValidatorSigner}; +use crate::sharding::{ChunkHashHeight, ShardChunkHeader, ShardChunkHeaderV1}; +use crate::types::{Balance, BlockHeight, EpochId, Gas}; use crate::version::{ProtocolVersion, SHARD_CHUNK_HEADER_UPGRADE_VERSION}; use borsh::{BorshDeserialize, BorshSerialize}; -use near_async::time::Utc; -use near_crypto::Signature; -use near_primitives_core::types::ShardId; +use near_time::Utc; use primitive_types::U256; -use reed_solomon_erasure::galois_8::ReedSolomon; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::ops::Index; use std::sync::Arc; @@ -90,14 +84,19 @@ pub enum Block { BlockV4(Arc), } +#[cfg(feature = "solomon")] +type ShardChunkReedSolomon = reed_solomon_erasure::galois_8::ReedSolomon; + +#[cfg(feature = "solomon")] pub fn genesis_chunks( - state_roots: Vec, - shard_ids: &[ShardId], + state_roots: Vec, + congestion_infos: Vec>, + shard_ids: &[crate::types::ShardId], initial_gas_limit: Gas, genesis_height: BlockHeight, genesis_protocol_version: ProtocolVersion, -) -> Vec { - let rs = ReedSolomon::new(1, 2).unwrap(); +) -> Vec { + let rs = ShardChunkReedSolomon::new(1, 2).unwrap(); let state_roots = if state_roots.len() == shard_ids.len() { state_roots } else { @@ -105,35 +104,66 @@ pub fn genesis_chunks( std::iter::repeat(state_roots[0]).take(shard_ids.len()).collect() }; - shard_ids - .into_iter() - .zip(state_roots) - .map(|(&shard_id, state_root)| { - let (encoded_chunk, _) = EncodedShardChunk::new( - CryptoHash::default(), - state_root, - CryptoHash::default(), - genesis_height, - shard_id, - &rs, - 0, - initial_gas_limit, - 0, - CryptoHash::default(), - vec![], - vec![], - &[], - CryptoHash::default(), - CongestionInfo::default(), - &EmptyValidatorSigner::default(), - genesis_protocol_version, - ) - .expect("Failed to decode genesis chunk"); - let mut chunk = encoded_chunk.decode_chunk(1).expect("Failed to decode genesis chunk"); - chunk.set_height_included(genesis_height); - chunk - }) - .collect() + let mut chunks = vec![]; + + let num = shard_ids.len(); + assert_eq!(state_roots.len(), num); + + for shard_id in 0..num { + let state_root = state_roots[shard_id]; + let congestion_info = congestion_infos[shard_id]; + let shard_id = shard_id as crate::types::ShardId; + + let encoded_chunk = genesis_chunk( + &rs, + genesis_protocol_version, + genesis_height, + initial_gas_limit, + shard_id, + state_root, + congestion_info, + ); + let mut chunk = encoded_chunk.decode_chunk(1).expect("Failed to decode genesis chunk"); + chunk.set_height_included(genesis_height); + chunks.push(chunk); + } + + chunks +} + +// Creates the genesis encoded shard chunk. The genesis chunks have most of the +// fields set to defaults. The remaining fields are set to the provided values. +#[cfg(feature = "solomon")] +fn genesis_chunk( + rs: &ShardChunkReedSolomon, + genesis_protocol_version: u32, + genesis_height: u64, + initial_gas_limit: u64, + shard_id: u64, + state_root: CryptoHash, + congestion_info: Option, +) -> crate::sharding::EncodedShardChunk { + let (encoded_chunk, _) = crate::sharding::EncodedShardChunk::new( + CryptoHash::default(), + state_root, + CryptoHash::default(), + genesis_height, + shard_id, + rs, + 0, + initial_gas_limit, + 0, + CryptoHash::default(), + vec![], + vec![], + &[], + CryptoHash::default(), + congestion_info, + &crate::validator_signer::EmptyValidatorSigner::default().into(), + genesis_protocol_version, + ) + .expect("Failed to decode genesis chunk"); + encoded_chunk } impl Block { @@ -244,29 +274,32 @@ impl Block { } /// Produces new block from header of previous block, current state root and set of transactions. + #[cfg(feature = "clock")] pub fn produce( this_epoch_protocol_version: ProtocolVersion, next_epoch_protocol_version: ProtocolVersion, prev: &BlockHeader, height: BlockHeight, - block_ordinal: NumBlocks, + block_ordinal: crate::types::NumBlocks, chunks: Vec, chunk_endorsements: Vec, epoch_id: EpochId, next_epoch_id: EpochId, epoch_sync_data_hash: Option, - approvals: Vec>>, + approvals: Vec>>, gas_price_adjustment_rate: Rational32, min_gas_price: Balance, max_gas_price: Balance, minted_amount: Option, - challenges_result: ChallengesResult, + challenges_result: crate::challenge::ChallengesResult, challenges: Challenges, - signer: &dyn ValidatorSigner, + signer: &crate::validator_signer::ValidatorSigner, next_bp_hash: CryptoHash, block_merkle_root: CryptoHash, - timestamp: Utc, + clock: near_time::Clock, + sandbox_delta_time: Option, ) -> Self { + use crate::hash::hash; // Collect aggregate of validators and gas usage/limits from chunks. let mut prev_validator_proposals = vec![]; let mut gas_used = 0; @@ -295,7 +328,11 @@ impl Block { ); let new_total_supply = prev.total_supply() + minted_amount.unwrap_or(0) - balance_burnt; - let now = timestamp.unix_timestamp_nanos() as u64; + let now = clock.now_utc().unix_timestamp_nanos() as u64; + #[cfg(feature = "sandbox")] + let now = now + sandbox_delta_time.unwrap().whole_nanoseconds() as u64; + #[cfg(not(feature = "sandbox"))] + debug_assert!(sandbox_delta_time.is_none()); let time = if now <= prev.raw_timestamp() { prev.raw_timestamp() + 1 } else { now }; let (vrf_value, vrf_proof) = signer.compute_vrf_with_proof(prev.random_value().as_ref()); @@ -360,6 +397,7 @@ impl Block { next_bp_hash, block_merkle_root, prev.height(), + clock, ); Self::block_from_protocol_version( @@ -591,8 +629,8 @@ impl Block { } } - pub fn shards_congestion_info(&self) -> HashMap { - let mut result = HashMap::new(); + pub fn block_congestion_info(&self) -> BlockCongestionInfo { + let mut result = BTreeMap::new(); for chunk in self.chunks().iter() { let shard_id = chunk.shard_id(); @@ -609,7 +647,7 @@ impl Block { result.insert(shard_id, extended_congestion_info); } } - result + BlockCongestionInfo::new(result) } pub fn hash(&self) -> &CryptoHash { @@ -773,8 +811,8 @@ impl Tip { height: header.height(), last_block_hash: *header.hash(), prev_block_hash: *header.prev_hash(), - epoch_id: header.epoch_id().clone(), - next_epoch_id: header.next_epoch_id().clone(), + epoch_id: *header.epoch_id(), + next_epoch_id: *header.next_epoch_id(), } } } diff --git a/core/primitives/src/block_header.rs b/core/primitives/src/block_header.rs index c2e485c4760..e6989c7dfb8 100644 --- a/core/primitives/src/block_header.rs +++ b/core/primitives/src/block_header.rs @@ -5,10 +5,10 @@ use crate::network::PeerId; use crate::types::validator_stake::{ValidatorStake, ValidatorStakeIter, ValidatorStakeV1}; use crate::types::{AccountId, Balance, BlockHeight, EpochId, MerkleHash, NumBlocks}; use crate::validator_signer::ValidatorSigner; -use crate::version::{get_protocol_version, ProtocolVersion, PROTOCOL_VERSION}; +use crate::version::ProtocolVersion; use borsh::{BorshDeserialize, BorshSerialize}; -use near_async::time::Utc; use near_crypto::{KeyType, PublicKey, Signature}; +use near_time::Utc; use std::sync::Arc; #[derive( @@ -248,7 +248,7 @@ impl Approval { parent_hash: CryptoHash, parent_height: BlockHeight, target_height: BlockHeight, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, ) -> Self { let inner = ApprovalInner::new(&parent_hash, parent_height, target_height); let signature = signer.sign_approval(&inner, target_height); @@ -407,6 +407,7 @@ impl BlockHeader { combine_hash(&hash_inner, &prev_hash) } + #[cfg(feature = "clock")] pub fn new( this_epoch_protocol_version: ProtocolVersion, next_epoch_protocol_version: ProtocolVersion, @@ -429,7 +430,7 @@ impl BlockHeader { next_gas_price: Balance, total_supply: Balance, challenges_result: ChallengesResult, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, last_final_block: CryptoHash, last_ds_final_block: CryptoHash, epoch_sync_data_hash: Option, @@ -437,6 +438,7 @@ impl BlockHeader { next_bp_hash: CryptoHash, block_merkle_root: CryptoHash, prev_height: BlockHeight, + clock: near_time::Clock, ) -> Self { let inner_lite = BlockHeaderInnerLite { height, @@ -475,7 +477,7 @@ impl BlockHeader { last_final_block, last_ds_final_block, approvals, - latest_protocol_version: PROTOCOL_VERSION, + latest_protocol_version: crate::version::PROTOCOL_VERSION, }; let (hash, signature) = signer.sign_block_header_parts( prev_hash, @@ -507,7 +509,7 @@ impl BlockHeader { last_final_block, last_ds_final_block, approvals, - latest_protocol_version: PROTOCOL_VERSION, + latest_protocol_version: crate::version::PROTOCOL_VERSION, }; let (hash, signature) = signer.sign_block_header_parts( prev_hash, @@ -539,7 +541,10 @@ impl BlockHeader { prev_height, epoch_sync_data_hash, approvals, - latest_protocol_version: get_protocol_version(next_epoch_protocol_version), + latest_protocol_version: crate::version::get_protocol_version( + next_epoch_protocol_version, + clock, + ), }; let (hash, signature) = signer.sign_block_header_parts( prev_hash, @@ -572,7 +577,10 @@ impl BlockHeader { prev_height, epoch_sync_data_hash, approvals, - latest_protocol_version: get_protocol_version(next_epoch_protocol_version), + latest_protocol_version: crate::version::get_protocol_version( + next_epoch_protocol_version, + clock, + ), }; let (hash, signature) = signer.sign_block_header_parts( prev_hash, diff --git a/core/primitives/src/challenge.rs b/core/primitives/src/challenge.rs index 571fcd15571..dcd296dfea7 100644 --- a/core/primitives/src/challenge.rs +++ b/core/primitives/src/challenge.rs @@ -122,7 +122,7 @@ impl Challenge { self.hash = CryptoHash::hash_borsh(&self.body); } - pub fn produce(body: ChallengeBody, signer: &dyn ValidatorSigner) -> Self { + pub fn produce(body: ChallengeBody, signer: &ValidatorSigner) -> Self { let (hash, signature) = signer.sign_challenge(&body); Self { body, account_id: signer.validator_id().clone(), signature, hash } } diff --git a/core/primitives/src/congestion_info.rs b/core/primitives/src/congestion_info.rs index 292e369b216..f6c6d0c3464 100644 --- a/core/primitives/src/congestion_info.rs +++ b/core/primitives/src/congestion_info.rs @@ -1,7 +1,10 @@ +use std::collections::BTreeMap; + use crate::errors::RuntimeError; use borsh::{BorshDeserialize, BorshSerialize}; use near_parameters::config::CongestionControlConfig; use near_primitives_core::types::{Gas, ShardId}; +use ordered_float::NotNan; /// This class combines the congestion control config, congestion info and /// missed chunks count. It contains the main congestion control logic and @@ -74,7 +77,7 @@ impl CongestionControl { } /// How much gas another shard can send to us in the next block. - pub fn outgoing_limit(&self, sender_shard: ShardId) -> Gas { + pub fn outgoing_gas_limit(&self, sender_shard: ShardId) -> Gas { let congestion = self.congestion_level(); // note: using float equality is okay here because @@ -91,6 +94,17 @@ impl CongestionControl { } } + /// How much data another shard can send to us in the next block. + pub fn outgoing_size_limit(&self, sender_shard: ShardId) -> Gas { + if sender_shard == self.info.allowed_shard() as u64 { + // The allowed shard is allowed to send more data to us. + self.config.outgoing_receipts_big_size_limit + } else { + // Other shards have a low standard limit. + self.config.outgoing_receipts_usual_size_limit + } + } + /// How much gas we accept for executing new transactions going to any /// uncongested shards. pub fn process_tx_limit(&self) -> Gas { @@ -98,11 +112,54 @@ impl CongestionControl { } /// Whether we can accept new transaction with the receiver set to this shard. - pub fn shard_accepts_transactions(&self) -> bool { - self.congestion_level() < self.config.reject_tx_congestion_threshold + /// + /// If the shard doesn't accept new transaction, provide the reason for + /// extra debugging information. + pub fn shard_accepts_transactions(&self) -> ShardAcceptsTransactions { + let incoming_congestion = self.incoming_congestion(); + let outgoing_congestion = self.outgoing_congestion(); + let memory_congestion = self.memory_congestion(); + let missed_chunks_congestion = self.missed_chunks_congestion(); + + let congestion_level = incoming_congestion + .max(outgoing_congestion) + .max(memory_congestion) + .max(missed_chunks_congestion); + + // Convert to NotNan here, if not possible, the max above is already meaningless. + let congestion_level = + NotNan::new(congestion_level).unwrap_or_else(|_| NotNan::new(1.0).unwrap()); + if *congestion_level < self.config.reject_tx_congestion_threshold { + return ShardAcceptsTransactions::Yes; + } + + let reason = if missed_chunks_congestion >= *congestion_level { + RejectTransactionReason::MissedChunks { missed_chunks: self.missed_chunks_count } + } else if incoming_congestion >= *congestion_level { + RejectTransactionReason::IncomingCongestion { congestion_level } + } else if outgoing_congestion >= *congestion_level { + RejectTransactionReason::OutgoingCongestion { congestion_level } + } else { + RejectTransactionReason::MemoryCongestion { congestion_level } + }; + ShardAcceptsTransactions::No(reason) } } +/// Result of [`CongestionControl::shard_accepts_transactions`]. +pub enum ShardAcceptsTransactions { + Yes, + No(RejectTransactionReason), +} + +/// Detailed information for why a shard rejects new transactions. +pub enum RejectTransactionReason { + IncomingCongestion { congestion_level: NotNan }, + OutgoingCongestion { congestion_level: NotNan }, + MemoryCongestion { congestion_level: NotNan }, + MissedChunks { missed_chunks: u64 }, +} + /// Stores the congestion level of a shard. /// /// The CongestionInfo is a part of the ChunkHeader. It is versioned and each @@ -136,9 +193,7 @@ impl CongestionInfo { // if the congestion info was correctly set in the chunk header based on the // information from the chunk extra. // - // TODO(congestion_control) validate allowed shard correctly - // If the shard is fully congested the any of the other shards can be the allowed shard. - // If the shard is not fully congested the allowed shard should be set to self. + // TODO(congestion_control) validate allowed shard pub fn validate_extra_and_header(extra: &CongestionInfo, header: &CongestionInfo) -> bool { match (extra, header) { (CongestionInfo::V1(extra), CongestionInfo::V1(header)) => { @@ -183,10 +238,9 @@ impl CongestionInfo { pub fn add_receipt_bytes(&mut self, bytes: u64) -> Result<(), RuntimeError> { match self { CongestionInfo::V1(inner) => { - inner.receipt_bytes = inner - .receipt_bytes - .checked_add(bytes) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + inner.receipt_bytes = inner.receipt_bytes.checked_add(bytes).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow("add_receipt_bytes".into()) + })?; } } Ok(()) @@ -195,10 +249,9 @@ impl CongestionInfo { pub fn remove_receipt_bytes(&mut self, bytes: u64) -> Result<(), RuntimeError> { match self { CongestionInfo::V1(inner) => { - inner.receipt_bytes = inner - .receipt_bytes - .checked_sub(bytes) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + inner.receipt_bytes = inner.receipt_bytes.checked_sub(bytes).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow("remove_receipt_bytes".into()) + })?; } } Ok(()) @@ -207,10 +260,10 @@ impl CongestionInfo { pub fn add_delayed_receipt_gas(&mut self, gas: Gas) -> Result<(), RuntimeError> { match self { CongestionInfo::V1(inner) => { - inner.delayed_receipts_gas = inner - .delayed_receipts_gas - .checked_add(gas as u128) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + inner.delayed_receipts_gas = + inner.delayed_receipts_gas.checked_add(gas as u128).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow("add_delayed_receipt_gas".into()) + })?; } } Ok(()) @@ -219,10 +272,10 @@ impl CongestionInfo { pub fn remove_delayed_receipt_gas(&mut self, gas: Gas) -> Result<(), RuntimeError> { match self { CongestionInfo::V1(inner) => { - inner.delayed_receipts_gas = inner - .delayed_receipts_gas - .checked_sub(gas as u128) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + inner.delayed_receipts_gas = + inner.delayed_receipts_gas.checked_sub(gas as u128).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow("remove_delayed_receipt_gas".into()) + })?; } } Ok(()) @@ -231,10 +284,10 @@ impl CongestionInfo { pub fn add_buffered_receipt_gas(&mut self, gas: Gas) -> Result<(), RuntimeError> { match self { CongestionInfo::V1(inner) => { - inner.buffered_receipts_gas = inner - .buffered_receipts_gas - .checked_add(gas as u128) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + inner.buffered_receipts_gas = + inner.buffered_receipts_gas.checked_add(gas as u128).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow("add_buffered_receipt_gas".into()) + })?; } } Ok(()) @@ -243,10 +296,12 @@ impl CongestionInfo { pub fn remove_buffered_receipt_gas(&mut self, gas: Gas) -> Result<(), RuntimeError> { match self { CongestionInfo::V1(inner) => { - inner.buffered_receipts_gas = inner - .buffered_receipts_gas - .checked_sub(gas as u128) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + inner.buffered_receipts_gas = + inner.buffered_receipts_gas.checked_sub(gas as u128).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow( + "remove_buffered_receipt_gas".into(), + ) + })?; } } Ok(()) @@ -274,49 +329,86 @@ impl CongestionInfo { /// Computes and sets the `allowed_shard` field. /// - /// If in a fully congested state, decide which shard of `other_shards` is - /// allowed to forward to `own_shard` this round. In this case, we stop all - /// of `other_shards` from sending anything to `own_shard`. But to guarantee - /// progress, we allow one shard of `other_shards` to send - /// `allowed_shard_outgoing_gas` in the next chunk. + /// If in a fully congested state, decide which shard of the shards is + /// allowed to forward gas to `own_shard` this round. In this case, we stop all + /// of the shards from sending anything to `own_shard`. But to guarantee + /// progress, we allow one shard to send `allowed_shard_outgoing_gas` + /// in the next chunk. /// - /// Otherwise, when the congestion level is < 1.0, set `allowed_shard` to - /// `own_shard`. The field is ignored in this case but we still want a - /// unique representation. + /// It is also used to determine the size limit for outgoing receipts from sender shards. + /// Only the allowed shard can send receipts of size `outgoing_receipts_big_size_limit`. + /// Other shards can only send receipts of size `outgoing_receipts_usual_size_limit`. pub fn finalize_allowed_shard( &mut self, own_shard: ShardId, - other_shards: &[ShardId], + all_shards: &[ShardId], congestion_seed: u64, - config: &CongestionControlConfig, ) { - let congestion_level = self.localized_congestion_level(config); - let allowed_shard = - Self::get_new_allowed_shard(own_shard, other_shards, congestion_seed, congestion_level); + let allowed_shard = Self::get_new_allowed_shard(own_shard, all_shards, congestion_seed); self.set_allowed_shard(allowed_shard as u16); } fn get_new_allowed_shard( own_shard: ShardId, - other_shards: &[ShardId], + all_shards: &[ShardId], congestion_seed: u64, - congestion_level: f64, ) -> ShardId { - if congestion_level < 1.0 { - return own_shard; - } - if let Some(index) = congestion_seed.checked_rem(other_shards.len() as u64) { + if let Some(index) = congestion_seed.checked_rem(all_shards.len() as u64) { // round robin for other shards based on the seed - return *other_shards + return *all_shards .get(index as usize) .expect("`checked_rem` should have ensured array access is in bound"); } - // checked_rem failed, hence other_shards.len() is 0 + // checked_rem failed, hence all_shards.len() is 0 // own_shard is the only choice. return own_shard; } } +/// The block congestion info contains the congestion info for all shards in the +/// block extended with the missed chunks count. +#[derive(Clone, Debug, Default)] +pub struct BlockCongestionInfo { + /// The per shard congestion info. It's important that the data structure is + /// deterministic because the allowed shard id selection depends on the + /// order of shard ids in this map. Ideally it should also be sorted by shard id. + shards_congestion_info: BTreeMap, +} + +impl BlockCongestionInfo { + pub fn new(shards_congestion_info: BTreeMap) -> Self { + Self { shards_congestion_info } + } + + pub fn iter(&self) -> impl Iterator { + self.shards_congestion_info.iter() + } + + pub fn all_shards(&self) -> Vec { + self.shards_congestion_info.keys().copied().collect() + } + + pub fn get(&self, shard_id: &ShardId) -> Option<&ExtendedCongestionInfo> { + self.shards_congestion_info.get(shard_id) + } + + pub fn get_mut(&mut self, shard_id: &ShardId) -> Option<&mut ExtendedCongestionInfo> { + self.shards_congestion_info.get_mut(shard_id) + } + + pub fn insert( + &mut self, + shard_id: ShardId, + value: ExtendedCongestionInfo, + ) -> Option { + self.shards_congestion_info.insert(shard_id, value) + } + + pub fn is_empty(&self) -> bool { + self.shards_congestion_info.is_empty() + } +} + /// The extended congestion info contains the congestion info and extra /// information extracted from the block that is needed for congestion control. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -392,6 +484,16 @@ fn mix(left: u64, right: u64, ratio: f64) -> u64 { return total.round() as u64; } +impl ShardAcceptsTransactions { + pub fn is_yes(&self) -> bool { + matches!(self, ShardAcceptsTransactions::Yes) + } + + pub fn is_no(&self) -> bool { + !self.is_yes() + } +} + #[cfg(test)] mod tests { use near_parameters::RuntimeConfigStore; @@ -471,10 +573,10 @@ mod tests { assert_eq!(0.0, congestion_control.outgoing_congestion()); assert_eq!(0.0, congestion_control.congestion_level()); - assert!(config.max_outgoing_gas.abs_diff(congestion_control.outgoing_limit(0)) <= 1); + assert!(config.max_outgoing_gas.abs_diff(congestion_control.outgoing_gas_limit(0)) <= 1); assert!(config.max_tx_gas.abs_diff(congestion_control.process_tx_limit()) <= 1); - assert!(congestion_control.shard_accepts_transactions()); + assert!(congestion_control.shard_accepts_transactions().is_yes()); } #[test] @@ -494,8 +596,8 @@ mod tests { let control = CongestionControl::new(config, info, 0); assert_eq!(1.0, control.congestion_level()); // fully congested, no more forwarding allowed - assert_eq!(0, control.outgoing_limit(1)); - assert!(!control.shard_accepts_transactions()); + assert_eq!(0, control.outgoing_gas_limit(1)); + assert!(control.shard_accepts_transactions().is_no()); // processing to other shards is not restricted by memory congestion assert_eq!(config.max_tx_gas, control.process_tx_limit()); } @@ -508,10 +610,10 @@ mod tests { assert_eq!( (0.5 * config.min_outgoing_gas as f64 + 0.5 * config.max_outgoing_gas as f64) as u64, - control.outgoing_limit(1) + control.outgoing_gas_limit(1) ); // at 50%, still no new transactions are allowed - assert!(!control.shard_accepts_transactions()); + assert!(control.shard_accepts_transactions().is_no()); } // reduce congestion to 1/8 @@ -522,10 +624,10 @@ mod tests { assert_eq!( (0.125 * config.min_outgoing_gas as f64 + 0.875 * config.max_outgoing_gas as f64) as u64, - control.outgoing_limit(1) + control.outgoing_gas_limit(1) ); // at 12.5%, new transactions are allowed (threshold is 0.25) - assert!(control.shard_accepts_transactions()); + assert!(control.shard_accepts_transactions().is_yes()); } } @@ -546,8 +648,8 @@ mod tests { let control = CongestionControl::new(config, info, 0); assert_eq!(1.0, control.congestion_level()); // fully congested, no more forwarding allowed - assert_eq!(0, control.outgoing_limit(1)); - assert!(!control.shard_accepts_transactions()); + assert_eq!(0, control.outgoing_gas_limit(1)); + assert!(control.shard_accepts_transactions().is_no()); // processing to other shards is restricted by own incoming congestion assert_eq!(config.min_tx_gas, control.process_tx_limit()); } @@ -560,10 +662,10 @@ mod tests { assert_eq!( (0.5 * config.min_outgoing_gas as f64 + 0.5 * config.max_outgoing_gas as f64) as u64, - control.outgoing_limit(1) + control.outgoing_gas_limit(1) ); // at 50%, still no new transactions to us are allowed - assert!(!control.shard_accepts_transactions()); + assert!(control.shard_accepts_transactions().is_no()); // but we accept new transactions to other shards assert_eq!( (0.5 * config.min_tx_gas as f64 + 0.5 * config.max_tx_gas as f64) as u64, @@ -579,10 +681,10 @@ mod tests { assert_eq!( (0.125 * config.min_outgoing_gas as f64 + 0.875 * config.max_outgoing_gas as f64) as u64, - control.outgoing_limit(1) + control.outgoing_gas_limit(1) ); // at 12.5%, new transactions are allowed (threshold is 0.25) - assert!(control.shard_accepts_transactions()); + assert!(control.shard_accepts_transactions().is_yes()); assert_eq!( (0.125 * config.min_tx_gas as f64 + 0.875 * config.max_tx_gas as f64) as u64, control.process_tx_limit() @@ -606,8 +708,8 @@ mod tests { let control = CongestionControl::new(config, info, 0); assert_eq!(1.0, control.congestion_level()); // fully congested, no more forwarding allowed - assert_eq!(0, control.outgoing_limit(1)); - assert!(!control.shard_accepts_transactions()); + assert_eq!(0, control.outgoing_gas_limit(1)); + assert!(control.shard_accepts_transactions().is_no()); // processing to other shards is not restricted by own outgoing congestion assert_eq!(config.max_tx_gas, control.process_tx_limit()); @@ -617,10 +719,10 @@ mod tests { assert_eq!(0.5, control.congestion_level()); assert_eq!( (0.5 * config.min_outgoing_gas as f64 + 0.5 * config.max_outgoing_gas as f64) as u64, - control.outgoing_limit(1) + control.outgoing_gas_limit(1) ); // at 50%, still no new transactions to us are allowed - assert!(!control.shard_accepts_transactions()); + assert!(control.shard_accepts_transactions().is_no()); // reduce congestion to 1/8 info.remove_buffered_receipt_gas(3 * config.max_congestion_outgoing_gas / 8).unwrap(); @@ -629,10 +731,10 @@ mod tests { assert_eq!( (0.125 * config.min_outgoing_gas as f64 + 0.875 * config.max_outgoing_gas as f64) as u64, - control.outgoing_limit(1) + control.outgoing_gas_limit(1) ); // at 12.5%, new transactions are allowed (threshold is 0.25) - assert!(control.shard_accepts_transactions()); + assert!(control.shard_accepts_transactions().is_yes()); } #[test] @@ -641,7 +743,11 @@ mod tests { return; } - let config = get_config(); + // The default config is quite restricting, allow more missed chunks for + // this test to check the middle cases. + let mut config = get_config(); + config.max_congestion_missed_chunks = 10; + let info = CongestionInfo::default(); // Test missed chunks congestion without any other congestion @@ -684,50 +790,55 @@ mod tests { return; } - let config = get_config(); + // The default config is quite restricting, allow more missed chunks for + // this test to check the middle cases. + let mut config = get_config(); + config.max_congestion_missed_chunks = 10; // Setup half congested congestion info. let mut info = CongestionInfo::default(); info.add_buffered_receipt_gas(config.max_congestion_outgoing_gas / 2).unwrap(); let shard = 2; - let other_shards = [0, 1, 3, 4]; + let all_shards = [0, 1, 2, 3, 4]; // Test without missed chunks congestion. let missed_chunks_count = 0; let mut control = CongestionControl::new(config, info, missed_chunks_count); - control.info.finalize_allowed_shard(shard, &other_shards, 3, &config); + control.info.finalize_allowed_shard(shard, &all_shards, 3); let expected_outgoing_limit = 0.5 * config.min_outgoing_gas as f64 + 0.5 * config.max_outgoing_gas as f64; - for other_shard in other_shards { - assert_eq!(control.outgoing_limit(other_shard), expected_outgoing_limit as u64); + for shard in all_shards { + assert_eq!(control.outgoing_gas_limit(shard), expected_outgoing_limit as u64); } // Test with some missed chunks congestion. let missed_chunks_count = 8; let mut control = CongestionControl::new(config, info, missed_chunks_count); - control.info.finalize_allowed_shard(shard, &other_shards, 3, &config); + control.info.finalize_allowed_shard(shard, &all_shards, 3); let expected_outgoing_limit = mix(config.max_outgoing_gas, config.min_outgoing_gas, 0.8) as f64; - for other_shard in other_shards { - assert_eq!(control.outgoing_limit(other_shard), expected_outgoing_limit as u64); + for shard in all_shards { + assert_eq!(control.outgoing_gas_limit(shard), expected_outgoing_limit as u64); } // Test with full missed chunks congestion. let missed_chunks_count = config.max_congestion_missed_chunks; let mut control = CongestionControl::new(config, info, missed_chunks_count); - control.info.finalize_allowed_shard(shard, &other_shards, 3, &config); + control.info.finalize_allowed_shard(shard, &all_shards, 3); - // The allowed shard should be set to own shard. None of the other - // shards should be allowed to send anything. - let expected_outgoing_limit = 0; - for other_shard in other_shards { - assert_eq!(control.outgoing_limit(other_shard), expected_outgoing_limit as u64); + // Full congestion - only the allowed shard should be able to send something. + for shard in all_shards { + if shard == control.info.allowed_shard() as u64 { + assert_eq!(control.outgoing_gas_limit(shard), config.allowed_shard_outgoing_gas); + } else { + assert_eq!(control.outgoing_gas_limit(shard), 0); + } } } } diff --git a/core/primitives/src/epoch_manager.rs b/core/primitives/src/epoch_manager.rs index 33bd4d6d4f9..a251355d61a 100644 --- a/core/primitives/src/epoch_manager.rs +++ b/core/primitives/src/epoch_manager.rs @@ -33,10 +33,14 @@ pub struct EpochConfig { pub num_block_producer_seats_per_shard: Vec, /// Expected number of hidden validator seats per each shard. pub avg_hidden_validator_seats_per_shard: Vec, - /// Criterion for kicking out block producers. + /// Threshold for kicking out block producers. pub block_producer_kickout_threshold: u8, - /// Criterion for kicking out chunk producers. + /// Threshold for kicking out chunk producers. pub chunk_producer_kickout_threshold: u8, + /// Threshold for kicking out nodes which are only chunk validators. + pub chunk_validator_only_kickout_threshold: u8, + /// Number of target chunk validator mandates for each shard. + pub target_validator_mandates_per_shard: NumSeats, /// Max ratio of validators that we can kick out in an epoch pub validator_max_kickout_stake_perc: u8, /// Online minimum threshold below which validator doesn't receive reward. @@ -186,6 +190,7 @@ impl AllEpochConfig { if checked_feature!("stable", LowerValidatorKickoutPercentForDebugging, protocol_version) { config.block_producer_kickout_threshold = 50; config.chunk_producer_kickout_threshold = 50; + config.chunk_validator_only_kickout_threshold = 50; } } @@ -252,19 +257,26 @@ impl AllEpochConfig { config.validator_selection_config.num_chunk_only_producer_seats = 200; } - // Adjust the number of block and chunk producers for all chains except - // mainnet, to make it easier to test the change. - if chain_id != near_primitives_core::chains::MAINNET + // Adjust the number of block and chunk producers for testnet, to make it easier to test the change. + if chain_id == near_primitives_core::chains::TESTNET && checked_feature!("stable", TestnetFewerBlockProducers, protocol_version) { let shard_ids = config.shard_layout.shard_ids(); - // Decrease the number of block producers from 100 to 20. + // Decrease the number of block and chunk producers from 100 to 20. config.num_block_producer_seats = 20; + if checked_feature!("stable", NoChunkOnlyProducers, protocol_version) { + config.validator_selection_config.num_chunk_producer_seats = 20; + } config.num_block_producer_seats_per_shard = shard_ids.map(|_| config.num_block_producer_seats).collect(); // Decrease the number of chunk producers. config.validator_selection_config.num_chunk_only_producer_seats = 100; } + + if checked_feature!("stable", NoChunkOnlyProducers, protocol_version) { + // Make sure there is no chunk only producer in stateless validation + config.validator_selection_config.num_chunk_only_producer_seats = 0; + } } fn config_max_kickout_stake(config: &mut EpochConfig, protocol_version: u32) { @@ -611,15 +623,14 @@ pub mod epoch_info { use crate::epoch_manager::ValidatorWeight; use crate::types::validator_stake::{ValidatorStake, ValidatorStakeIter}; use crate::types::{BlockChunkValidatorStats, ValidatorKickoutReason}; - use crate::validator_mandates::{ChunkValidatorStakeAssignment, ValidatorMandates}; + use crate::validator_mandates::ValidatorMandates; use crate::version::PROTOCOL_VERSION; use borsh::{BorshDeserialize, BorshSerialize}; use near_primitives_core::hash::CryptoHash; use near_primitives_core::types::{ AccountId, Balance, EpochHeight, ProtocolVersion, ValidatorId, }; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; + use smart_default::SmartDefault; use std::collections::{BTreeMap, HashMap}; @@ -948,6 +959,16 @@ pub mod epoch_info { } } + #[inline] + pub fn chunk_producers_settlement_mut(&mut self) -> &mut Vec> { + match self { + Self::V1(v1) => &mut v1.chunk_producers_settlement, + Self::V2(v2) => &mut v2.chunk_producers_settlement, + Self::V3(v3) => &mut v3.chunk_producers_settlement, + Self::V4(v4) => &mut v4.chunk_producers_settlement, + } + } + #[inline] pub fn validator_kickout(&self) -> &HashMap { match self { @@ -1127,6 +1148,23 @@ pub mod epoch_info { } } + #[inline] + pub fn rng_seed(&self) -> RngSeed { + match self { + Self::V1(_) | Self::V2(_) => Default::default(), + Self::V3(v3) => v3.rng_seed, + Self::V4(v4) => v4.rng_seed, + } + } + + #[inline] + pub fn validator_mandates(&self) -> ValidatorMandates { + match self { + Self::V1(_) | Self::V2(_) | Self::V3(_) => Default::default(), + Self::V4(v4) => v4.validator_mandates.clone(), + } + } + pub fn sample_block_producer(&self, height: BlockHeight) -> ValidatorId { match &self { Self::V1(v1) => { @@ -1183,10 +1221,11 @@ pub mod epoch_info { } } + #[cfg(feature = "rand")] pub fn sample_chunk_validators( &self, height: BlockHeight, - ) -> ChunkValidatorStakeAssignment { + ) -> crate::validator_mandates::ChunkValidatorStakeAssignment { // Chunk validator assignment was introduced with `V4`. match &self { Self::V1(_) | Self::V2(_) | Self::V3(_) => Default::default(), @@ -1228,11 +1267,14 @@ pub mod epoch_info { hash(&buffer).0 } } + } + #[cfg(feature = "rand")] + impl EpochInfo { /// Returns a new RNG obtained from combining the provided `seed` and `height`. /// /// The returned RNG can be used to shuffle slices via [`rand::seq::SliceRandom`]. - fn chunk_validate_rng(seed: &RngSeed, height: BlockHeight) -> ChaCha20Rng { + fn chunk_validate_rng(seed: &RngSeed, height: BlockHeight) -> rand_chacha::ChaCha20Rng { // A deterministic seed is produces using the block height and the provided seed. // This is important as all nodes need to agree on the set and order of chunk_validators let mut buffer = [0u8; 40]; @@ -1244,18 +1286,18 @@ pub mod epoch_info { // https://docs.rs/rand_core/0.6.2/rand_core/trait.SeedableRng.html#associated-types // Therefore `buffer` is hashed to obtain a `[u8; 32]`. let seed = hash(&buffer); - SeedableRng::from_seed(seed.0) + rand::SeedableRng::from_seed(seed.0) } /// Returns a new RNG used for random chunk producer modifications /// during shard assignments. - pub fn shard_assignment_rng(seed: &RngSeed) -> ChaCha20Rng { + pub fn shard_assignment_rng(seed: &RngSeed) -> rand_chacha::ChaCha20Rng { let mut buffer = [0u8; 62]; buffer[0..32].copy_from_slice(seed); // Do this to avoid any possibility of colliding with any other rng. buffer[32..62].copy_from_slice(b"shard_assignment_shuffling_rng"); let seed = hash(&buffer); - SeedableRng::from_seed(seed.0) + rand::SeedableRng::from_seed(seed.0) } } @@ -1433,7 +1475,7 @@ pub mod epoch_sync { header.raw_timestamp(), ); - *block_info.epoch_id_mut() = epoch_first_header.epoch_id().clone(); + *block_info.epoch_id_mut() = *epoch_first_header.epoch_id(); *block_info.epoch_first_block_mut() = *epoch_first_header.hash(); Ok(block_info) } diff --git a/core/primitives/src/errors.rs b/core/primitives/src/errors.rs index 8cd728a6f8d..ce9170dc9c3 100644 --- a/core/primitives/src/errors.rs +++ b/core/primitives/src/errors.rs @@ -53,7 +53,7 @@ impl From for TxExecutionError { #[derive(Debug, Clone, PartialEq, Eq)] pub enum RuntimeError { /// An unexpected integer overflow occurred. The likely issue is an invalid state or the transition. - UnexpectedIntegerOverflow, + UnexpectedIntegerOverflow(String), /// An error happened during TX verification and account charging. It's likely the chunk is invalid. /// and should be challenged. InvalidTxError(InvalidTxError), @@ -77,7 +77,16 @@ impl std::fmt::Display for RuntimeError { impl std::error::Error for RuntimeError {} /// Contexts in which `StorageError::MissingTrieValue` error might occur. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive( + Debug, + Clone, + PartialEq, + Eq, + serde::Deserialize, + serde::Serialize, + BorshSerialize, + BorshDeserialize, +)] pub enum MissingTrieValueContext { /// Missing trie value when reading from TrieIterator. TrieIterator, @@ -91,7 +100,16 @@ pub enum MissingTrieValueContext { /// Errors which may occur during working with trie storages, storing /// trie values (trie nodes and state values) by their hashes. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive( + Debug, + Clone, + PartialEq, + Eq, + serde::Deserialize, + serde::Serialize, + BorshSerialize, + BorshDeserialize, +)] pub enum StorageError { /// Key-value db internal failure StorageInternalError, @@ -139,15 +157,27 @@ pub enum InvalidTxError { /// Happens if a wrong AccessKey used or AccessKey has not enough permissions InvalidAccessKeyError(InvalidAccessKeyError), /// TX signer_id is not a valid [`AccountId`] - InvalidSignerId { signer_id: String }, + InvalidSignerId { + signer_id: String, + }, /// TX signer_id is not found in a storage - SignerDoesNotExist { signer_id: AccountId }, + SignerDoesNotExist { + signer_id: AccountId, + }, /// Transaction nonce must be `account[access_key].nonce + 1`. - InvalidNonce { tx_nonce: Nonce, ak_nonce: Nonce }, + InvalidNonce { + tx_nonce: Nonce, + ak_nonce: Nonce, + }, /// Transaction nonce is larger than the upper bound given by the block height - NonceTooLarge { tx_nonce: Nonce, upper_bound: Nonce }, + NonceTooLarge { + tx_nonce: Nonce, + upper_bound: Nonce, + }, /// TX receiver_id is not a valid AccountId - InvalidReceiverId { receiver_id: String }, + InvalidReceiverId { + receiver_id: String, + }, /// TX signature is not valid InvalidSignature, /// Account does not have enough balance to cover TX cost @@ -175,9 +205,36 @@ pub enum InvalidTxError { /// An error occurred while validating actions of a Transaction. ActionsValidation(ActionsValidationError), /// The size of serialized transaction exceeded the limit. - TransactionSizeExceeded { size: u64, limit: u64 }, + TransactionSizeExceeded { + size: u64, + limit: u64, + }, /// Transaction version is invalid. InvalidTransactionVersion, + // Error occurred during storage access + StorageError(StorageError), + /// The receiver shard of the transaction is too congested to accept new + /// transactions at the moment. + ShardCongested { + /// The congested shard. + shard_id: u32, + /// A value between 0 (no congestion) and 1 (max congestion). + congestion_level: ordered_float::NotNan, + }, + /// The receiver shard of the transaction missed several chunks and rejects + /// new transaction until it can make progress again. + ShardStuck { + /// The shard that fails making progress. + shard_id: u32, + /// The number of blocks since the last included chunk of the shard. + missed_chunks: u64, + }, +} + +impl From for InvalidTxError { + fn from(error: StorageError) -> Self { + InvalidTxError::StorageError(error) + } } impl std::error::Error for InvalidTxError {} @@ -290,6 +347,8 @@ pub enum ReceiptValidationError { NumberInputDataDependenciesExceeded { number_of_input_data_dependencies: u64, limit: u64 }, /// An error occurred while validating actions of an ActionReceipt. ActionsValidation(ActionsValidationError), + /// Receipt is bigger than the limit. + ReceiptSizeExceeded { size: u64, limit: u64 }, } impl Display for ReceiptValidationError { @@ -320,6 +379,11 @@ impl Display for ReceiptValidationError { number_of_input_data_dependencies, limit ), ReceiptValidationError::ActionsValidation(e) => write!(f, "{}", e), + ReceiptValidationError::ReceiptSizeExceeded { size, limit } => write!( + f, + "The size of the receipt exceeded the limit: {} > {}", + size, limit + ), } } } @@ -576,6 +640,18 @@ impl Display for InvalidTxError { InvalidTxError::InvalidTransactionVersion => { write!(f, "Transaction version is invalid") } + InvalidTxError::StorageError(error) => { + write!(f, "Storage error: {}", error) + } + InvalidTxError::ShardCongested { shard_id, congestion_level } => { + write!(f, "Shard {shard_id} is currently at congestion level {congestion_level:.3} and rejects new transactions.") + } + InvalidTxError::ShardStuck { shard_id, missed_chunks } => { + write!( + f, + "Shard {shard_id} missed {missed_chunks} chunks and rejects new transactions." + ) + } } } } @@ -640,8 +716,6 @@ pub struct BalanceMismatchError { pub processed_delayed_receipts_balance: Balance, #[serde(with = "dec_format")] pub initial_postponed_receipts_balance: Balance, - // TODO(congestion_control): remove cfg on stabilization - #[cfg(feature = "nightly")] #[serde(with = "dec_format")] pub forwarded_buffered_receipts_balance: Balance, // Output balances @@ -657,8 +731,6 @@ pub struct BalanceMismatchError { pub tx_burnt_amount: Balance, #[serde(with = "dec_format")] pub slashed_burnt_amount: Balance, - // TODO(congestion_control): remove cfg on stabilization - #[cfg(feature = "nightly")] #[serde(with = "dec_format")] pub new_buffered_receipts_balance: Balance, #[serde(with = "dec_format")] @@ -673,11 +745,8 @@ impl Display for BalanceMismatchError { .saturating_add(self.initial_accounts_balance) .saturating_add(self.incoming_receipts_balance) .saturating_add(self.processed_delayed_receipts_balance) - .saturating_add(self.initial_postponed_receipts_balance); - // TODO(congestion_control): remove cfg on stabilization - #[cfg(feature = "nightly")] - let initial_balance = - initial_balance.saturating_add(self.forwarded_buffered_receipts_balance); + .saturating_add(self.initial_postponed_receipts_balance) + .saturating_add(self.forwarded_buffered_receipts_balance); let final_balance = self .final_accounts_balance .saturating_add(self.outgoing_receipts_balance) @@ -685,46 +754,9 @@ impl Display for BalanceMismatchError { .saturating_add(self.final_postponed_receipts_balance) .saturating_add(self.tx_burnt_amount) .saturating_add(self.slashed_burnt_amount) - .saturating_add(self.other_burnt_amount); - // TODO(congestion_control): remove cfg on stabilization - #[cfg(feature = "nightly")] - let final_balance = final_balance.saturating_add(self.new_buffered_receipts_balance); + .saturating_add(self.other_burnt_amount) + .saturating_add(self.new_buffered_receipts_balance); - // TODO(congestion_control): remove cfg on stabilization - #[cfg(not(feature = "nightly"))] - return write!( - f, - "Balance Mismatch Error. The input balance {} doesn't match output balance {}\n\ - Inputs:\n\ - \tIncoming validator rewards sum: {}\n\ - \tInitial accounts balance sum: {}\n\ - \tIncoming receipts balance sum: {}\n\ - \tProcessed delayed receipts balance sum: {}\n\ - \tInitial postponed receipts balance sum: {}\n\ - Outputs:\n\ - \tFinal accounts balance sum: {}\n\ - \tOutgoing receipts balance sum: {}\n\ - \tNew delayed receipts balance sum: {}\n\ - \tFinal postponed receipts balance sum: {}\n\ - \tTx fees burnt amount: {}\n\ - \tSlashed amount: {}\n\ - \tOther burnt amount: {}", - initial_balance, - final_balance, - self.incoming_validator_rewards, - self.initial_accounts_balance, - self.incoming_receipts_balance, - self.processed_delayed_receipts_balance, - self.initial_postponed_receipts_balance, - self.final_accounts_balance, - self.outgoing_receipts_balance, - self.new_delayed_receipts_balance, - self.final_postponed_receipts_balance, - self.tx_burnt_amount, - self.slashed_burnt_amount, - self.other_burnt_amount, - ); - #[cfg(feature = "nightly")] write!( f, "Balance Mismatch Error. The input balance {} doesn't match output balance {}\n\ @@ -784,8 +816,8 @@ impl From for InvalidTxError { } impl From for RuntimeError { - fn from(_: IntegerOverflowError) -> Self { - RuntimeError::UnexpectedIntegerOverflow + fn from(err: IntegerOverflowError) -> Self { + RuntimeError::UnexpectedIntegerOverflow(err.to_string()) } } diff --git a/core/primitives/src/lib.rs b/core/primitives/src/lib.rs index 891d52c4067..8f9854fa87f 100644 --- a/core/primitives/src/lib.rs +++ b/core/primitives/src/lib.rs @@ -18,8 +18,10 @@ pub mod errors; pub mod merkle; pub mod network; pub mod profile_data_v2; +pub mod profile_data_v3; pub mod rand; pub mod receipt; +#[cfg(feature = "solomon")] pub mod reed_solomon; pub mod runtime; pub mod sandbox; diff --git a/core/primitives/src/network.rs b/core/primitives/src/network.rs index d670be8bef6..f61fadccea7 100644 --- a/core/primitives/src/network.rs +++ b/core/primitives/src/network.rs @@ -1,7 +1,7 @@ use crate::hash::CryptoHash; use crate::types::{AccountId, EpochId}; use borsh::{BorshDeserialize, BorshSerialize}; -use near_crypto::{KeyType, PublicKey, SecretKey, Signature}; +use near_crypto::{PublicKey, Signature}; use std::fmt; use std::hash::Hash; use std::sync::Arc; @@ -32,8 +32,10 @@ impl PeerId { } impl PeerId { + #[cfg(feature = "rand")] + pub fn random() -> Self { - PeerId::new(SecretKey::from_random(KeyType::ED25519).public_key()) + PeerId::new(near_crypto::SecretKey::from_random(near_crypto::KeyType::ED25519).public_key()) } } diff --git a/core/primitives/src/profile_data_v3.rs b/core/primitives/src/profile_data_v3.rs new file mode 100644 index 00000000000..270c367d2eb --- /dev/null +++ b/core/primitives/src/profile_data_v3.rs @@ -0,0 +1,457 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use enum_map::{enum_map, Enum, EnumMap}; +use near_parameters::{ActionCosts, ExtCosts, ExtCostsConfig}; +use near_primitives_core::types::{Compute, Gas}; +use std::fmt; +use strum::IntoEnumIterator; + +/// Profile of gas consumption. +#[derive(Clone, PartialEq, Eq)] +pub struct ProfileDataV3 { + /// Gas spent on sending or executing actions. + pub actions_profile: EnumMap, + /// Non-action gas spent outside the WASM VM while executing a contract. + pub wasm_ext_profile: EnumMap, + /// Gas spent on execution inside the WASM VM. + pub wasm_gas: Gas, +} + +impl Default for ProfileDataV3 { + fn default() -> ProfileDataV3 { + ProfileDataV3::new() + } +} + +impl ProfileDataV3 { + #[inline] + pub fn new() -> Self { + Self { + actions_profile: enum_map! { _ => 0 }, + wasm_ext_profile: enum_map! { _ => 0 }, + wasm_gas: 0, + } + } + + /// Test instance with unique numbers in each field. + pub fn test() -> Self { + let mut profile_data = ProfileDataV3::default(); + for (i, cost) in ExtCosts::iter().enumerate() { + profile_data.add_ext_cost(cost, i as Gas); + } + for (i, cost) in ActionCosts::iter().enumerate() { + profile_data.add_action_cost(cost, i as Gas + 1000); + } + profile_data + } + + #[inline] + pub fn merge(&mut self, other: &ProfileDataV3) { + for ((_, gas), (_, other_gas)) in + self.actions_profile.iter_mut().zip(other.actions_profile.iter()) + { + *gas = gas.saturating_add(*other_gas); + } + for ((_, gas), (_, other_gas)) in + self.wasm_ext_profile.iter_mut().zip(other.wasm_ext_profile.iter()) + { + *gas = gas.saturating_add(*other_gas); + } + self.wasm_gas = self.wasm_gas.saturating_add(other.wasm_gas); + } + + #[inline] + pub fn add_action_cost(&mut self, action: ActionCosts, value: Gas) { + self.actions_profile[action] = self.actions_profile[action].saturating_add(value); + } + + #[inline] + pub fn add_ext_cost(&mut self, ext: ExtCosts, value: Gas) { + self.wasm_ext_profile[ext] = self.wasm_ext_profile[ext].saturating_add(value); + } + + /// WasmInstruction is the only cost we don't explicitly account for. + /// Instead, we compute it at the end of contract call as the difference + /// between total gas burnt and what we've explicitly accounted for in the + /// profile. + /// + /// This is because WasmInstruction is the hottest cost and is implemented + /// with the help on the VM side, so we don't want to have profiling logic + /// there both for simplicity and efficiency reasons. + pub fn compute_wasm_instruction_cost(&mut self, total_gas_burnt: Gas) { + self.wasm_gas = + total_gas_burnt.saturating_sub(self.action_gas()).saturating_sub(self.host_gas()); + } + + pub fn get_action_cost(&self, action: ActionCosts) -> Gas { + self.actions_profile[action] + } + + pub fn get_ext_cost(&self, ext: ExtCosts) -> Gas { + self.wasm_ext_profile[ext] + } + + pub fn get_wasm_cost(&self) -> Gas { + self.wasm_gas + } + + fn host_gas(&self) -> Gas { + self.wasm_ext_profile.as_slice().iter().copied().fold(0, Gas::saturating_add) + } + + pub fn action_gas(&self) -> Gas { + self.actions_profile.as_slice().iter().copied().fold(0, Gas::saturating_add) + } + + /// Returns total compute usage of host calls. + pub fn total_compute_usage(&self, ext_costs_config: &ExtCostsConfig) -> Compute { + let ext_compute_cost = self + .wasm_ext_profile + .iter() + .map(|(key, value)| { + // Technically, gas cost might be zero while the compute cost is non-zero. To + // handle this case, we would need to explicitly count number of calls, not just + // the total gas usage. + // We don't have such costs at the moment, so this case is not implemented. + debug_assert!(key.gas(ext_costs_config) > 0 || key.compute(ext_costs_config) == 0); + + if *value == 0 { + return *value; + } + // If the `value` is non-zero, the gas cost also must be non-zero. + debug_assert!(key.gas(ext_costs_config) != 0); + ((*value as u128).saturating_mul(key.compute(ext_costs_config) as u128) + / (key.gas(ext_costs_config) as u128)) as u64 + }) + .fold(0, Compute::saturating_add); + + // We currently only support compute costs for host calls. In the future we might add + // them for actions as well. + ext_compute_cost.saturating_add(self.action_gas()).saturating_add(self.get_wasm_cost()) + } +} + +impl BorshDeserialize for ProfileDataV3 { + fn deserialize_reader(rd: &mut R) -> std::io::Result { + let actions_array: Vec = BorshDeserialize::deserialize_reader(rd)?; + let ext_array: Vec = BorshDeserialize::deserialize_reader(rd)?; + let wasm_gas: u64 = BorshDeserialize::deserialize_reader(rd)?; + + // Mapping raw arrays to enum maps. + // The enum map could be smaller or larger than the raw array. + // Extra values in the array that are unknown to the current binary will + // be ignored. Missing values are filled with 0. + let actions_profile = enum_map! { + cost => actions_array.get(borsh_action_index(cost)).copied().unwrap_or(0) + }; + let wasm_ext_profile = enum_map! { + cost => ext_array.get(borsh_ext_index(cost)).copied().unwrap_or(0) + }; + + Ok(Self { actions_profile, wasm_ext_profile, wasm_gas }) + } +} + +impl BorshSerialize for ProfileDataV3 { + fn serialize(&self, writer: &mut W) -> Result<(), std::io::Error> { + let mut actions_costs: Vec = vec![0u64; ActionCosts::LENGTH]; + for (cost, gas) in self.actions_profile.iter() { + actions_costs[borsh_action_index(cost)] = *gas; + } + BorshSerialize::serialize(&actions_costs, writer)?; + + let mut ext_costs: Vec = vec![0u64; ExtCosts::LENGTH]; + for (cost, gas) in self.wasm_ext_profile.iter() { + ext_costs[borsh_ext_index(cost)] = *gas; + } + BorshSerialize::serialize(&ext_costs, writer)?; + + let wasm_cost: u64 = self.wasm_gas; + BorshSerialize::serialize(&wasm_cost, writer) + } +} + +/// Fixed index of an action cost for borsh (de)serialization. +/// +/// We use borsh to store profiles on the DB and borsh is quite fragile with +/// changes. This mapping is to decouple the Rust enum from the borsh +/// representation. The enum can be changed freely but here in the mapping we +/// can only append more values at the end. +/// +/// TODO: Consider changing this to a different format (e.g. protobuf) because +/// canonical representation is not required here. +const fn borsh_action_index(action: ActionCosts) -> usize { + // actual indices are defined on the enum variants + action as usize +} + +/// Fixed index of an ext cost for borsh (de)serialization. +/// +/// We use borsh to store profiles on the DB and borsh is quite fragile with +/// changes. This mapping is to decouple the Rust enum from the borsh +/// representation. The enum can be changed freely but here in the mapping we +/// can only append more values at the end. +/// +/// TODO: Consider changing this to a different format (e.g. protobuf) because +/// canonical representation is not required here. +const fn borsh_ext_index(ext: ExtCosts) -> usize { + // actual indices are defined on the enum variants + ext as usize +} + +impl fmt::Debug for ProfileDataV3 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use num_rational::Ratio; + let host_gas = self.host_gas(); + let action_gas = self.action_gas(); + + writeln!(f, "------------------------------")?; + writeln!(f, "Action gas: {}", action_gas)?; + writeln!(f, "------ Host functions --------")?; + for cost in ExtCosts::iter() { + let d = self.get_ext_cost(cost); + if d != 0 { + writeln!( + f, + "{} -> {} [{}% host]", + cost, + d, + Ratio::new(d * 100, core::cmp::max(host_gas, 1)).to_integer(), + )?; + } + } + writeln!(f, "------ Actions --------")?; + for cost in ActionCosts::iter() { + let d = self.get_action_cost(cost); + if d != 0 { + writeln!(f, "{} -> {}", cost, d)?; + } + } + writeln!(f, "------------------------------")?; + Ok(()) + } +} + +/// Tests for ProfileDataV3 +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(not(feature = "nightly"))] + fn test_profile_data_debug() { + let profile_data = ProfileDataV3::test(); + // we don't care about exact formatting, but the numbers should not change unexpectedly + let pretty_debug_str = format!("{profile_data:#?}"); + expect_test::expect![[r#" + ------------------------------ + Action gas: 16120 + ------ Host functions -------- + contract_loading_base -> 1 [0% host] + contract_loading_bytes -> 2 [0% host] + read_memory_base -> 3 [0% host] + read_memory_byte -> 4 [0% host] + write_memory_base -> 5 [0% host] + write_memory_byte -> 6 [0% host] + read_register_base -> 7 [0% host] + read_register_byte -> 8 [0% host] + write_register_base -> 9 [0% host] + write_register_byte -> 10 [0% host] + utf8_decoding_base -> 11 [0% host] + utf8_decoding_byte -> 12 [0% host] + utf16_decoding_base -> 13 [0% host] + utf16_decoding_byte -> 14 [0% host] + sha256_base -> 15 [0% host] + sha256_byte -> 16 [0% host] + keccak256_base -> 17 [0% host] + keccak256_byte -> 18 [0% host] + keccak512_base -> 19 [0% host] + keccak512_byte -> 20 [0% host] + ripemd160_base -> 21 [1% host] + ripemd160_block -> 22 [1% host] + ecrecover_base -> 23 [1% host] + log_base -> 24 [1% host] + log_byte -> 25 [1% host] + storage_write_base -> 26 [1% host] + storage_write_key_byte -> 27 [1% host] + storage_write_value_byte -> 28 [1% host] + storage_write_evicted_byte -> 29 [1% host] + storage_read_base -> 30 [1% host] + storage_read_key_byte -> 31 [1% host] + storage_read_value_byte -> 32 [1% host] + storage_remove_base -> 33 [1% host] + storage_remove_key_byte -> 34 [1% host] + storage_remove_ret_value_byte -> 35 [1% host] + storage_has_key_base -> 36 [1% host] + storage_has_key_byte -> 37 [1% host] + storage_iter_create_prefix_base -> 38 [1% host] + storage_iter_create_prefix_byte -> 39 [1% host] + storage_iter_create_range_base -> 40 [1% host] + storage_iter_create_from_byte -> 41 [1% host] + storage_iter_create_to_byte -> 42 [2% host] + storage_iter_next_base -> 43 [2% host] + storage_iter_next_key_byte -> 44 [2% host] + storage_iter_next_value_byte -> 45 [2% host] + touching_trie_node -> 46 [2% host] + read_cached_trie_node -> 47 [2% host] + promise_and_base -> 48 [2% host] + promise_and_per_promise -> 49 [2% host] + promise_return -> 50 [2% host] + validator_stake_base -> 51 [2% host] + validator_total_stake_base -> 52 [2% host] + alt_bn128_g1_multiexp_base -> 53 [2% host] + alt_bn128_g1_multiexp_element -> 54 [2% host] + alt_bn128_pairing_check_base -> 55 [2% host] + alt_bn128_pairing_check_element -> 56 [2% host] + alt_bn128_g1_sum_base -> 57 [2% host] + alt_bn128_g1_sum_element -> 58 [2% host] + ed25519_verify_base -> 59 [2% host] + ed25519_verify_byte -> 60 [2% host] + yield_create_base -> 61 [2% host] + yield_create_byte -> 62 [2% host] + yield_resume_base -> 63 [3% host] + yield_resume_byte -> 64 [3% host] + ------ Actions -------- + create_account -> 1000 + delete_account -> 1001 + deploy_contract_base -> 1002 + deploy_contract_byte -> 1003 + function_call_base -> 1004 + function_call_byte -> 1005 + transfer -> 1006 + stake -> 1007 + add_full_access_key -> 1008 + add_function_call_key_base -> 1009 + add_function_call_key_byte -> 1010 + delete_key -> 1011 + new_action_receipt -> 1012 + new_data_receipt_base -> 1013 + new_data_receipt_byte -> 1014 + delegate -> 1015 + ------------------------------ + "#]] + .assert_eq(&pretty_debug_str) + } + + #[test] + fn test_profile_data_debug_no_data() { + let profile_data = ProfileDataV3::default(); + // we don't care about exact formatting, but at least it should not panic + println!("{:#?}", &profile_data); + } + + #[test] + fn test_no_panic_on_overflow() { + let mut profile_data = ProfileDataV3::default(); + profile_data.add_action_cost(ActionCosts::add_full_access_key, u64::MAX); + profile_data.add_action_cost(ActionCosts::add_full_access_key, u64::MAX); + + let res = profile_data.get_action_cost(ActionCosts::add_full_access_key); + assert_eq!(res, u64::MAX); + } + + #[test] + fn test_merge() { + let mut profile_data = ProfileDataV3::default(); + profile_data.add_action_cost(ActionCosts::add_full_access_key, 111); + profile_data.add_ext_cost(ExtCosts::storage_read_base, 11); + + let mut profile_data2 = ProfileDataV3::default(); + profile_data2.add_action_cost(ActionCosts::add_full_access_key, 222); + profile_data2.add_ext_cost(ExtCosts::storage_read_base, 22); + + profile_data.merge(&profile_data2); + assert_eq!(profile_data.get_action_cost(ActionCosts::add_full_access_key), 333); + assert_eq!(profile_data.get_ext_cost(ExtCosts::storage_read_base), 33); + } + + #[test] + fn test_total_compute_usage() { + let ext_costs_config = ExtCostsConfig::test_with_undercharging_factor(3); + let mut profile_data = ProfileDataV3::default(); + profile_data.add_ext_cost( + ExtCosts::storage_read_base, + 2 * ExtCosts::storage_read_base.gas(&ext_costs_config), + ); + profile_data.add_ext_cost( + ExtCosts::storage_write_base, + 5 * ExtCosts::storage_write_base.gas(&ext_costs_config), + ); + profile_data.add_action_cost(ActionCosts::function_call_base, 100); + + assert_eq!( + profile_data.total_compute_usage(&ext_costs_config), + 3 * profile_data.host_gas() + profile_data.action_gas() + ); + } + + #[test] + fn test_borsh_ser_deser() { + let mut profile_data = ProfileDataV3::default(); + for (i, cost) in ExtCosts::iter().enumerate() { + profile_data.add_ext_cost(cost, i as Gas); + } + for (i, cost) in ActionCosts::iter().enumerate() { + profile_data.add_action_cost(cost, i as Gas + 1000); + } + let buf = borsh::to_vec(&profile_data).expect("failed serializing a normal profile"); + + let restored: ProfileDataV3 = BorshDeserialize::deserialize(&mut buf.as_slice()) + .expect("failed deserializing a normal profile"); + + assert_eq!(profile_data, restored); + } + + #[test] + fn test_borsh_incomplete_profile() { + let action_profile = vec![50u64, 60]; + let ext_profile = vec![100u64, 200]; + let wasm_cost = 99u64; + let input = manually_encode_profile_v2(action_profile, ext_profile, wasm_cost); + + let profile: ProfileDataV3 = BorshDeserialize::deserialize(&mut input.as_slice()) + .expect("should be able to parse a profile with less entries"); + + assert_eq!(50, profile.get_action_cost(ActionCosts::create_account)); + assert_eq!(60, profile.get_action_cost(ActionCosts::delete_account)); + assert_eq!(0, profile.get_action_cost(ActionCosts::deploy_contract_base)); + + assert_eq!(100, profile.get_ext_cost(ExtCosts::base)); + assert_eq!(200, profile.get_ext_cost(ExtCosts::contract_loading_base)); + assert_eq!(0, profile.get_ext_cost(ExtCosts::contract_loading_bytes)); + } + + #[test] + fn test_borsh_larger_profile_than_current() { + let action_profile = vec![1234u64; ActionCosts::LENGTH + 5]; + let ext_profile = vec![5678u64; ExtCosts::LENGTH + 10]; + let wasm_cost = 90u64; + let input = manually_encode_profile_v2(action_profile, ext_profile, wasm_cost); + + let profile: ProfileDataV3 = BorshDeserialize::deserialize(&mut input.as_slice()).expect( + "should be able to parse a profile with more entries than the current version has", + ); + + for action in ActionCosts::iter() { + assert_eq!(1234, profile.get_action_cost(action), "{action:?}"); + } + + for ext in ExtCosts::iter() { + assert_eq!(5678, profile.get_ext_cost(ext), "{ext:?}"); + } + + assert_eq!(90, profile.wasm_gas); + } + + #[track_caller] + fn manually_encode_profile_v2( + action_profile: Vec, + ext_profile: Vec, + wasm_cost: u64, + ) -> Vec { + let mut input = vec![]; + BorshSerialize::serialize(&action_profile, &mut input).unwrap(); + BorshSerialize::serialize(&ext_profile, &mut input).unwrap(); + BorshSerialize::serialize(&wasm_cost, &mut input).unwrap(); + input + } +} diff --git a/core/primitives/src/reed_solomon.rs b/core/primitives/src/reed_solomon.rs index 8aea8cf4710..ea47e404002 100644 --- a/core/primitives/src/reed_solomon.rs +++ b/core/primitives/src/reed_solomon.rs @@ -12,7 +12,7 @@ pub fn reed_solomon_encode( let encoded_length = bytes.len(); let data_parts = rs.data_shard_count(); - let part_length = (encoded_length + data_parts - 1) / data_parts; + let part_length = reed_solomon_part_length(encoded_length, data_parts); // Pad the bytes to be a multiple of `part_length` // Convert encoded data into `data_shard_count` number of parts and pad with `parity_shard_count` None values @@ -52,3 +52,7 @@ pub fn reed_solomon_decode( T::try_from_slice(&encoded_data) } + +pub fn reed_solomon_part_length(encoded_length: usize, data_parts: usize) -> usize { + (encoded_length + data_parts - 1) / data_parts +} diff --git a/core/primitives/src/shard_layout.rs b/core/primitives/src/shard_layout.rs index 87ac2778f9e..ead63553cf0 100644 --- a/core/primitives/src/shard_layout.rs +++ b/core/primitives/src/shard_layout.rs @@ -491,9 +491,9 @@ impl<'de> serde::de::Visitor<'de> for ShardUIdVisitor { mod tests { use crate::epoch_manager::{AllEpochConfig, EpochConfig, ValidatorSelectionConfig}; use crate::shard_layout::{account_id_to_shard_id, ShardLayout, ShardLayoutV1, ShardUId}; + use near_primitives_core::types::ProtocolVersion; use near_primitives_core::types::{AccountId, ShardId}; use near_primitives_core::version::ProtocolFeature; - use near_vm_runner::logic::ProtocolVersion; use rand::distributions::Alphanumeric; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; @@ -532,6 +532,8 @@ mod tests { avg_hidden_validator_seats_per_shard: vec![], block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, + target_validator_mandates_per_shard: 0, validator_max_kickout_stake_perc: 0, online_min_threshold: 0.into(), online_max_threshold: 0.into(), diff --git a/core/primitives/src/sharding.rs b/core/primitives/src/sharding.rs index c3b31933cdf..88f69d771db 100644 --- a/core/primitives/src/sharding.rs +++ b/core/primitives/src/sharding.rs @@ -2,7 +2,6 @@ use crate::congestion_info::CongestionInfo; use crate::hash::{hash, CryptoHash}; use crate::merkle::{combine_hash, merklize, verify_path, MerklePath}; use crate::receipt::Receipt; -use crate::reed_solomon::reed_solomon_encode; use crate::transaction::SignedTransaction; use crate::types::validator_stake::{ValidatorStake, ValidatorStakeIter, ValidatorStakeV1}; use crate::types::{Balance, BlockHeight, Gas, MerkleHash, ShardId, StateRoot}; @@ -11,7 +10,6 @@ use crate::version::{ProtocolFeature, ProtocolVersion, SHARD_CHUNK_HEADER_UPGRAD use borsh::{BorshDeserialize, BorshSerialize}; use near_crypto::Signature; use near_fmt::AbbrBytes; -use reed_solomon_erasure::galois_8::ReedSolomon; use std::cmp::Ordering; use std::sync::Arc; use tracing::debug_span; @@ -128,7 +126,7 @@ impl ShardChunkHeaderV2 { prev_outgoing_receipts_root: CryptoHash, tx_root: CryptoHash, prev_validator_proposals: Vec, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, ) -> Self { let inner = ShardChunkHeaderInnerV1 { prev_block_hash, @@ -193,10 +191,11 @@ impl ShardChunkHeaderV3 { prev_outgoing_receipts_root: CryptoHash, tx_root: CryptoHash, prev_validator_proposals: Vec, - congestion_info: CongestionInfo, - signer: &dyn ValidatorSigner, + congestion_info: Option, + signer: &ValidatorSigner, ) -> Self { - let inner = if ProtocolFeature::CongestionControl.enabled(protocol_version) { + let inner = if let Some(congestion_info) = congestion_info { + assert!(ProtocolFeature::CongestionControl.enabled(protocol_version)); ShardChunkHeaderInner::V3(ShardChunkHeaderInnerV3 { prev_block_hash, prev_state_root, @@ -233,7 +232,7 @@ impl ShardChunkHeaderV3 { Self::from_inner(inner, signer) } - pub fn from_inner(inner: ShardChunkHeaderInner, signer: &dyn ValidatorSigner) -> Self { + pub fn from_inner(inner: ShardChunkHeaderInner, signer: &ValidatorSigner) -> Self { let hash = Self::compute_hash(&inner); let signature = signer.sign_chunk_hash(&hash); Self { inner, height_included: 0, signature, hash } @@ -451,9 +450,13 @@ impl ShardChunkHeader { SHARD_CHUNK_HEADER_UPGRADE_VERSION <= version && version < BLOCK_HEADER_V3_VERSION } ShardChunkHeader::V3(header) => match header.inner { - ShardChunkHeaderInner::V1(_) | ShardChunkHeaderInner::V2(_) => { + ShardChunkHeaderInner::V1(_) => { version >= BLOCK_HEADER_V3_VERSION && version < CONGESTION_CONTROL_VERSION } + // Note that we allow V2 in the congestion control version. + // That is because the first chunk where this feature is + // enabled does not have the congestion info. + ShardChunkHeaderInner::V2(_) => version >= BLOCK_HEADER_V3_VERSION, ShardChunkHeaderInner::V3(_) => version >= CONGESTION_CONTROL_VERSION, }, } @@ -501,7 +504,7 @@ impl ShardChunkHeaderV1 { prev_outgoing_receipts_root: CryptoHash, tx_root: CryptoHash, prev_validator_proposals: Vec, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, ) -> Self { let inner = ShardChunkHeaderInnerV1 { prev_block_hash, @@ -1016,13 +1019,14 @@ impl EncodedShardChunk { TransactionReceipt::try_from_slice(&encoded_data) } + #[cfg(feature = "solomon")] pub fn new( prev_block_hash: CryptoHash, prev_state_root: StateRoot, prev_outcome_root: CryptoHash, height: BlockHeight, shard_id: ShardId, - rs: &ReedSolomon, + rs: &reed_solomon_erasure::galois_8::ReedSolomon, prev_gas_used: Gas, gas_limit: Gas, prev_balance_burnt: Balance, @@ -1031,11 +1035,11 @@ impl EncodedShardChunk { transactions: Vec, prev_outgoing_receipts: &[Receipt], prev_outgoing_receipts_root: CryptoHash, - congestion_info: CongestionInfo, - signer: &dyn ValidatorSigner, + congestion_info: Option, + signer: &ValidatorSigner, protocol_version: ProtocolVersion, ) -> Result<(Self, Vec), std::io::Error> { - let (transaction_receipts_parts, encoded_length) = reed_solomon_encode( + let (transaction_receipts_parts, encoded_length) = crate::reed_solomon::reed_solomon_encode( rs, TransactionReceipt(transactions, prev_outgoing_receipts.to_vec()), ); diff --git a/core/primitives/src/signable_message.rs b/core/primitives/src/signable_message.rs index f7f1f6b7965..59ee8415f02 100644 --- a/core/primitives/src/signable_message.rs +++ b/core/primitives/src/signable_message.rs @@ -94,7 +94,7 @@ impl<'a, T: BorshSerialize> SignableMessage<'a, T> { Self { discriminant, msg } } - pub fn sign(&self, signer: &dyn Signer) -> Signature { + pub fn sign(&self, signer: &Signer) -> Signature { let bytes = borsh::to_vec(&self).expect("Failed to deserialize"); let hash = hash(&bytes); signer.sign(hash.as_bytes()) @@ -241,7 +241,8 @@ mod tests { let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key()); let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction); - let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action }; + let signed = + SignedDelegateAction { signature: signable.sign(&signer.into()), delegate_action }; assert!(signed.verify()); } @@ -259,7 +260,8 @@ mod tests { discriminant: MessageDiscriminant::new_on_chain(wrong_nep).unwrap(), msg: &delegate_action, }; - let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action }; + let signed = + SignedDelegateAction { signature: signable.sign(&signer.into()), delegate_action }; assert!(!signed.verify()); } @@ -276,7 +278,8 @@ mod tests { // here we use it as an off-chain only signature let wrong_discriminant = MessageDiscriminant::new_off_chain(correct_nep).unwrap(); let signable = SignableMessage { discriminant: wrong_discriminant, msg: &delegate_action }; - let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action }; + let signed = + SignedDelegateAction { signature: signable.sign(&signer.into()), delegate_action }; assert!(!signed.verify()); } diff --git a/core/primitives/src/snapshots/near_primitives__views__tests__runtime_config_view.snap b/core/primitives/src/snapshots/near_primitives__views__tests__runtime_config_view.snap index be77c2d52c4..9afb79a4bb2 100644 --- a/core/primitives/src/snapshots/near_primitives__views__tests__runtime_config_view.snap +++ b/core/primitives/src/snapshots/near_primitives__views__tests__runtime_config_view.snap @@ -18,7 +18,7 @@ expression: "&view" }, "cost_per_byte": { "send_sir": 17212011, - "send_not_sir": 17212011, + "send_not_sir": 47683715, "execution": 17212011 } }, @@ -35,7 +35,7 @@ expression: "&view" }, "deploy_contract_cost_per_byte": { "send_sir": 6812999, - "send_not_sir": 6812999, + "send_not_sir": 47683715, "execution": 64572944 }, "function_call_cost": { @@ -45,7 +45,7 @@ expression: "&view" }, "function_call_cost_per_byte": { "send_sir": 2235934, - "send_not_sir": 2235934, + "send_not_sir": 47683715, "execution": 2235934 }, "transfer_cost": { @@ -71,7 +71,7 @@ expression: "&view" }, "function_call_cost_per_byte": { "send_sir": 1925331, - "send_not_sir": 1925331, + "send_not_sir": 47683715, "execution": 1925331 } }, @@ -178,6 +178,7 @@ expression: "&view" "regular_op_cost": 822756, "vm_kind": "", "disable_9393_fix": false, + "discard_custom_sections": false, "storage_get_mode": "FlatStorage", "fix_contract_loading_cost": false, "implicit_account_creation": true, @@ -205,7 +206,8 @@ expression: "&view" "max_arguments_length": 4194304, "max_length_returned_data": 4194304, "max_contract_size": 4194304, - "max_transaction_size": 4194304, + "max_transaction_size": 1572864, + "max_receipt_size": 4194304, "max_length_storage_key": 2048, "max_length_storage_value": 4194304, "max_promises_per_function_call_action": 1024, @@ -216,16 +218,30 @@ expression: "&view" "account_id_validity_rules_version": 1, "yield_timeout_length_in_blocks": 200, "max_yield_payload_size": 1024, - "per_receipt_storage_proof_size_limit": 999999999999999 + "per_receipt_storage_proof_size_limit": 4000000 } }, "account_creation_config": { "min_allowed_top_level_account_length": 65, "registrar_account_id": "registrar" }, + "congestion_control_config": { + "max_congestion_incoming_gas": 20000000000000000, + "max_congestion_outgoing_gas": 10000000000000000, + "max_congestion_memory_consumption": 1000000000, + "max_congestion_missed_chunks": 5, + "max_outgoing_gas": 300000000000000000, + "min_outgoing_gas": 1000000000000000, + "allowed_shard_outgoing_gas": 1000000000000000, + "max_tx_gas": 500000000000000, + "min_tx_gas": 20000000000000, + "reject_tx_congestion_threshold": 0.5, + "outgoing_receipts_usual_size_limit": 102400, + "outgoing_receipts_big_size_limit": 4718592 + }, "witness_config": { - "main_storage_proof_size_soft_limit": 999999999999999, - "combined_transactions_size_limit": 999999999999999, - "new_transactions_validation_state_size_soft_limit": 999999999999999 + "main_storage_proof_size_soft_limit": 3000000, + "combined_transactions_size_limit": 4194304, + "new_transactions_validation_state_size_soft_limit": 572864 } } diff --git a/core/primitives/src/state.rs b/core/primitives/src/state.rs index 668f090b753..3313101343b 100644 --- a/core/primitives/src/state.rs +++ b/core/primitives/src/state.rs @@ -74,7 +74,7 @@ impl FlatStateValue { /// in FlatState as `FlatStateValue::Ref`, otherwise the whole value will be /// stored as `FlatStateValue::Inlined`. /// See the following comment for reasoning behind the threshold value: - /// https://github.com/near/nearcore/issues/8243#issuecomment-1523049994 + /// pub const INLINE_DISK_VALUE_THRESHOLD: usize = 4000; pub fn on_disk(value: &[u8]) -> Self { diff --git a/core/primitives/src/stateless_validation.rs b/core/primitives/src/stateless_validation.rs index c4b50bc0716..f7d08e3e59d 100644 --- a/core/primitives/src/stateless_validation.rs +++ b/core/primitives/src/stateless_validation.rs @@ -14,7 +14,20 @@ use bytesize::ByteSize; use near_crypto::{PublicKey, Signature}; use near_primitives_core::hash::CryptoHash; use near_primitives_core::types::{AccountId, Balance, BlockHeight, ShardId}; -use near_primitives_core::version::PROTOCOL_VERSION; +use near_primitives_core::version::{ProtocolFeature, PROTOCOL_VERSION}; + +/// Represents max allowed size of the compressed state witness, +/// corresponds to EncodedChunkStateWitness struct size. +/// The value is set to max network message size when `test_features` +/// is enabled to make it possible to test blockchain behaviour with +/// arbitrary large witness (see #11703). +pub const MAX_COMPRESSED_STATE_WITNESS_SIZE: ByteSize = + ByteSize::mib(if cfg!(feature = "test_features") { 512 } else { 48 }); + +/// Represents max allowed size of the raw (not compressed) state witness, +/// corresponds to the size of borsh-serialized ChunkStateWitness. +pub const MAX_UNCOMPRESSED_STATE_WITNESS_SIZE: ByteSize = + ByteSize::mib(if cfg!(feature = "test_features") { 512 } else { 64 }); /// An arbitrary static string to make sure that this struct cannot be /// serialized to look identical to another serialized struct. For chunk @@ -52,7 +65,7 @@ impl PartialEncodedStateWitness { part_ord: usize, part: Vec, encoded_length: usize, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, ) -> Self { let inner = PartialEncodedStateWitnessInner::new( epoch_id, @@ -68,7 +81,7 @@ impl PartialEncodedStateWitness { pub fn chunk_production_key(&self) -> ChunkProductionKey { ChunkProductionKey { shard_id: self.shard_id(), - epoch_id: self.epoch_id().clone(), + epoch_id: *self.epoch_id(), height_created: self.height_created(), } } @@ -94,6 +107,10 @@ impl PartialEncodedStateWitness { self.inner.part_ord } + pub fn part_size(&self) -> usize { + self.inner.part.len() + } + /// Decomposes the partial witness to return (part_ord, part, encoded_length) pub fn decompose(self) -> (usize, Box<[u8]>, usize) { (self.inner.part_ord, self.inner.part, self.inner.encoded_length) @@ -167,10 +184,7 @@ impl EncodedChunkStateWitness { /// Returns decoded witness along with the raw (uncompressed) witness size. pub fn decode(&self) -> std::io::Result<(ChunkStateWitness, ChunkStateWitnessSize)> { // We want to limit the size of decompressed data to address "Zip bomb" attack. - // The value here is the same as NETWORK_MESSAGE_MAX_SIZE_BYTES. - const MAX_WITNESS_SIZE: ByteSize = ByteSize::mib(512); - - self.decode_with_limit(MAX_WITNESS_SIZE) + self.decode_with_limit(MAX_UNCOMPRESSED_STATE_WITNESS_SIZE) } /// Decompress and borsh-deserialize encoded witness bytes. @@ -343,12 +357,16 @@ impl ChunkStateWitness { pub fn chunk_production_key(&self) -> ChunkProductionKey { ChunkProductionKey { shard_id: self.chunk_header.shard_id(), - epoch_id: self.epoch_id.clone(), + epoch_id: self.epoch_id, height_created: self.chunk_header.height_created(), } } pub fn new_dummy(height: BlockHeight, shard_id: ShardId, prev_block_hash: CryptoHash) -> Self { + let congestion_info = ProtocolFeature::CongestionControl + .enabled(PROTOCOL_VERSION) + .then_some(CongestionInfo::default()); + let header = ShardChunkHeader::V3(ShardChunkHeaderV3::new( PROTOCOL_VERSION, prev_block_hash, @@ -364,8 +382,8 @@ impl ChunkStateWitness { Default::default(), Default::default(), Default::default(), - CongestionInfo::default(), - &EmptyValidatorSigner::default(), + congestion_info, + &EmptyValidatorSigner::default().into(), )); Self::new( "alice.near".parse().unwrap(), @@ -410,7 +428,7 @@ pub struct ChunkEndorsement { } impl ChunkEndorsement { - pub fn new(chunk_hash: ChunkHash, signer: &dyn ValidatorSigner) -> ChunkEndorsement { + pub fn new(chunk_hash: ChunkHash, signer: &ValidatorSigner) -> ChunkEndorsement { let inner = ChunkEndorsementInner::new(chunk_hash); let account_id = signer.validator_id().clone(); let signature = signer.sign_chunk_endorsement(&inner); diff --git a/core/primitives/src/telemetry.rs b/core/primitives/src/telemetry.rs index 83a1c11e107..4216b3725c5 100644 --- a/core/primitives/src/telemetry.rs +++ b/core/primitives/src/telemetry.rs @@ -9,6 +9,7 @@ pub struct TelemetryAgentInfo { pub name: String, pub version: String, pub build: String, + pub protocol_version: u32, } #[derive(serde::Serialize, Debug)] @@ -22,6 +23,7 @@ pub struct TelemetrySystemInfo { #[derive(serde::Serialize, Debug)] pub struct TelemetryChainInfo { + pub chain_id: String, pub node_id: String, pub account_id: Option, pub is_validator: bool, diff --git a/core/primitives/src/test_utils.rs b/core/primitives/src/test_utils.rs index 353c02d1eec..d711639b2b7 100644 --- a/core/primitives/src/test_utils.rs +++ b/core/primitives/src/test_utils.rs @@ -5,8 +5,7 @@ use crate::block_header::BlockHeader; use crate::challenge::Challenges; use crate::errors::EpochError; use crate::hash::CryptoHash; -use crate::merkle::PartialMerkleTree; -use crate::num_rational::Ratio; + use crate::sharding::{ShardChunkHeader, ShardChunkHeaderV3}; use crate::transaction::{ Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction, @@ -14,13 +13,11 @@ use crate::transaction::{ TransactionV0, TransactionV1, TransferAction, }; use crate::types::{AccountId, Balance, EpochId, EpochInfoProvider, Gas, Nonce}; -use crate::validator_signer::{InMemoryValidatorSigner, ValidatorSigner}; +use crate::validator_signer::ValidatorSigner; use crate::version::PROTOCOL_VERSION; use crate::views::{ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionStatus}; -use near_async::time::Clock; use near_crypto::vrf::Value; -use near_crypto::{EmptySigner, InMemorySigner, KeyType, PublicKey, SecretKey, Signature, Signer}; -use near_primitives_core::account::id::AccountIdRef; +use near_crypto::{EmptySigner, PublicKey, SecretKey, Signer}; use near_primitives_core::types::{ProtocolVersion, ShardId}; use std::collections::HashMap; use std::sync::Arc; @@ -80,7 +77,7 @@ impl Transaction { } } - pub fn sign(self, signer: &dyn Signer) -> SignedTransaction { + pub fn sign(self, signer: &Signer) -> SignedTransaction { let signature = signer.sign(self.get_hash_and_size().0.as_ref()); SignedTransaction::new(signature, self) } @@ -144,7 +141,7 @@ impl SignedTransaction { nonce: Nonce, signer_id: AccountId, receiver_id: AccountId, - signer: &dyn Signer, + signer: &Signer, actions: Vec, block_hash: CryptoHash, _priority_fee: u64, @@ -165,7 +162,7 @@ impl SignedTransaction { nonce: Nonce, signer_id: AccountId, receiver_id: AccountId, - signer: &dyn Signer, + signer: &Signer, actions: Vec, block_hash: CryptoHash, priority_fee: u64, @@ -186,7 +183,7 @@ impl SignedTransaction { nonce: Nonce, signer_id: AccountId, receiver_id: AccountId, - signer: &dyn Signer, + signer: &Signer, deposit: Balance, block_hash: CryptoHash, ) -> Self { @@ -204,7 +201,7 @@ impl SignedTransaction { pub fn stake( nonce: Nonce, signer_id: AccountId, - signer: &dyn Signer, + signer: &Signer, stake: Balance, public_key: PublicKey, block_hash: CryptoHash, @@ -226,7 +223,7 @@ impl SignedTransaction { new_account_id: AccountId, amount: Balance, public_key: PublicKey, - signer: &dyn Signer, + signer: &Signer, block_hash: CryptoHash, ) -> Self { Self::from_actions( @@ -247,6 +244,26 @@ impl SignedTransaction { ) } + pub fn deploy_contract( + nonce: Nonce, + contract_id: &AccountId, + code: Vec, + signer: &Signer, + block_hash: CryptoHash, + ) -> SignedTransaction { + let signer_id = contract_id.clone(); + let receiver_id = contract_id.clone(); + Self::from_actions( + nonce, + signer_id, + receiver_id, + signer, + vec![Action::DeployContract(DeployContractAction { code })], + block_hash, + 0, + ) + } + pub fn create_contract( nonce: Nonce, originator: AccountId, @@ -254,7 +271,7 @@ impl SignedTransaction { code: Vec, amount: Balance, public_key: PublicKey, - signer: &dyn Signer, + signer: &Signer, block_hash: CryptoHash, ) -> Self { Self::from_actions( @@ -280,7 +297,7 @@ impl SignedTransaction { nonce: Nonce, signer_id: AccountId, receiver_id: AccountId, - signer: &dyn Signer, + signer: &Signer, deposit: Balance, method_name: String, args: Vec, @@ -308,7 +325,7 @@ impl SignedTransaction { signer_id: AccountId, receiver_id: AccountId, beneficiary_id: AccountId, - signer: &dyn Signer, + signer: &Signer, block_hash: CryptoHash, ) -> Self { Self::from_actions( @@ -327,7 +344,7 @@ impl SignedTransaction { 0, "test".parse().unwrap(), "test".parse().unwrap(), - &EmptySigner {}, + &EmptySigner::new().into(), vec![], block_hash, 0, @@ -368,7 +385,7 @@ impl BlockHeader { } } - pub fn resign(&mut self, signer: &dyn ValidatorSigner) { + pub fn resign(&mut self, signer: &ValidatorSigner) { let (hash, signature) = signer.sign_block_header_parts( *self.prev_hash(), &self.inner_lite_bytes(), @@ -451,36 +468,38 @@ impl BlockBody { /// # Examples /// /// // TODO(mm-near): change it to doc-tested code once we have easy way to create a genesis block. -/// let signer = EmptyValidatorSigner::default(); +/// let signer = EmptyValidatorSigner::default().into(); /// let test_block = test_utils::TestBlockBuilder::new(prev, signer).height(33).build(); +#[cfg(feature = "clock")] pub struct TestBlockBuilder { - clock: Clock, + clock: near_time::Clock, prev: Block, - signer: Arc, + signer: Arc, height: u64, epoch_id: EpochId, next_epoch_id: EpochId, next_bp_hash: CryptoHash, - approvals: Vec>>, + approvals: Vec>>, block_merkle_root: CryptoHash, } +#[cfg(feature = "clock")] impl TestBlockBuilder { - pub fn new(clock: Clock, prev: &Block, signer: Arc) -> Self { - let mut tree = PartialMerkleTree::default(); + pub fn new(clock: near_time::Clock, prev: &Block, signer: Arc) -> Self { + let mut tree = crate::merkle::PartialMerkleTree::default(); tree.insert(*prev.hash()); - + let next_epoch_id = if prev.header().is_genesis() { + EpochId(*prev.hash()) + } else { + *prev.header().next_epoch_id() + }; Self { clock, prev: prev.clone(), - signer: signer.clone(), + signer, height: prev.header().height() + 1, - epoch_id: prev.header().epoch_id().clone(), - next_epoch_id: if prev.header().is_genesis() { - EpochId(*prev.hash()) - } else { - prev.header().next_epoch_id().clone() - }, + epoch_id: *prev.header().epoch_id(), + next_epoch_id, next_bp_hash: *prev.header().next_bp_hash(), approvals: vec![], block_merkle_root: tree.root(), @@ -502,13 +521,16 @@ impl TestBlockBuilder { self.next_bp_hash = next_bp_hash; self } - pub fn approvals(mut self, approvals: Vec>>) -> Self { + pub fn approvals(mut self, approvals: Vec>>) -> Self { self.approvals = approvals; self } /// Updates the merkle tree by adding the previous hash, and updates the new block's merkle_root. - pub fn block_merkle_tree(mut self, block_merkle_tree: &mut PartialMerkleTree) -> Self { + pub fn block_merkle_tree( + mut self, + block_merkle_tree: &mut crate::merkle::PartialMerkleTree, + ) -> Self { block_merkle_tree.insert(*self.prev.hash()); self.block_merkle_root = block_merkle_tree.root(); self @@ -528,7 +550,7 @@ impl TestBlockBuilder { self.next_epoch_id, None, self.approvals, - Ratio::new(0, 1), + num_rational::Ratio::new(0, 1), 0, 0, Some(0), @@ -537,7 +559,8 @@ impl TestBlockBuilder { self.signer.as_ref(), self.next_bp_hash, self.block_merkle_root, - self.clock.now_utc(), + self.clock, + None, ) } } @@ -715,12 +738,14 @@ pub fn encode(xs: &[u64]) -> Vec { // Helper function that creates a new signer for a given account, that uses the account name as seed. // Should be used only in tests. -pub fn create_test_signer(account_name: &str) -> InMemoryValidatorSigner { - InMemoryValidatorSigner::from_seed( +#[cfg(feature = "rand")] +pub fn create_test_signer(account_name: &str) -> ValidatorSigner { + crate::validator_signer::InMemoryValidatorSigner::from_seed( account_name.parse().unwrap(), - KeyType::ED25519, + near_crypto::KeyType::ED25519, account_name, ) + .into() } /// Helper function that creates a new signer for a given account, that uses the account name as seed. @@ -728,12 +753,22 @@ pub fn create_test_signer(account_name: &str) -> InMemoryValidatorSigner { /// This also works for predefined implicit accounts, where the signer will use the implicit key. /// /// Should be used only in tests. -pub fn create_user_test_signer(account_name: &AccountIdRef) -> InMemorySigner { +#[cfg(feature = "rand")] +pub fn create_user_test_signer( + account_name: &near_primitives_core::account::id::AccountIdRef, +) -> near_crypto::InMemorySigner { let account_id = account_name.to_owned(); if account_id == near_implicit_test_account() { - InMemorySigner::from_secret_key(account_id, near_implicit_test_account_secret()) + near_crypto::InMemorySigner::from_secret_key( + account_id, + near_implicit_test_account_secret(), + ) } else { - InMemorySigner::from_seed(account_id, KeyType::ED25519, account_name.as_str()) + near_crypto::InMemorySigner::from_seed( + account_id, + near_crypto::KeyType::ED25519, + account_name.as_str(), + ) } } diff --git a/core/primitives/src/transaction.rs b/core/primitives/src/transaction.rs index c0a1b15c13f..6f9941f9717 100644 --- a/core/primitives/src/transaction.rs +++ b/core/primitives/src/transaction.rs @@ -1,13 +1,13 @@ use crate::errors::TxExecutionError; use crate::hash::{hash, CryptoHash}; use crate::merkle::MerklePath; +use crate::profile_data_v3::ProfileDataV3; use crate::types::{AccountId, Balance, Gas, Nonce}; use borsh::{BorshDeserialize, BorshSerialize}; use near_crypto::{PublicKey, Signature}; use near_fmt::{AbbrBytes, Slice}; use near_primitives_core::serialize::{from_base64, to_base64}; use near_primitives_core::types::Compute; -use near_vm_runner::ProfileDataV3; use serde::de::Error as DecodeError; use serde::ser::Error as EncodeError; use std::borrow::Borrow; @@ -480,7 +480,8 @@ mod tests { #[test] fn test_verify_transaction() { - let signer = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer: Signer = + InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); let transaction = Transaction::V0(TransactionV0 { signer_id: "test".parse().unwrap(), public_key: signer.public_key(), diff --git a/core/primitives/src/trie_key.rs b/core/primitives/src/trie_key.rs index d2cb3ba9d1b..3fb7d16d493 100644 --- a/core/primitives/src/trie_key.rs +++ b/core/primitives/src/trie_key.rs @@ -35,7 +35,7 @@ pub mod col { /// This column id is used when storing: /// * the indices of the delayed receipts queue (a singleton per shard) /// * the delayed receipts themselves - /// The identifier is shared between two different key types for for historical reasons. It + /// The identifier is shared between two different key types for historical reasons. It /// is valid because the length of `TrieKey::DelayedReceipt` is always greater than /// `TrieKey::DelayedReceiptIndices` when serialized to bytes. pub const DELAYED_RECEIPT_OR_INDICES: u8 = 7; diff --git a/core/primitives/src/types.rs b/core/primitives/src/types.rs index 593910435b8..45c80a7ea1f 100644 --- a/core/primitives/src/types.rs +++ b/core/primitives/src/types.rs @@ -16,7 +16,7 @@ use std::sync::Arc; mod chunk_validator_stats; -pub use chunk_validator_stats::ChunkValidatorStats; +pub use chunk_validator_stats::ChunkStats; /// Hash used by to store state root. pub type StateRoot = CryptoHash; @@ -183,7 +183,7 @@ pub enum StateChangeCause { /// Updated delayed receipts queue in the state. /// We either processed previously delayed receipts or added more receipts to the delayed queue. UpdatedDelayedReceipts, - /// State change that happens when we update validator accounts. Not associated with with any + /// State change that happens when we update validator accounts. Not associated with any /// specific transaction or receipt. ValidatorAccountsUpdate, /// State change that is happens due to migration that happens in first block of an epoch @@ -480,6 +480,7 @@ impl StateRootNode { #[derive( Debug, Clone, + Copy, Default, Hash, Eq, @@ -744,9 +745,8 @@ pub mod chunk_extra { use crate::types::StateRoot; use borsh::{BorshDeserialize, BorshSerialize}; use near_primitives_core::hash::CryptoHash; - use near_primitives_core::types::{Balance, Gas}; + use near_primitives_core::types::{Balance, Gas, ProtocolVersion}; use near_primitives_core::version::{ProtocolFeature, PROTOCOL_VERSION}; - use near_vm_runner::logic::ProtocolVersion; pub use super::ChunkExtraV1; @@ -996,10 +996,18 @@ pub struct ValidatorStats { pub expected: NumBlocks, } +impl ValidatorStats { + /// Compare stats with threshold which is an expected percentage from 0 to + /// 100. + pub fn less_than(&self, threshold: u8) -> bool { + self.produced * 100 < u64::from(threshold) * self.expected + } +} + #[derive(Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] pub struct BlockChunkValidatorStats { pub block_stats: ValidatorStats, - pub chunk_stats: ChunkValidatorStats, + pub chunk_stats: ChunkStats, } #[derive(serde::Deserialize, Debug, arbitrary::Arbitrary, PartialEq, Eq)] @@ -1069,6 +1077,8 @@ pub enum ValidatorKickoutReason { }, /// Enough stake but is not chosen because of seat limits. DidNotGetASeat, + /// Validator didn't produce enough chunk endorsements. + NotEnoughChunkEndorsements { produced: NumBlocks, expected: NumBlocks }, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] @@ -1080,7 +1090,7 @@ pub enum TransactionOrReceiptId { /// Provides information about current epoch validators. /// Used to break dependency between epoch manager and runtime. -pub trait EpochInfoProvider { +pub trait EpochInfoProvider: Send + Sync { /// Get current stake of a validator in the given epoch. /// If the account is not a validator, returns `None`. fn validator_stake( diff --git a/core/primitives/src/types/chunk_validator_stats.rs b/core/primitives/src/types/chunk_validator_stats.rs index 0879b393247..40f962bffe3 100644 --- a/core/primitives/src/types/chunk_validator_stats.rs +++ b/core/primitives/src/types/chunk_validator_stats.rs @@ -6,21 +6,36 @@ use { /// An extension to `ValidatorStats` which also tracks endorsements /// coming from stateless validators. #[derive(Default, BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq)] -pub struct ChunkValidatorStats { +pub struct ChunkStats { pub production: ValidatorStats, pub endorsement: ValidatorStats, } -impl ChunkValidatorStats { +impl ChunkStats { + pub const fn new( + chunks_produced: u64, + chunks_expected: u64, + endorsements_produced: u64, + endorsements_expected: u64, + ) -> Self { + ChunkStats { + production: ValidatorStats { produced: chunks_produced, expected: chunks_expected }, + endorsement: ValidatorStats { + produced: endorsements_produced, + expected: endorsements_expected, + }, + } + } + pub const fn new_with_production(produced: u64, expected: u64) -> Self { - ChunkValidatorStats { + ChunkStats { production: ValidatorStats { produced, expected }, endorsement: ValidatorStats { produced: 0, expected: 0 }, } } pub const fn new_with_endorsement(produced: u64, expected: u64) -> Self { - ChunkValidatorStats { + ChunkStats { production: ValidatorStats { produced: 0, expected: 0 }, endorsement: ValidatorStats { produced, expected }, } @@ -42,6 +57,10 @@ impl ChunkValidatorStats { &mut self.production.expected } + pub fn production_stats(&self) -> &ValidatorStats { + &self.production + } + pub fn endorsement_stats(&self) -> &ValidatorStats { &self.endorsement } @@ -53,13 +72,13 @@ impl ChunkValidatorStats { #[test] fn test_mutability() { - let mut stats = ChunkValidatorStats::new_with_production(0, 0); + let mut stats = ChunkStats::new_with_production(0, 0); *stats.expected_mut() += 1; - assert_eq!(stats, ChunkValidatorStats::new_with_production(0, 1)); + assert_eq!(stats, ChunkStats::new_with_production(0, 1)); *stats.produced_mut() += 1; - assert_eq!(stats, ChunkValidatorStats::new_with_production(1, 1)); + assert_eq!(stats, ChunkStats::new_with_production(1, 1)); let endorsement_stats = stats.endorsement_stats_mut(); endorsement_stats.produced += 10; @@ -67,7 +86,7 @@ fn test_mutability() { assert_eq!( stats, - ChunkValidatorStats { + ChunkStats { production: ValidatorStats { produced: 1, expected: 1 }, endorsement: ValidatorStats { produced: 10, expected: 10 } } @@ -76,7 +95,7 @@ fn test_mutability() { *stats.expected_mut() += 1; assert_eq!( stats, - ChunkValidatorStats { + ChunkStats { production: ValidatorStats { produced: 1, expected: 2 }, endorsement: ValidatorStats { produced: 10, expected: 10 } } @@ -85,7 +104,7 @@ fn test_mutability() { *stats.produced_mut() += 1; assert_eq!( stats, - ChunkValidatorStats { + ChunkStats { production: ValidatorStats { produced: 2, expected: 2 }, endorsement: ValidatorStats { produced: 10, expected: 10 } } @@ -97,7 +116,7 @@ fn test_mutability() { assert_eq!( stats, - ChunkValidatorStats { + ChunkStats { production: ValidatorStats { produced: 2, expected: 2 }, endorsement: ValidatorStats { produced: 20, expected: 20 } } diff --git a/core/primitives/src/upgrade_schedule.rs b/core/primitives/src/upgrade_schedule.rs index a1f0560266c..09f55c203b0 100644 --- a/core/primitives/src/upgrade_schedule.rs +++ b/core/primitives/src/upgrade_schedule.rs @@ -100,6 +100,7 @@ impl ProtocolUpgradeVotingSchedule { } /// This method returns the protocol version that the node should vote for. + #[cfg(feature = "clock")] pub(crate) fn get_protocol_version( &self, now: DateTime, @@ -115,8 +116,9 @@ impl ProtocolUpgradeVotingSchedule { } // The datetime values in the schedule are sorted in ascending order. - // Find the last datetime value that is less than the current time. The - // schedule is sorted and the last value is the client_protocol_version + // Find the first datetime value that is less than the current time + // and higher than next_epoch_protocol_version. + // The schedule is sorted and the last value is the client_protocol_version // so we are guaranteed to find a correct protocol version. let mut result = next_epoch_protocol_version; for (time, version) in &self.schedule { @@ -124,6 +126,9 @@ impl ProtocolUpgradeVotingSchedule { break; } result = *version; + if *version > next_epoch_protocol_version { + break; + } } result @@ -332,13 +337,55 @@ mod tests { let now = ProtocolUpgradeVotingSchedule::parse_datetime("2000-01-15 00:00:00").unwrap(); assert_eq!( current_protocol_version + 2, - schedule.get_protocol_version(now, current_protocol_version) + schedule.get_protocol_version(now, current_protocol_version + 1) ); let now = ProtocolUpgradeVotingSchedule::parse_datetime("2000-01-20 00:00:00").unwrap(); assert_eq!( current_protocol_version + 2, + schedule.get_protocol_version(now, current_protocol_version + 1) + ); + } + + #[test] + fn test_upgrades_are_voted_one_at_a_time() { + let current_protocol_version = 100; + let client_protocol_version = 103; + let schedule = vec![ + ("2000-01-10 00:00:00", current_protocol_version + 1), + ("2000-01-10 01:00:00", current_protocol_version + 2), + ("2000-01-10 02:00:00", current_protocol_version + 3), + ]; + + let schedule = make_voting_schedule(client_protocol_version, schedule); + + // Test that the current version is returned before the first upgrade. + let now = ProtocolUpgradeVotingSchedule::parse_datetime("2000-01-05 00:00:00").unwrap(); + assert_eq!( + current_protocol_version, + schedule.get_protocol_version(now, current_protocol_version) + ); + + // Upgrades are scheduled very close to each other, but they should be voted on at a time. + // Test the first upgrade. + let now = ProtocolUpgradeVotingSchedule::parse_datetime("2000-01-10 10:00:00").unwrap(); + assert_eq!( + current_protocol_version + 1, schedule.get_protocol_version(now, current_protocol_version) ); + + // Test the second upgrade. + let now = ProtocolUpgradeVotingSchedule::parse_datetime("2000-01-10 10:00:00").unwrap(); + assert_eq!( + current_protocol_version + 2, + schedule.get_protocol_version(now, current_protocol_version + 1) + ); + + // Test the final upgrade. + let now = ProtocolUpgradeVotingSchedule::parse_datetime("2000-01-10 10:00:00").unwrap(); + assert_eq!( + current_protocol_version + 3, + schedule.get_protocol_version(now, current_protocol_version + 2) + ); } #[test] diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 7f111c6cf80..186a3b3bf34 100644 --- a/core/primitives/src/utils.rs +++ b/core/primitives/src/utils.rs @@ -3,9 +3,8 @@ use std::convert::AsRef; use std::fmt; use chrono; -use chrono::{DateTime, NaiveDateTime}; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; +use chrono::DateTime; + use serde; use crate::hash::{hash, CryptoHash}; @@ -414,8 +413,7 @@ macro_rules! unwrap_or_return { pub fn from_timestamp(timestamp: u64) -> DateTime { let secs = (timestamp / NS_IN_SECOND) as i64; let nsecs = (timestamp % NS_IN_SECOND) as u32; - let naive = NaiveDateTime::from_timestamp_opt(secs, nsecs).unwrap(); - DateTime::from_naive_utc_and_offset(naive, chrono::Utc) + DateTime::from_timestamp(secs, nsecs).unwrap() } /// Converts DateTime UTC time into timestamp in ns. @@ -441,7 +439,11 @@ pub fn get_num_seats_per_shard(num_shards: NumShards, num_seats: NumSeats) -> Ve } /// Generate random string of given length +#[cfg(feature = "rand")] pub fn generate_random_string(len: usize) -> String { + use rand::distributions::Alphanumeric; + use rand::{thread_rng, Rng}; + let bytes = thread_rng().sample_iter(&Alphanumeric).take(len).collect(); String::from_utf8(bytes).unwrap() } diff --git a/core/primitives/src/validator_mandates/mod.rs b/core/primitives/src/validator_mandates/mod.rs index 5e936b6492d..fbd729019f3 100644 --- a/core/primitives/src/validator_mandates/mod.rs +++ b/core/primitives/src/validator_mandates/mod.rs @@ -1,10 +1,6 @@ -use std::collections::HashMap; - use crate::types::{validator_stake::ValidatorStake, ValidatorId}; use borsh::{BorshDeserialize, BorshSerialize}; -use itertools::Itertools; use near_primitives_core::types::Balance; -use rand::{seq::SliceRandom, Rng}; mod compute_price; @@ -102,95 +98,105 @@ impl ValidatorMandates { Self { config, stake_per_mandate, mandates, partials } } +} - /// Returns a validator assignment obtained by shuffling mandates and assigning them to shards. - /// Shard ids are shuffled as well in this process to avoid a bias lower shard ids, see - /// [`ShuffledShardIds`]. - /// - /// It clones mandates since [`ValidatorMandates`] is supposed to be valid for an epoch, while a - /// new assignment is calculated at every height. - pub fn sample(&self, rng: &mut R) -> ChunkValidatorStakeAssignment - where - R: Rng + ?Sized, - { - // Shuffling shard ids to avoid a bias towards lower ids, see [`ShuffledShardIds`]. We - // do two separate shuffes for full and partial mandates to reduce the likelihood of - // assigning fewer full _and_ partial mandates to the _same_ shard. - let shard_ids_for_mandates = ShuffledShardIds::new(rng, self.config.num_shards); - let shard_ids_for_partials = ShuffledShardIds::new(rng, self.config.num_shards); - - let shuffled_mandates = self.shuffled_mandates(rng); - let shuffled_partials = self.shuffled_partials(rng); - - // Distribute shuffled mandates and partials across shards. For each shard with `shard_id` - // in `[0, num_shards)`, we take the elements of the vector with index `i` such that `i % - // num_shards == shard_id`. - // - // Assume, for example, there are 10 mandates and 4 shards. Then for `shard_id = 1` we - // collect the mandates with indices 1, 5, and 9. - let stake_per_mandate = self.stake_per_mandate; - let mut stake_assignment_per_shard = vec![HashMap::new(); self.config.num_shards]; - for shard_id in 0..self.config.num_shards { - // Achieve shard id shuffling by writing to the position of the alias of `shard_id`. - let mandates_assignment = - &mut stake_assignment_per_shard[shard_ids_for_mandates.get_alias(shard_id)]; - - // For the current `shard_id`, collect mandates with index `i` such that - // `i % num_shards == shard_id`. - for idx in (shard_id..shuffled_mandates.len()).step_by(self.config.num_shards) { - let validator_id = shuffled_mandates[idx]; - *mandates_assignment.entry(validator_id).or_default() += stake_per_mandate; +#[cfg(feature = "rand")] +mod validator_mandates_sample { + use super::*; + use itertools::Itertools; + use rand::{seq::SliceRandom, Rng}; + + impl ValidatorMandates { + /// Returns a validator assignment obtained by shuffling mandates and assigning them to shards. + /// Shard ids are shuffled as well in this process to avoid a bias lower shard ids, see + /// [`ShuffledShardIds`]. + /// + /// It clones mandates since [`ValidatorMandates`] is supposed to be valid for an epoch, while a + /// new assignment is calculated at every height. + pub fn sample(&self, rng: &mut R) -> ChunkValidatorStakeAssignment + where + R: Rng + ?Sized, + { + // Shuffling shard ids to avoid a bias towards lower ids, see [`ShuffledShardIds`]. We + // do two separate shuffes for full and partial mandates to reduce the likelihood of + // assigning fewer full _and_ partial mandates to the _same_ shard. + let shard_ids_for_mandates = ShuffledShardIds::new(rng, self.config.num_shards); + let shard_ids_for_partials = ShuffledShardIds::new(rng, self.config.num_shards); + + let shuffled_mandates = self.shuffled_mandates(rng); + let shuffled_partials = self.shuffled_partials(rng); + + // Distribute shuffled mandates and partials across shards. For each shard with `shard_id` + // in `[0, num_shards)`, we take the elements of the vector with index `i` such that `i % + // num_shards == shard_id`. + // + // Assume, for example, there are 10 mandates and 4 shards. Then for `shard_id = 1` we + // collect the mandates with indices 1, 5, and 9. + let stake_per_mandate = self.stake_per_mandate; + let mut stake_assignment_per_shard = + vec![std::collections::HashMap::new(); self.config.num_shards]; + for shard_id in 0..self.config.num_shards { + // Achieve shard id shuffling by writing to the position of the alias of `shard_id`. + let mandates_assignment = + &mut stake_assignment_per_shard[shard_ids_for_mandates.get_alias(shard_id)]; + + // For the current `shard_id`, collect mandates with index `i` such that + // `i % num_shards == shard_id`. + for idx in (shard_id..shuffled_mandates.len()).step_by(self.config.num_shards) { + let validator_id = shuffled_mandates[idx]; + *mandates_assignment.entry(validator_id).or_default() += stake_per_mandate; + } + + // Achieve shard id shuffling by writing to the position of the alias of `shard_id`. + let partials_assignment = + &mut stake_assignment_per_shard[shard_ids_for_partials.get_alias(shard_id)]; + + // For the current `shard_id`, collect partials with index `i` such that + // `i % num_shards == shard_id`. + for idx in (shard_id..shuffled_partials.len()).step_by(self.config.num_shards) { + let (validator_id, partial_weight) = shuffled_partials[idx]; + *partials_assignment.entry(validator_id).or_default() += partial_weight; + } } - // Achieve shard id shuffling by writing to the position of the alias of `shard_id`. - let partials_assignment = - &mut stake_assignment_per_shard[shard_ids_for_partials.get_alias(shard_id)]; - - // For the current `shard_id`, collect partials with index `i` such that - // `i % num_shards == shard_id`. - for idx in (shard_id..shuffled_partials.len()).step_by(self.config.num_shards) { - let (validator_id, partial_weight) = shuffled_partials[idx]; - *partials_assignment.entry(validator_id).or_default() += partial_weight; + // Deterministically shuffle the validator order for each shard + let mut ordered_stake_assignment_per_shard = Vec::with_capacity(self.config.num_shards); + for shard_id in 0..self.config.num_shards { + // first sort the validators by id then shuffle using rng + let stake_assignment = &stake_assignment_per_shard[shard_id]; + let mut ordered_validator_ids = stake_assignment.keys().sorted().collect_vec(); + ordered_validator_ids.shuffle(rng); + let ordered_mandate_assignment = ordered_validator_ids + .into_iter() + .map(|validator_id| (*validator_id, stake_assignment[validator_id])) + .collect_vec(); + ordered_stake_assignment_per_shard.push(ordered_mandate_assignment); } - } - // Deterministically shuffle the validator order for each shard - let mut ordered_stake_assignment_per_shard = Vec::with_capacity(self.config.num_shards); - for shard_id in 0..self.config.num_shards { - // first sort the validators by id then shuffle using rng - let stake_assignment = &stake_assignment_per_shard[shard_id]; - let mut ordered_validator_ids = stake_assignment.keys().sorted().collect_vec(); - ordered_validator_ids.shuffle(rng); - let ordered_mandate_assignment = ordered_validator_ids - .into_iter() - .map(|validator_id| (*validator_id, stake_assignment[validator_id])) - .collect_vec(); - ordered_stake_assignment_per_shard.push(ordered_mandate_assignment); + ordered_stake_assignment_per_shard } - ordered_stake_assignment_per_shard - } - - /// Clones the contained mandates and shuffles them. Cloning is required as a shuffle happens at - /// every height while the `ValidatorMandates` are to be valid for an epoch. - fn shuffled_mandates(&self, rng: &mut R) -> Vec - where - R: Rng + ?Sized, - { - let mut shuffled_mandates = self.mandates.clone(); - shuffled_mandates.shuffle(rng); - shuffled_mandates - } + /// Clones the contained mandates and shuffles them. Cloning is required as a shuffle happens at + /// every height while the `ValidatorMandates` are to be valid for an epoch. + pub(super) fn shuffled_mandates(&self, rng: &mut R) -> Vec + where + R: Rng + ?Sized, + { + let mut shuffled_mandates = self.mandates.clone(); + shuffled_mandates.shuffle(rng); + shuffled_mandates + } - /// Clones the contained partials and shuffles them. Cloning is required as a shuffle happens at - /// every height while the `ValidatorMandates` are to be valid for an epoch. - fn shuffled_partials(&self, rng: &mut R) -> Vec<(ValidatorId, Balance)> - where - R: Rng + ?Sized, - { - let mut shuffled_partials = self.partials.clone(); - shuffled_partials.shuffle(rng); - shuffled_partials + /// Clones the contained partials and shuffles them. Cloning is required as a shuffle happens at + /// every height while the `ValidatorMandates` are to be valid for an epoch. + pub(super) fn shuffled_partials(&self, rng: &mut R) -> Vec<(ValidatorId, Balance)> + where + R: Rng + ?Sized, + { + let mut shuffled_partials = self.partials.clone(); + shuffled_partials.shuffle(rng); + shuffled_partials + } } } @@ -225,11 +231,14 @@ struct ShuffledShardIds { shuffled_ids: Vec, } +#[cfg(feature = "rand")] impl ShuffledShardIds { fn new(rng: &mut R, num_shards: usize) -> Self where - R: Rng + ?Sized, + R: rand::Rng + ?Sized, { + use rand::seq::SliceRandom; + let mut shuffled_ids = (0..num_shards).collect::>(); shuffled_ids.shuffle(rng); Self { shuffled_ids } diff --git a/core/primitives/src/validator_signer.rs b/core/primitives/src/validator_signer.rs index 69fd3e41b43..1ccf530db97 100644 --- a/core/primitives/src/validator_signer.rs +++ b/core/primitives/src/validator_signer.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::path::Path; use std::sync::Arc; @@ -14,53 +15,125 @@ use crate::stateless_validation::{ use crate::telemetry::TelemetryInfo; use crate::types::{AccountId, BlockHeight, EpochId}; +/// Enum for validator signer, that holds validator id and key used for signing data. +#[derive(Clone, Debug, PartialEq)] +pub enum ValidatorSigner { + /// Dummy validator signer, does not hold a key. Use for tests only! + Empty(EmptyValidatorSigner), + /// Default validator signer that holds data in memory. + InMemory(InMemoryValidatorSigner), +} + /// Validator signer that is used to sign blocks and approvals. -pub trait ValidatorSigner: Sync + Send { +impl ValidatorSigner { /// Account id of the given validator. - fn validator_id(&self) -> &AccountId; + pub fn validator_id(&self) -> &AccountId { + match self { + ValidatorSigner::Empty(signer) => signer.validator_id(), + ValidatorSigner::InMemory(signer) => signer.validator_id(), + } + } /// Public key that identifies this validator. - fn public_key(&self) -> PublicKey; + pub fn public_key(&self) -> PublicKey { + match self { + ValidatorSigner::Empty(signer) => signer.public_key(), + ValidatorSigner::InMemory(signer) => signer.public_key(), + } + } /// Serializes telemetry info to JSON and signs it, returning JSON with "signature" field. - fn sign_telemetry(&self, info: &TelemetryInfo) -> serde_json::Value; + pub fn sign_telemetry(&self, info: &TelemetryInfo) -> serde_json::Value { + match self { + ValidatorSigner::Empty(signer) => signer.sign_telemetry(info), + ValidatorSigner::InMemory(signer) => signer.sign_telemetry(info), + } + } /// Signs given parts of the header. - fn sign_block_header_parts( + pub fn sign_block_header_parts( &self, prev_hash: CryptoHash, inner_lite: &[u8], inner_rest: &[u8], - ) -> (CryptoHash, Signature); + ) -> (CryptoHash, Signature) { + match self { + ValidatorSigner::Empty(signer) => { + signer.sign_block_header_parts(prev_hash, inner_lite, inner_rest) + } + ValidatorSigner::InMemory(signer) => { + signer.sign_block_header_parts(prev_hash, inner_lite, inner_rest) + } + } + } /// Signs given inner of the chunk header. - fn sign_chunk_hash(&self, chunk_hash: &ChunkHash) -> Signature; + pub fn sign_chunk_hash(&self, chunk_hash: &ChunkHash) -> Signature { + match self { + ValidatorSigner::Empty(signer) => signer.sign_chunk_hash(chunk_hash), + ValidatorSigner::InMemory(signer) => signer.sign_chunk_hash(chunk_hash), + } + } /// Signs approval of given parent hash and reference hash. - fn sign_approval(&self, inner: &ApprovalInner, target_height: BlockHeight) -> Signature; + pub fn sign_approval(&self, inner: &ApprovalInner, target_height: BlockHeight) -> Signature { + match self { + ValidatorSigner::Empty(signer) => signer.sign_approval(inner, target_height), + ValidatorSigner::InMemory(signer) => signer.sign_approval(inner, target_height), + } + } /// Signs chunk endorsement to be sent to block producer. - fn sign_chunk_endorsement(&self, inner: &ChunkEndorsementInner) -> Signature; + pub fn sign_chunk_endorsement(&self, inner: &ChunkEndorsementInner) -> Signature { + match self { + ValidatorSigner::Empty(signer) => signer.sign_chunk_endorsement(inner), + ValidatorSigner::InMemory(signer) => signer.sign_chunk_endorsement(inner), + } + } /// Signs chunk state witness to be sent to all validators. - fn sign_chunk_state_witness(&self, witness_bytes: &EncodedChunkStateWitness) -> Signature; + pub fn sign_chunk_state_witness(&self, witness_bytes: &EncodedChunkStateWitness) -> Signature { + match self { + ValidatorSigner::Empty(signer) => signer.sign_chunk_state_witness(witness_bytes), + ValidatorSigner::InMemory(signer) => signer.sign_chunk_state_witness(witness_bytes), + } + } /// Signs partial encoded state witness to be sent and forwarded to all validators. - fn sign_partial_encoded_state_witness( + pub fn sign_partial_encoded_state_witness( &self, part: &PartialEncodedStateWitnessInner, - ) -> Signature; + ) -> Signature { + match self { + ValidatorSigner::Empty(signer) => signer.sign_partial_encoded_state_witness(part), + ValidatorSigner::InMemory(signer) => signer.sign_partial_encoded_state_witness(part), + } + } /// Signs challenge body. - fn sign_challenge(&self, challenge_body: &ChallengeBody) -> (CryptoHash, Signature); + pub fn sign_challenge(&self, challenge_body: &ChallengeBody) -> (CryptoHash, Signature) { + match self { + ValidatorSigner::Empty(signer) => signer.sign_challenge(challenge_body), + ValidatorSigner::InMemory(signer) => signer.sign_challenge(challenge_body), + } + } /// Signs account announce. - fn sign_account_announce( + pub fn sign_account_announce( &self, account_id: &AccountId, peer_id: &PeerId, epoch_id: &EpochId, - ) -> Signature; + ) -> Signature { + match self { + ValidatorSigner::Empty(signer) => { + signer.sign_account_announce(account_id, peer_id, epoch_id) + } + ValidatorSigner::InMemory(signer) => { + signer.sign_account_announce(account_id, peer_id, epoch_id) + } + } + } /// Signs a proto-serialized AccountKeyPayload (see /// chain/network/src/network_protocol/network.proto). @@ -73,26 +146,57 @@ pub trait ValidatorSigner: Sync + Send { /// used only for networking purposes and are not persisted on chain. /// Moving to proto serialization for stuff stored on chain would be way /// harder. - fn sign_account_key_payload(&self, proto_bytes: &[u8]) -> Signature; + pub fn sign_account_key_payload(&self, proto_bytes: &[u8]) -> Signature { + match self { + ValidatorSigner::Empty(signer) => signer.sign_account_key_payload(proto_bytes), + ValidatorSigner::InMemory(signer) => signer.sign_account_key_payload(proto_bytes), + } + } - fn compute_vrf_with_proof( + pub fn compute_vrf_with_proof( &self, data: &[u8], - ) -> (near_crypto::vrf::Value, near_crypto::vrf::Proof); + ) -> (near_crypto::vrf::Value, near_crypto::vrf::Proof) { + match self { + ValidatorSigner::Empty(_) => unimplemented!(), + ValidatorSigner::InMemory(signer) => signer.compute_vrf_with_proof(data), + } + } /// Used by test infrastructure, only implement if make sense for testing otherwise raise `unimplemented`. - fn write_to_file(&self, path: &Path) -> std::io::Result<()>; + pub fn write_to_file(&self, path: &Path) -> std::io::Result<()> { + match self { + ValidatorSigner::Empty(_) => unimplemented!(), + ValidatorSigner::InMemory(signer) => signer.write_to_file(path), + } + } +} + +impl From for ValidatorSigner { + fn from(signer: EmptyValidatorSigner) -> Self { + ValidatorSigner::Empty(signer) + } +} + +impl From for ValidatorSigner { + fn from(signer: InMemoryValidatorSigner) -> Self { + ValidatorSigner::InMemory(signer) + } } /// Test-only signer that "signs" everything with 0s. /// Don't use in any production or code that requires signature verification. -#[derive(smart_default::SmartDefault)] +#[derive(smart_default::SmartDefault, Clone, Debug, PartialEq)] pub struct EmptyValidatorSigner { #[default("test".parse().unwrap())] account_id: AccountId, } -impl ValidatorSigner for EmptyValidatorSigner { +impl EmptyValidatorSigner { + pub fn new(account_id: AccountId) -> ValidatorSigner { + ValidatorSigner::Empty(Self { account_id }) + } + fn validator_id(&self) -> &AccountId { &self.account_id } @@ -154,34 +258,27 @@ impl ValidatorSigner for EmptyValidatorSigner { fn sign_account_key_payload(&self, _proto_bytes: &[u8]) -> Signature { Signature::default() } - - fn compute_vrf_with_proof( - &self, - _data: &[u8], - ) -> (near_crypto::vrf::Value, near_crypto::vrf::Proof) { - unimplemented!() - } - - fn write_to_file(&self, _path: &Path) -> std::io::Result<()> { - unimplemented!() - } } /// Signer that keeps secret key in memory and signs locally. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct InMemoryValidatorSigner { account_id: AccountId, - signer: Arc, + signer: Arc, } impl InMemoryValidatorSigner { + #[cfg(feature = "rand")] + pub fn from_random(account_id: AccountId, key_type: KeyType) -> Self { - let signer = Arc::new(InMemorySigner::from_random(account_id.clone(), key_type)); + let signer = Arc::new(InMemorySigner::from_random(account_id.clone(), key_type).into()); Self { account_id, signer } } + #[cfg(feature = "rand")] + pub fn from_seed(account_id: AccountId, key_type: KeyType, seed: &str) -> Self { - let signer = Arc::new(InMemorySigner::from_seed(account_id.clone(), key_type, seed)); + let signer = Arc::new(InMemorySigner::from_seed(account_id.clone(), key_type, seed).into()); Self { account_id, signer } } @@ -189,21 +286,19 @@ impl InMemoryValidatorSigner { self.signer.public_key() } + pub fn from_signer(signer: InMemorySigner) -> Self { + Self { account_id: signer.account_id.clone(), signer: Arc::new(signer.into()) } + } + pub fn from_file(path: &Path) -> std::io::Result { let signer = InMemorySigner::from_file(path)?; - Ok(Self { account_id: signer.account_id.clone(), signer: Arc::new(signer) }) + Ok(Self::from_signer(signer)) } -} -impl ValidatorSigner for InMemoryValidatorSigner { - fn validator_id(&self) -> &AccountId { + pub fn validator_id(&self) -> &AccountId { &self.account_id } - fn public_key(&self) -> PublicKey { - self.signer.public_key() - } - fn sign_telemetry(&self, info: &TelemetryInfo) -> serde_json::Value { let mut value = serde_json::to_value(info).expect("Telemetry must serialize to JSON"); let content = serde_json::to_string(&value).expect("Telemetry must serialize to JSON"); @@ -250,7 +345,7 @@ impl ValidatorSigner for InMemoryValidatorSigner { (hash, signature) } - fn sign_account_announce( + pub fn sign_account_announce( &self, account_id: &AccountId, peer_id: &PeerId, diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index 109e30e40a3..4a366459749 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -67,6 +67,10 @@ pub const PROTOCOL_UPGRADE_SCHEDULE: Lazy = Lazy: // that they are ordered by datetime and version, the last one is the // PROTOCOL_VERSION and that there is enough time between subsequent // upgrades. + // + // At most one protocol version upgrade vote can happen per epoch. If, by any + // chance, two or more votes get scheduled on the same epoch, the latest upgrades + // will be postponed. // e.g. // let v1_protocol_version = 50; @@ -83,7 +87,13 @@ pub const PROTOCOL_UPGRADE_SCHEDULE: Lazy = Lazy: /// Gives new clients an option to upgrade without announcing that they support /// the new version. This gives non-validator nodes time to upgrade. See /// -pub fn get_protocol_version(next_epoch_protocol_version: ProtocolVersion) -> ProtocolVersion { - let now = chrono::Utc::now(); - PROTOCOL_UPGRADE_SCHEDULE.get_protocol_version(now, next_epoch_protocol_version) +#[cfg(feature = "clock")] +pub fn get_protocol_version( + next_epoch_protocol_version: ProtocolVersion, + clock: near_time::Clock, +) -> ProtocolVersion { + let now = clock.now_utc(); + let chrono = chrono::DateTime::from_timestamp(now.unix_timestamp(), now.nanosecond()); + PROTOCOL_UPGRADE_SCHEDULE + .get_protocol_version(chrono.unwrap_or_default(), next_epoch_protocol_version) } diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 1d873f2d8d0..e53086ca60d 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -40,11 +40,13 @@ use crate::types::{ }; use crate::version::{ProtocolVersion, Version}; use borsh::{BorshDeserialize, BorshSerialize}; -use near_async::time::Utc; use near_crypto::{PublicKey, Signature}; use near_fmt::{AbbrBytes, Slice}; +use near_parameters::config::CongestionControlConfig; +use near_parameters::view::CongestionControlConfigView; use near_parameters::{ActionCosts, ExtCosts}; use near_primitives_core::version::PROTOCOL_VERSION; +use near_time::Utc; use serde_with::base64::Base64; use serde_with::serde_as; use std::collections::HashMap; @@ -81,29 +83,6 @@ pub struct ContractCodeView { pub hash: CryptoHash, } -/// State for the view call. -#[derive(Debug)] -pub struct ViewApplyState { - /// Currently building block height. - pub block_height: BlockHeight, - /// Prev block hash - pub prev_block_hash: CryptoHash, - /// Currently building block hash - pub block_hash: CryptoHash, - /// To which shard the applied chunk belongs. - pub shard_id: ShardId, - /// Current epoch id - pub epoch_id: EpochId, - /// Current epoch height - pub epoch_height: EpochHeight, - /// The current block timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC). - pub block_timestamp: u64, - /// Current Protocol version when we apply the state transition - pub current_protocol_version: ProtocolVersion, - /// Cache for compiled contracts. - pub cache: Option>, -} - impl From<&Account> for AccountView { fn from(account: &Account) -> Self { AccountView { @@ -354,12 +333,12 @@ pub struct StatusSyncInfo { pub latest_block_hash: CryptoHash, pub latest_block_height: BlockHeight, pub latest_state_root: CryptoHash, - #[serde(with = "near_async::time::serde_utc_as_iso")] + #[serde(with = "near_time::serde_utc_as_iso")] pub latest_block_time: Utc, pub syncing: bool, pub earliest_block_hash: Option, pub earliest_block_height: Option, - #[serde(with = "near_async::time::serde_opt_utc_as_iso")] + #[serde(with = "near_time::serde_opt_utc_as_iso")] pub earliest_block_time: Option, pub epoch_id: Option, pub epoch_start_height: Option, @@ -412,7 +391,7 @@ pub struct AccountDataView { pub peer_id: PublicKey, pub proxies: Vec, pub account_key: PublicKey, - #[serde(with = "near_async::time::serde_utc_as_iso")] + #[serde(with = "near_time::serde_utc_as_iso")] pub timestamp: Utc, } @@ -594,7 +573,7 @@ pub struct ChainProcessingInfo { pub struct BlockProcessingInfo { pub height: BlockHeight, pub hash: CryptoHash, - #[serde(with = "near_async::time::serde_utc_as_iso")] + #[serde(with = "near_time::serde_utc_as_iso")] pub received_timestamp: Utc, /// Time (in ms) between when the block was first received and when it was processed pub in_progress_ms: u128, @@ -660,10 +639,10 @@ pub struct ChunkProcessingInfo { pub created_by: Option, pub status: ChunkProcessingStatus, /// Timestamp of first time when we request for this chunk. - #[serde(with = "near_async::time::serde_opt_utc_as_iso")] + #[serde(with = "near_time::serde_opt_utc_as_iso")] pub requested_timestamp: Option, /// Timestamp of when the chunk is complete - #[serde(with = "near_async::time::serde_opt_utc_as_iso")] + #[serde(with = "near_time::serde_opt_utc_as_iso")] pub completed_timestamp: Option, /// Time (in millis) that it takes between when the chunk is requested and when it is completed. pub request_duration: Option, @@ -674,13 +653,13 @@ pub struct ChunkProcessingInfo { pub struct PartCollectionInfo { pub part_owner: AccountId, // Time when the part is received through any message - #[serde(with = "near_async::time::serde_opt_utc_as_iso")] + #[serde(with = "near_time::serde_opt_utc_as_iso")] pub received_time: Option, // Time when we receive a PartialEncodedChunkForward containing this part - #[serde(with = "near_async::time::serde_opt_utc_as_iso")] + #[serde(with = "near_time::serde_opt_utc_as_iso")] pub forwarded_received_time: Option, // Time when we receive the PartialEncodedChunk message containing this part - #[serde(with = "near_async::time::serde_opt_utc_as_iso")] + #[serde(with = "near_time::serde_opt_utc_as_iso")] pub chunk_received_time: Option, } @@ -2498,14 +2477,29 @@ impl From for CongestionInfo { } } +impl CongestionInfoView { + pub fn congestion_level(&self, config_view: CongestionControlConfigView) -> f64 { + let congestion_config = CongestionControlConfig::from(config_view); + // Localized means without considering missed chunks congestion. As far + // as clients are concerned, this is the only congestion level that + // matters. + // Missed chunks congestion exists to reduce incoming load after a + // number of chunks were missed. It is not a property of a specific + // chunk but rather a property that changes over time. It is even a bit + // misleading to call it congestion, as it is not a problem with too + // much traffic. + CongestionInfo::from(self.clone()).localized_congestion_level(&congestion_config) + } +} + #[cfg(test)] #[cfg(not(feature = "nightly"))] #[cfg(not(feature = "statelessnet_protocol"))] mod tests { use super::ExecutionMetadataView; use crate::profile_data_v2::ProfileDataV2; + use crate::profile_data_v3::ProfileDataV3; use crate::transaction::ExecutionMetadata; - use near_vm_runner::ProfileDataV3; /// The JSON representation used in RPC responses must not remove or rename /// fields, only adding fields is allowed or we risk breaking clients. diff --git a/core/store/Cargo.toml b/core/store/Cargo.toml index 0e9b811c087..4ae2190d87e 100644 --- a/core/store/Cargo.toml +++ b/core/store/Cargo.toml @@ -42,7 +42,7 @@ thiserror.workspace = true tokio.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-chain-configs = { workspace = true, features = ["metrics"] } near-crypto.workspace = true near-fmt.workspace = true @@ -80,8 +80,8 @@ single_thread_rocksdb = [] # Deactivate RocksDB IO background threads test_features = ["near-vm-runner/test_features"] new_epoch_sync = [] +# TODO(#11639): extract metrics into separate feature nightly_protocol = [ - "near-async/nightly_protocol", "near-chain-configs/nightly_protocol", "near-chain/nightly_protocol", "near-chunks/nightly_protocol", @@ -92,7 +92,6 @@ nightly_protocol = [ "near-vm-runner/nightly_protocol", ] nightly = [ - "near-async/nightly", "near-chain-configs/nightly", "near-chain/nightly", "near-chunks/nightly", @@ -103,3 +102,4 @@ nightly = [ "near-vm-runner/nightly", "nightly_protocol", ] +statelessnet_protocol = ["near-primitives/statelessnet_protocol"] diff --git a/core/store/benches/finalize_bench.rs b/core/store/benches/finalize_bench.rs index fc6d9bb0730..aedce9232ec 100644 --- a/core/store/benches/finalize_bench.rs +++ b/core/store/benches/finalize_bench.rs @@ -18,7 +18,7 @@ use bencher::{black_box, Bencher}; use borsh::BorshSerialize; use near_chain::Chain; use near_chunks::shards_manager_actor::ShardsManagerActor; -use near_crypto::{InMemorySigner, KeyType, Signer}; +use near_crypto::{InMemorySigner, KeyType}; use near_primitives::congestion_info::CongestionInfo; use near_primitives::hash::CryptoHash; use near_primitives::merkle::{merklize, MerklePathItem}; @@ -32,7 +32,7 @@ use near_primitives::sharding::{ use near_primitives::transaction::{Action, FunctionCallAction, SignedTransaction}; use near_primitives::types::AccountId; use near_primitives::validator_signer::InMemoryValidatorSigner; -use near_primitives::version::PROTOCOL_VERSION; +use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use near_store::DBCol; use rand::prelude::SliceRandom; use reed_solomon_erasure::galois_8::ReedSolomon; @@ -116,6 +116,10 @@ fn create_benchmark_receipts() -> Vec { } fn create_chunk_header(height: u64, shard_id: u64) -> ShardChunkHeader { + let congestion_info = ProtocolFeature::CongestionControl + .enabled(PROTOCOL_VERSION) + .then_some(CongestionInfo::default()); + ShardChunkHeader::V3(ShardChunkHeaderV3::new( PROTOCOL_VERSION, CryptoHash::default(), @@ -131,8 +135,8 @@ fn create_chunk_header(height: u64, shard_id: u64) -> ShardChunkHeader { CryptoHash::default(), CryptoHash::default(), vec![], - CongestionInfo::default(), - &validator_signer(), + congestion_info, + &validator_signer().into(), )) } @@ -184,6 +188,11 @@ fn create_encoded_shard_chunk( receipts: &[Receipt], ) -> (EncodedShardChunk, Vec>) { let rs = ReedSolomon::new(33, 67).unwrap(); + + let congestion_info = ProtocolFeature::CongestionControl + .enabled(PROTOCOL_VERSION) + .then_some(CongestionInfo::default()); + ShardsManagerActor::create_encoded_shard_chunk( Default::default(), Default::default(), @@ -198,8 +207,8 @@ fn create_encoded_shard_chunk( receipts, Default::default(), Default::default(), - CongestionInfo::default(), - &validator_signer(), + congestion_info, + &validator_signer().into(), &rs, 100, ) diff --git a/core/store/src/columns.rs b/core/store/src/columns.rs index 6c497ac24a2..01a6d9b2172 100644 --- a/core/store/src/columns.rs +++ b/core/store/src/columns.rs @@ -136,10 +136,9 @@ pub enum DBCol { /// - *Rows*: EpochId (CryptoHash) /// - *Content type*: LightClientBlockView EpochLightClientBlocks, - /// Mapping from Receipt id to destination Shard Id, i.e, the shard that this receipt is sent to. - /// - *Rows*: ReceiptId (CryptoHash) - /// - *Content type*: Shard Id || ref_count (u64 || u64) - ReceiptIdToShardId, + // Deprecated. + #[strum(serialize = "ReceiptIdToShardId")] + _ReceiptIdToShardId, // Deprecated. #[strum(serialize = "NextBlockWithNewChunk")] _NextBlockWithNewChunk, @@ -404,7 +403,7 @@ impl DBCol { /// ``` pub const fn is_rc(&self) -> bool { match self { - DBCol::State | DBCol::Transactions | DBCol::Receipts | DBCol::ReceiptIdToShardId => { + DBCol::State | DBCol::Transactions | DBCol::Receipts | DBCol::_ReceiptIdToShardId => { true } _ => false, @@ -439,7 +438,6 @@ impl DBCol { | DBCol::OutgoingReceipts // TODO can be changed to reconstruction on request instead of saving in cold storage. | DBCol::PartialChunks - | DBCol::ReceiptIdToShardId | DBCol::Receipts | DBCol::State | DBCol::StateChanges @@ -473,6 +471,8 @@ impl DBCol { // LatestChunkStateWitnesses stores the last N observed witnesses, used only for debugging. DBCol::LatestChunkStateWitnesses => false, DBCol::LatestWitnessesByIndex => false, + // Deprecated. + DBCol::_ReceiptIdToShardId => false, // Columns that are not GC-ed need not be copied to the cold storage. DBCol::BlockHeader @@ -543,7 +543,7 @@ impl DBCol { DBCol::AccountAnnouncements => &[DBKeyType::AccountId], DBCol::NextBlockHashes => &[DBKeyType::PreviousBlockHash], DBCol::EpochLightClientBlocks => &[DBKeyType::EpochId], - DBCol::ReceiptIdToShardId => &[DBKeyType::ReceiptHash], + DBCol::_ReceiptIdToShardId => &[DBKeyType::ReceiptHash], DBCol::_NextBlockWithNewChunk => &[DBKeyType::BlockHash, DBKeyType::ShardId], DBCol::_LastBlockWithNewChunk => &[DBKeyType::ShardId], DBCol::PeerComponent => &[DBKeyType::PeerId], diff --git a/core/store/src/db.rs b/core/store/src/db.rs index ee1ff8a1c1c..88d8381314c 100644 --- a/core/store/src/db.rs +++ b/core/store/src/db.rs @@ -21,6 +21,7 @@ pub use self::splitdb::SplitDB; pub use self::slice::DBSlice; pub use self::testdb::TestDB; +use std::sync::Arc; // `DBCol::BlockMisc` keys pub const HEAD_KEY: &[u8; 4] = b"HEAD"; @@ -33,6 +34,7 @@ pub const LATEST_KNOWN_KEY: &[u8; 12] = b"LATEST_KNOWN"; pub const LARGEST_TARGET_HEIGHT_KEY: &[u8; 21] = b"LARGEST_TARGET_HEIGHT"; pub const GENESIS_JSON_HASH_KEY: &[u8; 17] = b"GENESIS_JSON_HASH"; pub const GENESIS_STATE_ROOTS_KEY: &[u8; 19] = b"GENESIS_STATE_ROOTS"; +pub const GENESIS_CONGESTION_INFO_KEY: &[u8] = b"GENESIS_CONGESTION_INFO_KEY"; pub const COLD_HEAD_KEY: &[u8; 9] = b"COLD_HEAD"; pub const STATE_SYNC_DUMP_KEY: &[u8; 15] = b"STATE_SYNC_DUMP"; pub const STATE_SNAPSHOT_KEY: &[u8; 18] = b"STATE_SNAPSHOT_KEY"; @@ -249,6 +251,12 @@ pub trait Database: Sync + Send { path: &std::path::Path, columns_to_keep: Option<&[DBCol]>, ) -> anyhow::Result<()>; + + /// If this is a test database, return a copy of the entire database. + /// Otherwise return None. + fn copy_if_test(&self) -> Option> { + None + } } fn assert_no_overwrite(col: DBCol, key: &[u8], value: &[u8], old_value: &[u8]) { diff --git a/core/store/src/db/rocksdb.rs b/core/store/src/db/rocksdb.rs index aa37575d457..3b8d2836cd6 100644 --- a/core/store/src/db/rocksdb.rs +++ b/core/store/src/db/rocksdb.rs @@ -767,7 +767,7 @@ fn col_name(col: DBCol) -> &'static str { DBCol::AccountAnnouncements => "col24", DBCol::NextBlockHashes => "col25", DBCol::EpochLightClientBlocks => "col26", - DBCol::ReceiptIdToShardId => "col27", + DBCol::_ReceiptIdToShardId => "col27", DBCol::_NextBlockWithNewChunk => "col28", DBCol::_LastBlockWithNewChunk => "col29", DBCol::PeerComponent => "col30", diff --git a/core/store/src/db/testdb.rs b/core/store/src/db/testdb.rs index d1bc719ac83..25fc467b897 100644 --- a/core/store/src/db/testdb.rs +++ b/core/store/src/db/testdb.rs @@ -135,4 +135,19 @@ impl Database for TestDB { ) -> anyhow::Result<()> { Ok(()) } + + fn copy_if_test(&self) -> Option> { + let copy = Self::default(); + { + let mut db = copy.db.write().unwrap(); + for (col, map) in self.db.read().unwrap().iter() { + let new_col = &mut db[col]; + for (key, value) in map.iter() { + new_col.insert(key.clone(), value.clone()); + } + } + copy.stats.write().unwrap().clone_from(&self.stats.read().unwrap()); + } + Some(Arc::new(copy)) + } } diff --git a/core/store/src/flat/manager.rs b/core/store/src/flat/manager.rs index 1ea47f03573..3dd63a805a1 100644 --- a/core/store/src/flat/manager.rs +++ b/core/store/src/flat/manager.rs @@ -1,7 +1,6 @@ use crate::flat::{ store_helper, BlockInfo, FlatStorageReadyStatus, FlatStorageStatus, POISONED_LOCK_ERR, }; -use near_primitives::block::Block; use near_primitives::errors::StorageError; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardUId; @@ -10,7 +9,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use tracing::debug; -use crate::{get_genesis_hash, Store, StoreUpdate}; +use crate::{Store, StoreUpdate}; use super::chunk_view::FlatStorageChunkView; use super::{ @@ -87,24 +86,18 @@ impl FlatStorageManager { } /// Update flat storage for given processed or caught up block, which includes: - /// - merge deltas from current flat storage head to new one; - /// - update flat storage head to the hash of final block visible from given one; + /// - merge deltas from current flat storage head to new one given in + /// `new_flat_head`; + /// - update flat storage head to the new one; /// - remove info about unreachable blocks from memory. pub fn update_flat_storage_for_shard( &self, shard_uid: ShardUId, - block: &Block, + new_flat_head: CryptoHash, ) -> Result<(), StorageError> { if let Some(flat_storage) = self.get_flat_storage_for_shard(shard_uid) { - let mut new_flat_head = *block.header().last_final_block(); - if new_flat_head == CryptoHash::default() { - let genesis_hash = get_genesis_hash(&self.0.store) - .map_err(|e| FlatStorageError::StorageInternalError(e.to_string()))? - .expect("Genesis hash must exist. Consider initialization."); - new_flat_head = genesis_hash; - } // Try to update flat head. - flat_storage.update_flat_head(&new_flat_head, false).unwrap_or_else(|err| { + flat_storage.update_flat_head(&new_flat_head).unwrap_or_else(|err| { match &err { FlatStorageError::BlockNotSupported(_) => { // It's possible that new head is not a child of current flat head, e.g. when we have a @@ -122,7 +115,6 @@ impl FlatStorageManager { ?new_flat_head, ?err, ?shard_uid, - block_hash = ?block.header().hash(), "Cannot update flat head"); } _ => { @@ -132,7 +124,7 @@ impl FlatStorageManager { } }); } else { - tracing::debug!(target: "store", ?shard_uid, block_height=?block.header().height(), "No flat storage!!!"); + tracing::debug!(target: "store", ?shard_uid, ?new_flat_head, "No flat storage!!!"); } Ok(()) } diff --git a/core/store/src/flat/storage.rs b/core/store/src/flat/storage.rs index d2afd0a64d3..e16d4a6113a 100644 --- a/core/store/src/flat/storage.rs +++ b/core/store/src/flat/storage.rs @@ -342,6 +342,7 @@ impl FlatStorage { })?) } + // TODO(#11601): Direct call is DEPRECATED, consider removing non-strict mode. /// Update the head of the flat storage, including updating the flat state /// in memory and on disk and updating the flat state to reflect the state /// at the new head. If updating to given head is not possible, returns an @@ -365,7 +366,7 @@ impl FlatStorage { // new_head // // The segment [new_head, block_hash] contains two blocks with flat state changes. - pub fn update_flat_head( + fn update_flat_head_impl( &self, block_hash: &CryptoHash, strict: bool, @@ -436,6 +437,14 @@ impl FlatStorage { Ok(()) } + /// Update the head of the flat storage, including updating the flat state + /// in memory and on disk and updating the flat state to reflect the state + /// at the new head. If updating to given head is not possible, returns an + /// error. + pub fn update_flat_head(&self, block_hash: &CryptoHash) -> Result<(), FlatStorageError> { + self.update_flat_head_impl(block_hash, true) + } + /// Adds a delta (including the changes and block info) to flat storage, /// returns a StoreUpdate to store the delta on disk. Node that this StoreUpdate should be /// committed to disk in one db transaction together with the rest of changes caused by block, @@ -553,23 +562,23 @@ mod tests { // Check `BlockNotSupported` errors which are fine to occur during regular block processing. // First, check that flat head can be moved to block 1. let flat_head_hash = chain.get_block_hash(1); - assert_eq!(flat_storage.update_flat_head(&flat_head_hash, true), Ok(())); + assert_eq!(flat_storage.update_flat_head_impl(&flat_head_hash, true), Ok(())); // Check that attempt to move flat head to block 2 results in error because it lays in unreachable fork. let fork_block_hash = chain.get_block_hash(2); assert_eq!( - flat_storage.update_flat_head(&fork_block_hash, true), + flat_storage.update_flat_head_impl(&fork_block_hash, true), Err(FlatStorageError::BlockNotSupported((flat_head_hash, fork_block_hash))) ); // Check that attempt to move flat head to block 0 results in error because it is an unreachable parent. let parent_block_hash = chain.get_block_hash(0); assert_eq!( - flat_storage.update_flat_head(&parent_block_hash, true), + flat_storage.update_flat_head_impl(&parent_block_hash, true), Err(FlatStorageError::BlockNotSupported((flat_head_hash, parent_block_hash))) ); // Check that attempt to move flat head to non-existent block results in the same error. let not_existing_hash = hash(&[1, 2, 3]); assert_eq!( - flat_storage.update_flat_head(¬_existing_hash, true), + flat_storage.update_flat_head_impl(¬_existing_hash, true), Err(FlatStorageError::BlockNotSupported((flat_head_hash, not_existing_hash))) ); // Corrupt DB state for block 3 and try moving flat head to it. @@ -578,7 +587,7 @@ mod tests { store_helper::remove_delta(&mut store_update, shard_uid, chain.get_block_hash(3)); store_update.commit().unwrap(); assert_matches!( - flat_storage.update_flat_head(&chain.get_block_hash(3), true), + flat_storage.update_flat_head_impl(&chain.get_block_hash(3), true), Err(FlatStorageError::StorageInternalError(_)) ); } @@ -631,15 +640,15 @@ mod tests { // Simulates an actual sequence of calls to `update_flat_head()` in the // presence of forks. - assert_eq!(flat_storage.update_flat_head(&chain.get_block_hash(0), false), Ok(())); - assert_eq!(flat_storage.update_flat_head(&chain.get_block_hash(3), false), Ok(())); + assert_eq!(flat_storage.update_flat_head_impl(&chain.get_block_hash(0), false), Ok(())); + assert_eq!(flat_storage.update_flat_head_impl(&chain.get_block_hash(3), false), Ok(())); assert_matches!( - flat_storage.update_flat_head(&chain.get_block_hash(0), false), + flat_storage.update_flat_head_impl(&chain.get_block_hash(0), false), Err(FlatStorageError::BlockNotSupported(_)) ); - assert_eq!(flat_storage.update_flat_head(&chain.get_block_hash(6), false), Ok(())); + assert_eq!(flat_storage.update_flat_head_impl(&chain.get_block_hash(6), false), Ok(())); assert_matches!( - flat_storage.update_flat_head(&chain.get_block_hash(0), false), + flat_storage.update_flat_head_impl(&chain.get_block_hash(0), false), Err(FlatStorageError::BlockNotSupported(_)) ); } @@ -675,7 +684,7 @@ mod tests { // Check that flat head can be moved to block 8. let flat_head_hash = chain.get_block_hash(8); - assert_eq!(flat_storage.update_flat_head(&flat_head_hash, false), Ok(())); + assert_eq!(flat_storage.update_flat_head_impl(&flat_head_hash, false), Ok(())); } // This tests basic use cases for FlatStorageChunkView and FlatStorage. @@ -772,7 +781,7 @@ mod tests { // 5. Move the flat head to block 5, verify that chunk_view0 still returns the same values // and chunk_view1 returns an error. Also check that DBCol::FlatState is updated correctly - flat_storage.update_flat_head(&chain.get_block_hash(5), true).unwrap(); + flat_storage.update_flat_head_impl(&chain.get_block_hash(5), true).unwrap(); assert_eq!( store_helper::get_flat_state_value(&store, shard_uid, &[1]).unwrap(), Some(FlatStateValue::value_ref(&[5])) @@ -796,7 +805,7 @@ mod tests { // 6. Move the flat head to block 10, verify that chunk_view0 still returns the same values // Also checks that DBCol::FlatState is updated correctly. - flat_storage.update_flat_head(&chain.get_block_hash(10), true).unwrap(); + flat_storage.update_flat_head_impl(&chain.get_block_hash(10), true).unwrap(); let blocks = flat_storage.get_blocks_to_head(&chain.get_block_hash(10)).unwrap(); assert_eq!(blocks.len(), 0); assert_eq!(store_helper::get_flat_state_value(&store, shard_uid, &[1]).unwrap(), None); @@ -899,7 +908,7 @@ mod tests { // resulting in <=4 blocks on the way from the tip to the chosen head. for i in 2..num_blocks as BlockHeight { let final_block_hash = chain.get_block_hash(i - 2); - flat_storage.update_flat_head(&final_block_hash, false).unwrap(); + flat_storage.update_flat_head_impl(&final_block_hash, false).unwrap(); let block_hash = chain.get_block_hash(i); let blocks = flat_storage.get_blocks_to_head(&block_hash).unwrap(); @@ -965,7 +974,7 @@ mod tests { .collect::>(); let block_hash = chain.get_block_hash((num_blocks - 1) as BlockHeight); - flat_storage.update_flat_head(&block_hash, true).unwrap(); + flat_storage.update_flat_head_impl(&block_hash, true).unwrap(); let flat_head_hash = flat_storage.get_head_hash(); let flat_head_height = hashes.get(&flat_head_hash).unwrap(); @@ -1042,7 +1051,7 @@ mod tests { let mut max_lag = None; for i in 2..num_blocks as BlockHeight { let final_block_hash = chain.get_block_hash(i - 2); - flat_storage.update_flat_head(&final_block_hash, false).unwrap(); + flat_storage.update_flat_head_impl(&final_block_hash, false).unwrap(); let block_hash = chain.get_block_hash(i); let blocks = flat_storage.get_blocks_to_head(&block_hash).unwrap(); diff --git a/core/store/src/lib.rs b/core/store/src/lib.rs index 2da8087e764..904e67f9301 100644 --- a/core/store/src/lib.rs +++ b/core/store/src/lib.rs @@ -12,6 +12,7 @@ pub use crate::trie::{ }; use borsh::{BorshDeserialize, BorshSerialize}; pub use columns::DBCol; +use db::GENESIS_CONGESTION_INFO_KEY; pub use db::{ CHUNK_TAIL_KEY, COLD_HEAD_KEY, FINAL_HEAD_KEY, FORK_TAIL_KEY, GENESIS_JSON_HASH_KEY, GENESIS_STATE_ROOTS_KEY, HEADER_HEAD_KEY, HEAD_KEY, LARGEST_TARGET_HEIGHT_KEY, @@ -21,6 +22,7 @@ use metadata::{DbKind, DbVersion, KIND_KEY, VERSION_KEY}; use near_crypto::PublicKey; use near_fmt::{AbbrBytes, StorageKey}; use near_primitives::account::{AccessKey, Account}; +use near_primitives::congestion_info::CongestionInfo; pub use near_primitives::errors::{MissingTrieValueContext, StorageError}; use near_primitives::hash::CryptoHash; use near_primitives::receipt::{ @@ -1011,6 +1013,10 @@ pub fn get_genesis_state_roots(store: &Store) -> io::Result>(DBCol::BlockMisc, GENESIS_STATE_ROOTS_KEY) } +pub fn get_genesis_congestion_infos(store: &Store) -> io::Result>> { + store.get_ser::>(DBCol::BlockMisc, GENESIS_CONGESTION_INFO_KEY) +} + pub fn get_genesis_hash(store: &Store) -> io::Result> { store.get_ser::(DBCol::BlockMisc, GENESIS_JSON_HASH_KEY) } @@ -1027,6 +1033,15 @@ pub fn set_genesis_state_roots(store_update: &mut StoreUpdate, genesis_roots: &[ .expect("Borsh cannot fail"); } +pub fn set_genesis_congestion_infos( + store_update: &mut StoreUpdate, + congestion_infos: &[CongestionInfo], +) { + store_update + .set_ser(DBCol::BlockMisc, GENESIS_CONGESTION_INFO_KEY, &congestion_infos) + .expect("Borsh cannot fail"); +} + fn option_to_not_found(res: io::Result>, field_name: F) -> io::Result where F: std::string::ToString, diff --git a/core/store/src/metadata.rs b/core/store/src/metadata.rs index dbbc0932fae..a07581e3922 100644 --- a/core/store/src/metadata.rs +++ b/core/store/src/metadata.rs @@ -2,7 +2,7 @@ pub type DbVersion = u32; /// Current version of the database. -pub const DB_VERSION: DbVersion = 39; +pub const DB_VERSION: DbVersion = 40; /// Database version at which point DbKind was introduced. const DB_VERSION_WITH_KIND: DbVersion = 34; diff --git a/core/store/src/metrics.rs b/core/store/src/metrics.rs index a72506d74f7..dac5cd3279b 100644 --- a/core/store/src/metrics.rs +++ b/core/store/src/metrics.rs @@ -1,12 +1,12 @@ use crate::rocksdb_metrics::export_stats_as_metrics; use crate::{NodeStorage, Store, Temperature}; use actix_rt::ArbiterHandle; -use near_async::time::Duration; use near_o11y::metrics::{ exponential_buckets, try_create_histogram, try_create_histogram_vec, try_create_histogram_with_buckets, try_create_int_counter_vec, try_create_int_gauge, try_create_int_gauge_vec, Histogram, HistogramVec, IntCounterVec, IntGauge, IntGaugeVec, }; +use near_time::Duration; use once_cell::sync::Lazy; pub(crate) static DATABASE_OP_LATENCY_HIST: Lazy = Lazy::new(|| { @@ -584,8 +584,8 @@ mod test { use crate::metadata::{DbKind, DB_VERSION}; use crate::test_utils::create_test_node_storage_with_cold; use actix; - use near_async::time::Duration; use near_o11y::testonly::init_test_logger; + use near_time::Duration; use super::spawn_db_metrics_loop; diff --git a/core/store/src/migrations.rs b/core/store/src/migrations.rs index 0ba73a4592b..a3de5fccfd1 100644 --- a/core/store/src/migrations.rs +++ b/core/store/src/migrations.rs @@ -10,7 +10,7 @@ use near_primitives::types::{ validator_stake::ValidatorStake, AccountId, EpochId, ShardId, ValidatorId, ValidatorKickoutReason, ValidatorStats, }; -use near_primitives::types::{BlockChunkValidatorStats, ChunkValidatorStats}; +use near_primitives::types::{BlockChunkValidatorStats, ChunkStats}; use near_primitives::utils::get_outcome_id_block_hash; use near_primitives::version::ProtocolVersion; use std::collections::{BTreeMap, HashMap}; @@ -239,6 +239,27 @@ pub fn migrate_37_to_38(store: &Store) -> anyhow::Result<()> { Ok(()) } +/// `ValidatorKickoutReason` struct layout before DB version 38, included. +#[derive(BorshDeserialize)] +struct LegacyBlockChunkValidatorStatsV38 { + pub block_stats: ValidatorStats, + pub chunk_stats: ValidatorStats, +} + +/// `ValidatorKickoutReason` struct layout before DB version 38, included. +#[derive(BorshDeserialize)] +struct LegacyEpochSummaryV38 { + pub prev_epoch_last_block_hash: CryptoHash, + /// Proposals from the epoch, only the latest one per account + pub all_proposals: Vec, + /// Kickout set, includes slashed + pub validator_kickout: HashMap, + /// Only for validators who met the threshold and didn't get slashed + pub validator_block_chunk_stats: HashMap, + /// Protocol version for next epoch. + pub next_version: ProtocolVersion, +} + /// Migrates the database from version 38 to 39. /// /// Rewrites Epoch summary to include endorsement stats. @@ -260,26 +281,7 @@ pub fn migrate_38_to_39(store: &Store) -> anyhow::Result<()> { } type LegacyEpochInfoAggregator = EpochInfoAggregator; - type NewEpochInfoAggregator = EpochInfoAggregator; - - #[derive(BorshDeserialize)] - struct LegacyBlockChunkValidatorStats { - pub block_stats: ValidatorStats, - pub chunk_stats: ValidatorStats, - } - - #[derive(BorshDeserialize)] - struct LegacyEpochSummary { - pub prev_epoch_last_block_hash: CryptoHash, - /// Proposals from the epoch, only the latest one per account - pub all_proposals: Vec, - /// Kickout set, includes slashed - pub validator_kickout: HashMap, - /// Only for validators who met the threshold and didn't get slashed - pub validator_block_chunk_stats: HashMap, - /// Protocol version for next epoch. - pub next_version: ProtocolVersion, - } + type NewEpochInfoAggregator = EpochInfoAggregator; let mut update = store.store_update(); @@ -298,10 +300,7 @@ pub fn migrate_38_to_39(store: &Store) -> anyhow::Result<()> { .map(|(validator_id, stats)| { ( validator_id, - ChunkValidatorStats::new_with_production( - stats.produced, - stats.expected, - ), + ChunkStats::new_with_production(stats.produced, stats.expected), ) }) .collect(); @@ -319,7 +318,7 @@ pub fn migrate_38_to_39(store: &Store) -> anyhow::Result<()> { // Update EpochSummary for result in store.iter(DBCol::EpochValidatorInfo) { let (key, old_value) = result?; - let legacy_summary = LegacyEpochSummary::try_from_slice(&old_value)?; + let legacy_summary = LegacyEpochSummaryV38::try_from_slice(&old_value)?; let new_value = EpochSummary { prev_epoch_last_block_hash: legacy_summary.prev_epoch_last_block_hash, all_proposals: legacy_summary.all_proposals, @@ -330,7 +329,7 @@ pub fn migrate_38_to_39(store: &Store) -> anyhow::Result<()> { .map(|(account_id, stats)| { let new_stats = BlockChunkValidatorStats { block_stats: stats.block_stats, - chunk_stats: ChunkValidatorStats::new_with_production( + chunk_stats: ChunkStats::new_with_production( stats.chunk_stats.produced, stats.chunk_stats.expected, ), @@ -346,3 +345,16 @@ pub fn migrate_38_to_39(store: &Store) -> anyhow::Result<()> { update.commit()?; Ok(()) } + +/// Migrates the database from version 39 to 40. +/// +/// This involves deleting contents of _ReceiptIdToShardId column which is now +/// deprecated and no longer used. +pub fn migrate_39_to_40(store: &Store) -> anyhow::Result<()> { + let _span = + tracing::info_span!(target: "migrations", "Deleting contents of deprecated _ReceiptIdToShardId column").entered(); + let mut update = store.store_update(); + update.delete_all(DBCol::_ReceiptIdToShardId); + update.commit()?; + Ok(()) +} diff --git a/core/store/src/opener.rs b/core/store/src/opener.rs index cdcdd81b573..41a855031cb 100644 --- a/core/store/src/opener.rs +++ b/core/store/src/opener.rs @@ -590,6 +590,9 @@ pub fn checkpoint_hot_storage_and_cleanup_columns( let _span = tracing::info_span!(target: "state_snapshot", "checkpoint_hot_storage_and_cleanup_columns") .entered(); + if let Some(storage) = hot_store.storage.copy_if_test() { + return Ok(NodeStorage::new(storage)); + } let checkpoint_path = checkpoint_base_path.join("data"); std::fs::create_dir_all(&checkpoint_base_path)?; diff --git a/core/store/src/test_utils.rs b/core/store/src/test_utils.rs index db4480f85ef..5a5bb2f8dda 100644 --- a/core/store/src/test_utils.rs +++ b/core/store/src/test_utils.rs @@ -179,7 +179,7 @@ impl TestTriesBuilder { } update_for_chunk_extra.commit().unwrap(); - tries.load_mem_tries_for_enabled_shards(&shard_uids).unwrap(); + tries.load_mem_tries_for_enabled_shards(&shard_uids, false).unwrap(); } tries } diff --git a/core/store/src/trie/accounting_cache.rs b/core/store/src/trie/accounting_cache.rs index d0d1a6c7f26..7c6fe1ed24c 100644 --- a/core/store/src/trie/accounting_cache.rs +++ b/core/store/src/trie/accounting_cache.rs @@ -8,6 +8,19 @@ use near_primitives::shard_layout::ShardUId; use std::collections::HashMap; use std::sync::Arc; +/// Switch that controls whether the `TrieAccountingCache` is enabled. +pub struct TrieAccountingCacheSwitch(Arc); + +impl TrieAccountingCacheSwitch { + pub fn set(&self, enabled: bool) { + self.0.store(enabled, std::sync::atomic::Ordering::Relaxed); + } + + pub fn enabled(&self) -> bool { + self.0.load(std::sync::atomic::Ordering::Relaxed) + } +} + /// Deterministic cache to store trie nodes that have been accessed so far /// during the cache's lifetime. It is used for deterministic gas accounting /// so that previously accessed trie nodes and values are charged at a @@ -41,7 +54,7 @@ use std::sync::Arc; pub struct TrieAccountingCache { /// Whether the cache is enabled. By default it is not, but it can be /// turned on or off on the fly. - enable: bool, + enable: TrieAccountingCacheSwitch, /// Cache of trie node hash -> trie node body, or a leaf value hash -> /// leaf value. cache: HashMap>, @@ -78,11 +91,12 @@ impl TrieAccountingCache { accounting_cache_size: metrics::CHUNK_CACHE_SIZE.with_label_values(&metrics_labels), } }); - Self { enable: false, cache: HashMap::new(), db_read_nodes: 0, mem_read_nodes: 0, metrics } + let switch = TrieAccountingCacheSwitch(Default::default()); + Self { enable: switch, cache: HashMap::new(), db_read_nodes: 0, mem_read_nodes: 0, metrics } } - pub fn set_enabled(&mut self, enabled: bool) { - self.enable = enabled; + pub fn enable_switch(&self) -> TrieAccountingCacheSwitch { + TrieAccountingCacheSwitch(Arc::clone(&self.enable.0)) } /// Retrieve raw bytes from the cache if it exists, otherwise retrieve it @@ -105,7 +119,7 @@ impl TrieAccountingCache { } let node = storage.retrieve_raw_bytes(hash)?; - if self.enable { + if self.enable.enabled() { self.cache.insert(*hash, node.clone()); if let Some(metrics) = &self.metrics { metrics.accounting_cache_size.set(self.cache.len() as i64); @@ -123,7 +137,7 @@ impl TrieAccountingCache { } else { self.db_read_nodes += 1; } - if self.enable { + if self.enable.enabled() { self.cache.insert(hash, data); if let Some(metrics) = &self.metrics { metrics.accounting_cache_size.set(self.cache.len() as i64); diff --git a/core/store/src/trie/mem/arena/alloc.rs b/core/store/src/trie/mem/arena/alloc.rs index 04ef5221c43..d78c2a70563 100644 --- a/core/store/src/trie/mem/arena/alloc.rs +++ b/core/store/src/trie/mem/arena/alloc.rs @@ -45,11 +45,11 @@ pub struct Allocator { const MAX_ALLOC_SIZE: usize = 16 * 1024; const ROUND_UP_TO_8_BYTES_UNDER: usize = 256; const ROUND_UP_TO_64_BYTES_UNDER: usize = 1024; -const CHUNK_SIZE: usize = 4 * 1024 * 1024; +pub(crate) const CHUNK_SIZE: usize = 4 * 1024 * 1024; /// Calculates the allocation class (an index from 0 to NUM_ALLOCATION_CLASSES) /// for the given size that we wish to allocate. -const fn allocation_class(size: usize) -> usize { +pub(crate) const fn allocation_class(size: usize) -> usize { if size <= ROUND_UP_TO_8_BYTES_UNDER { (size + 7) / 8 - 1 } else if size <= ROUND_UP_TO_64_BYTES_UNDER { @@ -61,7 +61,7 @@ const fn allocation_class(size: usize) -> usize { } /// Calculates the size of the actual allocation for the given size class. -const fn allocation_size(size_class: usize) -> usize { +pub(crate) const fn allocation_size(size_class: usize) -> usize { if size_class <= allocation_class(ROUND_UP_TO_8_BYTES_UNDER) { (size_class + 1) * 8 } else if size_class <= allocation_class(ROUND_UP_TO_64_BYTES_UNDER) { @@ -88,13 +88,30 @@ impl Allocator { } } + pub fn new_with_initial_stats( + name: String, + active_allocs_bytes: usize, + active_allocs_count: usize, + ) -> Self { + let mut allocator = Self::new(name); + allocator.active_allocs_bytes = active_allocs_bytes; + allocator.active_allocs_count = active_allocs_count; + allocator.active_allocs_bytes_gauge.set(active_allocs_bytes as i64); + allocator.active_allocs_count_gauge.set(active_allocs_count as i64); + allocator + } + + pub fn update_memory_usage_gauge(&self, memory: &STArenaMemory) { + self.memory_usage_gauge.set(memory.chunks.len() as i64 * CHUNK_SIZE as i64); + } + /// Adds a new chunk to the arena, and updates the next_alloc_pos to the beginning of /// the new chunk. fn new_chunk(&mut self, memory: &mut STArenaMemory) { memory.chunks.push(vec![0; CHUNK_SIZE]); self.next_alloc_pos = ArenaPos { chunk: u32::try_from(memory.chunks.len() - 1).unwrap(), pos: 0 }; - self.memory_usage_gauge.set(memory.chunks.len() as i64 * CHUNK_SIZE as i64); + self.update_memory_usage_gauge(memory); } /// Allocates a slice of the given size in the arena. @@ -145,6 +162,11 @@ impl Allocator { pub fn num_active_allocs(&self) -> usize { self.active_allocs_count } + + #[cfg(test)] + pub fn active_allocs_bytes(&self) -> usize { + self.active_allocs_bytes + } } #[cfg(test)] diff --git a/core/store/src/trie/mem/arena/concurrent.rs b/core/store/src/trie/mem/arena/concurrent.rs new file mode 100644 index 00000000000..4f6bf34f138 --- /dev/null +++ b/core/store/src/trie/mem/arena/concurrent.rs @@ -0,0 +1,261 @@ +use super::alloc::{allocation_class, allocation_size, CHUNK_SIZE}; +use super::{Arena, ArenaMemory, ArenaPos, ArenaSliceMut, STArena}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +/// Arena that can be allocated on from multiple threads, but still allowing conversion to a +/// single-threaded `STArena` afterwards. +/// +/// The `ConcurrentArena` cannot be directly used; rather, for each thread wishing to use it, +/// `for_thread` must be called to get a `ConcurrentArenaForThread`, which then acts like a +/// normal arena, except that deallocation is not supported. +/// +/// The only synchronization they need is the chunk counter. The purpose is so that after +/// multiple threads allocate on their own arenas, the resulting memory can still be combined +/// into a single arena while allowing the pointers (ArenaPos) to still be valid. This is what +/// allows a memtrie to be loaded in parallel. +pub struct ConcurrentArena { + /// Chunks allocated by each `ConcurrentArenaForThread` share the same "logical" memory + /// space. This counter is used to ensure that each `ConcurrentArenaForThread` gets unique + /// chunk positions, so that the allocations made by different threads do not conflict in + /// their positions. + /// + /// The goal here is so that allocations coming from multiple threads can be merged into a + /// single arena at the end, without having to alter any arena pointers (ArenaPos). + next_chunk_pos: Arc, +} + +impl ConcurrentArena { + pub fn new() -> Self { + Self { next_chunk_pos: Arc::new(AtomicUsize::new(0)) } + } + + /// Returns an arena that can be used for one thread. + pub fn for_thread(&self) -> ConcurrentArenaForThread { + ConcurrentArenaForThread::new(self.next_chunk_pos.clone()) + } + + /// Converts the arena to a single-threaded arena. All returned values of `for_thread` must be + /// passed in. + /// + /// There is a caveat that may be fixed in the future if desired: the returned arena will have + /// some memory wasted. This is because the last chunk of each thread may not be full, and + /// the single-threaded arena is unable to make use of multiple partially filled chunks. The + /// maximum memory wasted is 4MB * number of threads; the average is 2MB * number of threads. + /// The wasted memory will be reclaimed when the memtrie shard is unloaded. + pub fn to_single_threaded( + self, + name: String, + threads: Vec, + ) -> STArena { + let mut chunks = vec![Vec::new(); self.next_chunk_pos.load(Ordering::Relaxed)]; + let mut active_allocs_bytes = 0; + let mut active_allocs_count = 0; + for thread in threads { + let memory = thread.memory; + for (pos, chunk) in memory.chunks.into_iter() { + assert!( + chunks[pos].is_empty(), + "Arena threads from the same ConcurrentArena passed in" + ); + chunks[pos] = chunk; + } + active_allocs_bytes += thread.allocator.active_allocs_bytes; + active_allocs_count += thread.allocator.active_allocs_count; + } + for chunk in &chunks { + assert!(!chunks.is_empty(), "Not all arena threads are passed in"); + assert_eq!(chunk.len(), CHUNK_SIZE); // may as well check this + } + STArena::new_from_existing_chunks(name, chunks, active_allocs_bytes, active_allocs_count) + } +} + +/// Arena to be used for a single thread. +pub struct ConcurrentArenaForThread { + memory: ConcurrentArenaMemory, + allocator: ConcurrentArenaAllocator, +} + +pub struct ConcurrentArenaMemory { + /// Chunks of memory allocated for this thread. The usize is the global chunk position. + chunks: Vec<(usize, Vec)>, + /// Index is global chunk position, value is local chunk position. + /// For a chunk position that does not belong to the thread, the value is `usize::MAX`. + /// This vector is as large as needed to contain the largest global chunk position used + /// by this thread, but might not be as large as the total number of chunks allocated + /// globally. + chunk_pos_global_to_local: Vec, +} + +impl ConcurrentArenaMemory { + pub fn new() -> Self { + Self { chunks: Vec::new(), chunk_pos_global_to_local: Vec::new() } + } + + pub fn add_chunk(&mut self, pos: usize) { + while self.chunk_pos_global_to_local.len() <= pos { + self.chunk_pos_global_to_local.push(usize::MAX); + } + self.chunk_pos_global_to_local[pos] = self.chunks.len(); + self.chunks.push((pos, vec![0; CHUNK_SIZE])); + } + + pub fn chunk(&self, pos: usize) -> &[u8] { + let index = self.chunk_pos_global_to_local[pos]; + &self.chunks[index].1 + } + + pub fn chunk_mut(&mut self, pos: usize) -> &mut [u8] { + let index = self.chunk_pos_global_to_local[pos]; + &mut self.chunks[index].1 + } +} + +impl ArenaMemory for ConcurrentArenaMemory { + fn raw_slice(&self, pos: ArenaPos, len: usize) -> &[u8] { + &self.chunk(pos.chunk())[pos.pos()..pos.pos() + len] + } + + fn raw_slice_mut(&mut self, pos: ArenaPos, len: usize) -> &mut [u8] { + &mut self.chunk_mut(pos.chunk())[pos.pos()..pos.pos() + len] + } +} + +/// Allocator for a single thread. Unlike the allocator for `STArena`, this one only supports +/// allocation and not deallocation, so it is substantially simpler. +pub struct ConcurrentArenaAllocator { + next_chunk_pos: Arc, + next_pos: ArenaPos, + + // Stats that will be transferred to the single-threaded arena. + active_allocs_bytes: usize, + active_allocs_count: usize, +} + +impl ConcurrentArenaAllocator { + fn new(next_chunk_pos: Arc) -> Self { + Self { + next_chunk_pos, + next_pos: ArenaPos::invalid(), + active_allocs_bytes: 0, + active_allocs_count: 0, + } + } + + pub fn allocate<'a>( + &mut self, + arena: &'a mut ConcurrentArenaMemory, + size: usize, + ) -> ArenaSliceMut<'a, ConcurrentArenaMemory> { + // We must allocate in the same kind of sizes as the single-threaded arena, + // so that after converting to `STArena`, these allocations can be properly + // reused. + let size_class = allocation_class(size); + let allocation_size = allocation_size(size_class); + if self.next_pos.is_invalid() || self.next_pos.pos() + allocation_size > CHUNK_SIZE { + let next_chunk_pos = self.next_chunk_pos.fetch_add(1, Ordering::Relaxed); + self.next_pos = ArenaPos { chunk: next_chunk_pos as u32, pos: 0 }; + arena.add_chunk(next_chunk_pos); + } + let pos = self.next_pos; + self.next_pos = pos.offset_by(allocation_size); + self.active_allocs_bytes += allocation_size; + self.active_allocs_count += 1; + ArenaSliceMut::new(arena, pos, size) + } +} + +impl ConcurrentArenaForThread { + fn new(next_chunk_pos: Arc) -> Self { + Self { + memory: ConcurrentArenaMemory::new(), + allocator: ConcurrentArenaAllocator::new(next_chunk_pos), + } + } +} + +impl Arena for ConcurrentArenaForThread { + type Memory = ConcurrentArenaMemory; + + fn memory(&self) -> &Self::Memory { + &self.memory + } + + fn memory_mut(&mut self) -> &mut Self::Memory { + &mut self.memory + } + + fn alloc(&mut self, size: usize) -> ArenaSliceMut { + self.allocator.allocate(&mut self.memory, size) + } +} + +#[cfg(test)] +mod tests { + use super::ConcurrentArena; + use crate::trie::mem::arena::alloc::CHUNK_SIZE; + use crate::trie::mem::arena::metrics::MEM_TRIE_ARENA_MEMORY_USAGE_BYTES; + use crate::trie::mem::arena::{Arena, ArenaMemory, ArenaWithDealloc}; + + #[test] + fn test_concurrent_arena() { + let arena = ConcurrentArena::new(); + let mut thread1 = arena.for_thread(); + let mut thread2 = arena.for_thread(); + let mut thread3 = arena.for_thread(); + + let mut alloc1 = thread1.alloc(17); + let mut alloc2 = thread2.alloc(25); + let mut alloc3 = thread3.alloc(40); + alloc1.raw_slice_mut().copy_from_slice(&[1; 17]); + alloc2.raw_slice_mut().copy_from_slice(&[2; 25]); + alloc3.raw_slice_mut().copy_from_slice(&[3; 40]); + let ptr1 = alloc1.raw_pos(); + let ptr2 = alloc2.raw_pos(); + let ptr3 = alloc3.raw_pos(); + + let name = rand::random::().to_string(); + let mut starena = arena.to_single_threaded(name.clone(), vec![thread1, thread2, thread3]); + + assert_eq!(starena.num_active_allocs(), 3); + assert_eq!(starena.active_allocs_bytes(), 24 + 32 + 40); + assert_eq!( + MEM_TRIE_ARENA_MEMORY_USAGE_BYTES.get_metric_with_label_values(&[&name]).unwrap().get(), + 3 * CHUNK_SIZE as i64 + ); + + let mut alloc4 = starena.alloc(17); + alloc4.raw_slice_mut().copy_from_slice(&[4; 17]); + let ptr4 = alloc4.raw_pos(); + + assert_eq!(starena.memory().raw_slice(ptr1, 17), &[1; 17]); + assert_eq!(starena.memory().raw_slice(ptr2, 25), &[2; 25]); + assert_eq!(starena.memory().raw_slice(ptr3, 40), &[3; 40]); + assert_eq!(starena.memory().raw_slice(ptr4, 17), &[4; 17]); + + // Allocations from the concurrent arena can be deallocated and reused in the converted STArena. + // Allocations of the same size class are reusable. + starena.dealloc(ptr1, 17); + let mut alloc5 = starena.alloc(23); + assert_eq!(alloc5.raw_pos(), ptr1); + alloc5.raw_slice_mut().copy_from_slice(&[5; 23]); + starena.dealloc(ptr2, 25); + let mut alloc6 = starena.alloc(32); + assert_eq!(alloc6.raw_pos(), ptr2); + alloc6.raw_slice_mut().copy_from_slice(&[6; 32]); + starena.dealloc(ptr3, 40); + let mut alloc7 = starena.alloc(37); + assert_eq!(alloc7.raw_pos(), ptr3); + alloc7.raw_slice_mut().copy_from_slice(&[7; 37]); + starena.dealloc(ptr4, 17); + let mut alloc8 = starena.alloc(24); + assert_eq!(alloc8.raw_pos(), ptr4); + alloc8.raw_slice_mut().copy_from_slice(&[8; 24]); + + assert_eq!(starena.memory().raw_slice(ptr1, 23), &[5; 23]); + assert_eq!(starena.memory().raw_slice(ptr2, 32), &[6; 32]); + assert_eq!(starena.memory().raw_slice(ptr3, 37), &[7; 37]); + assert_eq!(starena.memory().raw_slice(ptr4, 24), &[8; 24]); + } +} diff --git a/core/store/src/trie/mem/arena/mod.rs b/core/store/src/trie/mem/arena/mod.rs index d7213c3c816..235a58a9e91 100644 --- a/core/store/src/trie/mem/arena/mod.rs +++ b/core/store/src/trie/mem/arena/mod.rs @@ -1,4 +1,5 @@ mod alloc; +pub mod concurrent; mod metrics; use self::alloc::Allocator; @@ -153,11 +154,34 @@ impl STArena { Self { memory: STArenaMemory::new(), allocator: Allocator::new(name) } } + pub(crate) fn new_from_existing_chunks( + name: String, + chunks: Vec>, + active_allocs_bytes: usize, + active_allocs_count: usize, + ) -> Self { + let arena = Self { + memory: STArenaMemory { chunks }, + allocator: Allocator::new_with_initial_stats( + name, + active_allocs_bytes, + active_allocs_count, + ), + }; + arena.allocator.update_memory_usage_gauge(&arena.memory); + arena + } + /// Number of active allocations (alloc calls minus dealloc calls). #[cfg(test)] pub fn num_active_allocs(&self) -> usize { self.allocator.num_active_allocs() } + + #[cfg(test)] + pub fn active_allocs_bytes(&self) -> usize { + self.allocator.active_allocs_bytes() + } } impl Arena for STArena { diff --git a/core/store/src/trie/mem/construction.rs b/core/store/src/trie/mem/construction.rs index 7e5af49c1b2..9975671783a 100644 --- a/core/store/src/trie/mem/construction.rs +++ b/core/store/src/trie/mem/construction.rs @@ -220,8 +220,7 @@ impl<'a, A: Arena> TrieConstructor<'a, A> { /// Adds a leaf to the trie. The key must be greater than all previous keys /// inserted. - pub fn add_leaf(&mut self, key: &[u8], value: FlatStateValue) { - let mut nibbles = NibbleSlice::new(key); + pub fn add_leaf(&mut self, mut nibbles: NibbleSlice, value: FlatStateValue) { let mut i = 0; // We'll go down the segments to find where our nibbles deviate. // If the deviation happens in the middle of a segment, we would split diff --git a/core/store/src/trie/mem/flexible_data/encoding.rs b/core/store/src/trie/mem/flexible_data/encoding.rs index 99164b6fd6d..a76f845e2c1 100644 --- a/core/store/src/trie/mem/flexible_data/encoding.rs +++ b/core/store/src/trie/mem/flexible_data/encoding.rs @@ -1,5 +1,5 @@ use super::FlexibleDataHeader; -use crate::trie::mem::arena::{Arena, ArenaMemory, ArenaPtr, ArenaPtrMut, ArenaSliceMut}; +use crate::trie::mem::arena::{Arena, ArenaMemory, ArenaPtr, ArenaSliceMut}; use borsh::{BorshDeserialize, BorshSerialize}; use std::io::Write; @@ -103,37 +103,3 @@ impl<'a, M: ArenaMemory> RawDecoder<'a, M> { view } } - -/// Provides ability to decode, but also to overwrite some data. -pub struct RawDecoderMut<'a, M: ArenaMemory> { - data: ArenaPtrMut<'a, M>, - pos: usize, -} - -impl<'a, M: ArenaMemory> RawDecoderMut<'a, M> { - pub fn new(data: ArenaPtrMut<'a, M>) -> Self { - RawDecoderMut { data, pos: 0 } - } - - /// Same with `RawDecoder::decode`. - pub fn decode(&mut self) -> T { - let slice = self.data.slice(self.pos, T::SERIALIZED_SIZE); - let result = T::try_from_slice(slice.raw_slice()).unwrap(); - self.pos += T::SERIALIZED_SIZE; - result - } - - /// Same with `RawDecoder::peek`. - pub fn peek(&mut self) -> T { - let slice = self.data.slice(self.pos, T::SERIALIZED_SIZE); - T::try_from_slice(slice.raw_slice()).unwrap() - } - - /// Overwrites the data at the current position with the given data, - /// and advances the position by the size of the data. - pub fn overwrite(&mut self, data: T) { - let mut slice = self.data.slice_mut(self.pos, T::SERIALIZED_SIZE); - data.serialize(&mut slice.raw_slice_mut()).unwrap(); - self.pos += T::SERIALIZED_SIZE; - } -} diff --git a/core/store/src/trie/mem/loading.rs b/core/store/src/trie/mem/loading.rs index 04fe952076c..ac32986a4dc 100644 --- a/core/store/src/trie/mem/loading.rs +++ b/core/store/src/trie/mem/loading.rs @@ -6,29 +6,55 @@ use crate::flat::store_helper::{ use crate::flat::{FlatStorageError, FlatStorageStatus}; use crate::trie::mem::arena::Arena; use crate::trie::mem::construction::TrieConstructor; +use crate::trie::mem::parallel_loader::load_memtrie_in_parallel; use crate::trie::mem::updating::apply_memtrie_changes; -use crate::{DBCol, Store}; +use crate::{DBCol, NibbleSlice, Store}; use near_primitives::errors::StorageError; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::{get_block_shard_uid, ShardUId}; use near_primitives::state::FlatStateValue; use near_primitives::types::chunk_extra::ChunkExtra; use near_primitives::types::{BlockHeight, StateRoot}; -use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use std::collections::BTreeSet; use std::time::Instant; use tracing::{debug, info}; /// Loads a trie from the FlatState column. The returned `MemTries` contains /// exactly one trie root. +/// +/// `parallelize` can be used to speed up reading from db. However, it should +/// only be used when no other work is being done, such as during initial +/// startup. It also incurs a higher peak memory usage. pub fn load_trie_from_flat_state( store: &Store, shard_uid: ShardUId, state_root: CryptoHash, block_height: BlockHeight, + parallelize: bool, ) -> Result { - let mut tries = MemTries::new(shard_uid); + if parallelize && state_root != CryptoHash::default() { + const NUM_PARALLEL_SUBTREES_DESIRED: usize = 256; + let load_start = Instant::now(); + let (arena, root_id) = load_memtrie_in_parallel( + store.clone(), + shard_uid, + state_root, + NUM_PARALLEL_SUBTREES_DESIRED, + shard_uid.to_string(), + )?; + info!(target: "memtrie", shard_uid=%shard_uid, "Done loading trie from flat state, took {:?}", load_start.elapsed()); + let root = root_id.as_ptr(arena.memory()); + assert_eq!( + root.view().node_hash(), + state_root, + "In-memory trie for shard {} has incorrect state root", + shard_uid + ); + return Ok(MemTries::new_from_arena_and_root(shard_uid, block_height, arena, root_id)); + } + + let mut tries = MemTries::new(shard_uid); tries.construct_root(block_height, |arena| -> Result, StorageError> { info!(target: "memtrie", shard_uid=%shard_uid, "Loading trie from flat state..."); let load_start = Instant::now(); @@ -44,7 +70,7 @@ pub fn load_trie_from_flat_state( FlatStorageError::StorageInternalError(format!( "invalid FlatState key format: {err}" ))})?; - recon.add_leaf(&key, value); + recon.add_leaf(NibbleSlice::new(&key), value); num_keys_loaded += 1; if num_keys_loaded % 1000000 == 0 { debug!( @@ -67,15 +93,9 @@ pub fn load_trie_from_flat_state( debug!( target: "memtrie", %shard_uid, - "Loaded {} keys; computing hash and memory usage...", + "Loaded {} keys in total", num_keys_loaded ); - let mut subtrees = Vec::new(); - root_id.as_ptr_mut(arena.memory_mut()).take_small_subtrees(1024 * 1024, &mut subtrees); - subtrees.into_par_iter().for_each(|mut subtree| { - subtree.compute_hash_recursively(); - }); - root_id.as_ptr_mut(arena.memory_mut()).compute_hash_recursively(); info!(target: "memtrie", shard_uid=%shard_uid, "Done loading trie from flat state, took {:?}", load_start.elapsed()); let root = root_id.as_ptr(arena.memory()); @@ -119,6 +139,7 @@ pub fn load_trie_from_flat_state_and_delta( store: &Store, shard_uid: ShardUId, state_root: Option, + parallelize: bool, ) -> Result { debug!(target: "memtrie", %shard_uid, "Loading base trie from flat state..."); let flat_head = match get_flat_storage_status(&store, shard_uid)? { @@ -137,7 +158,8 @@ pub fn load_trie_from_flat_state_and_delta( }; let mut mem_tries = - load_trie_from_flat_state(&store, shard_uid, state_root, flat_head.height).unwrap(); + load_trie_from_flat_state(&store, shard_uid, state_root, flat_head.height, parallelize) + .unwrap(); debug!(target: "memtrie", %shard_uid, "Loading flat state deltas..."); // We load the deltas in order of height, so that we always have the previous state root @@ -199,7 +221,7 @@ mod tests { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; - fn check(keys: Vec>) { + fn check_maybe_parallelize(keys: Vec>, parallelize: bool) { let shard_tries = TestTriesBuilder::new().with_flat_storage(true).build(); let shard_uid = ShardUId::single_shard(); let changes = keys.iter().map(|key| (key.to_vec(), Some(key.to_vec()))).collect::>(); @@ -214,9 +236,14 @@ mod tests { let state_root = test_populate_trie(&shard_tries, &Trie::EMPTY_ROOT, shard_uid, changes); eprintln!("Trie and flat storage populated"); - let in_memory_trie = - load_trie_from_flat_state(&shard_tries.get_store(), shard_uid, state_root, 123) - .unwrap(); + let in_memory_trie = load_trie_from_flat_state( + &shard_tries.get_store(), + shard_uid, + state_root, + 123, + parallelize, + ) + .unwrap(); eprintln!("In memory trie loaded"); if keys.is_empty() { @@ -230,7 +257,8 @@ mod tests { &CryptoHash::default(), false, )); - trie_update.set_trie_cache_mode(near_primitives::types::TrieCacheMode::CachingChunk); + let _mode_guard = trie_update + .with_trie_cache_mode(Some(near_primitives::types::TrieCacheMode::CachingChunk)); let trie = trie_update.trie(); let root = in_memory_trie.get_root(&state_root).unwrap(); @@ -247,7 +275,7 @@ mod tests { // Do another access with the trie to see how many nodes we're supposed to // have accessed. let temp_trie = shard_tries.get_trie_for_shard(shard_uid, state_root); - temp_trie.get_optimized_ref(key, crate::KeyLookupMode::Trie).unwrap(); + temp_trie.get_optimized_ref(key, KeyLookupMode::Trie).unwrap(); assert_eq!( temp_trie.get_trie_nodes_count().db_reads, nodes_accessed.len() as u64, @@ -281,6 +309,11 @@ mod tests { } } + fn check(keys: Vec>) { + check_maybe_parallelize(keys.clone(), false); + check_maybe_parallelize(keys, true); + } + fn nibbles(hex: &str) -> Vec { if hex == "_" { return vec![]; @@ -466,7 +499,7 @@ mod tests { // Load into memory. It should load the base flat state (block 0), plus all // four deltas. We'll check against the state roots at each block; they should // all exist in the loaded memtrie. - let mem_tries = load_trie_from_flat_state_and_delta(&store, shard_uid, None).unwrap(); + let mem_tries = load_trie_from_flat_state_and_delta(&store, shard_uid, None, true).unwrap(); assert_eq!( memtrie_lookup(mem_tries.get_root(&state_root_0).unwrap(), &test_key.to_vec(), None) diff --git a/core/store/src/trie/mem/mod.rs b/core/store/src/trie/mem/mod.rs index 16794de1284..73146e3a145 100644 --- a/core/store/src/trie/mem/mod.rs +++ b/core/store/src/trie/mem/mod.rs @@ -17,6 +17,7 @@ pub mod loading; pub mod lookup; pub mod metrics; pub mod node; +mod parallel_loader; pub mod updating; /// Check this, because in the code we conveniently assume usize is 8 bytes. @@ -56,6 +57,18 @@ impl MemTries { } } + pub fn new_from_arena_and_root( + shard_uid: ShardUId, + block_height: BlockHeight, + arena: STArena, + root: MemTrieNodeId, + ) -> Self { + let mut tries = + Self { arena, roots: HashMap::new(), heights: Default::default(), shard_uid }; + tries.insert_root(root.as_ptr(tries.arena.memory()).view().node_hash(), root, block_height); + tries + } + /// Inserts a new root into the trie. The given function should perform /// the entire construction of the new trie, possibly based on some existing /// trie nodes. This internally takes care of refcounting. @@ -232,7 +245,6 @@ mod tests { extension: &NibbleSlice::new(&[]).encoded(true), }, ); - root.as_ptr_mut(arena.memory_mut()).compute_hash_recursively(); Ok(Some(root)) }) .unwrap(); diff --git a/core/store/src/trie/mem/node/encoding.rs b/core/store/src/trie/mem/node/encoding.rs index 7d69c9b29cc..33ed9ffb2f2 100644 --- a/core/store/src/trie/mem/node/encoding.rs +++ b/core/store/src/trie/mem/node/encoding.rs @@ -5,10 +5,9 @@ use crate::trie::mem::flexible_data::encoding::{BorshFixedSize, RawDecoder, RawE use crate::trie::mem::flexible_data::extension::EncodedExtensionHeader; use crate::trie::mem::flexible_data::value::EncodedValueHeader; use crate::trie::mem::flexible_data::FlexibleDataHeader; -use crate::trie::TRIE_COSTS; use borsh::{BorshDeserialize, BorshSerialize}; use near_primitives::hash::CryptoHash; -use near_primitives::state::FlatStateValue; +use std::mem::size_of; use smallvec::SmallVec; @@ -37,8 +36,8 @@ pub(crate) struct NonLeafHeader { } impl NonLeafHeader { - pub(crate) fn new(memory_usage: u64, node_hash: Option) -> Self { - Self { hash: node_hash.unwrap_or_default(), memory_usage } + pub(crate) fn new(memory_usage: u64, node_hash: CryptoHash) -> Self { + Self { hash: node_hash, memory_usage } } } @@ -128,43 +127,14 @@ impl MemTrieNodeId { } _ => {} } - // Let's also compute the memory usage of the node. We only do this for - // non-leaf nodes, because for leaf node it is very easy to just - // compute it on demand, so there's no need to store it. - let memory_usage = match &node { - InputMemTrieNode::Leaf { .. } => 0, - InputMemTrieNode::Extension { extension, child } => { - TRIE_COSTS.node_cost - + extension.len() as u64 * TRIE_COSTS.byte_of_key - + child.as_ptr(arena.memory()).view().memory_usage() - } - InputMemTrieNode::Branch { children } => { - let mut memory_usage = TRIE_COSTS.node_cost; - for child in children.iter() { - if let Some(child) = child { - memory_usage += child.as_ptr(arena.memory()).view().memory_usage(); - } - } - memory_usage - } - InputMemTrieNode::BranchWithValue { children, value } => { - let value_len = match value { - FlatStateValue::Ref(value_ref) => value_ref.len(), - FlatStateValue::Inlined(value) => value.len(), - }; - let mut memory_usage = TRIE_COSTS.node_cost - + value_len as u64 * TRIE_COSTS.byte_of_value - + TRIE_COSTS.node_cost; - for child in children.iter() { - if let Some(child) = child { - memory_usage += child.as_ptr(arena.memory()).view().memory_usage(); - } - } - memory_usage - } + // Prepare the raw node, for memory usage and hash computation. + let raw_node_with_size = if matches!(&node, InputMemTrieNode::Leaf { .. }) { + None + } else { + Some(node.to_raw_trie_node_with_size_non_leaf(arena.memory())) }; - // Finally, encode the data. We're still leaving the hash empty; that - // will be computed later in parallel. + + // Finally, encode the data. let data = match node { InputMemTrieNode::Leaf { value, extension } => { let extension_header = EncodedExtensionHeader::from_input(extension); @@ -190,9 +160,13 @@ impl MemTrieNodeId { arena, ExtensionHeader::SERIALIZED_SIZE + extension_header.flexible_data_length(), ); + let raw_node_with_size = raw_node_with_size.unwrap(); data.encode(ExtensionHeader { common: CommonHeader { refcount: 0, kind: NodeKind::Extension }, - nonleaf: NonLeafHeader::new(memory_usage, node_hash), + nonleaf: NonLeafHeader::new( + raw_node_with_size.memory_usage, + node_hash.unwrap_or_else(|| raw_node_with_size.hash()), + ), child: child.pos, extension: extension_header, }); @@ -205,9 +179,13 @@ impl MemTrieNodeId { arena, BranchHeader::SERIALIZED_SIZE + children_header.flexible_data_length(), ); + let raw_node_with_size = raw_node_with_size.unwrap(); data.encode(BranchHeader { common: CommonHeader { refcount: 0, kind: NodeKind::Branch }, - nonleaf: NonLeafHeader::new(memory_usage, node_hash), + nonleaf: NonLeafHeader::new( + raw_node_with_size.memory_usage, + node_hash.unwrap_or_else(|| raw_node_with_size.hash()), + ), children: children_header, }); data.encode_flexible(&children_header, &children); @@ -222,9 +200,13 @@ impl MemTrieNodeId { + children_header.flexible_data_length() + value_header.flexible_data_length(), ); + let raw_node_with_size = raw_node_with_size.unwrap(); data.encode(BranchWithValueHeader { common: CommonHeader { refcount: 0, kind: NodeKind::BranchWithValue }, - nonleaf: NonLeafHeader::new(memory_usage, node_hash), + nonleaf: NonLeafHeader::new( + raw_node_with_size.memory_usage, + node_hash.unwrap_or_else(|| raw_node_with_size.hash()), + ), children: children_header, value: value_header, }); @@ -238,24 +220,22 @@ impl MemTrieNodeId { /// Increments the refcount, returning the new refcount. pub(crate) fn add_ref(&self, memory: &mut impl ArenaMemory) -> u32 { - let mut ptr = self.as_ptr_mut(memory); - let mut decoder = ptr.decoder_mut(); - let mut header = decoder.peek::(); - let new_refcount = header.refcount + 1; - header.refcount = new_refcount; - decoder.overwrite(header); + // Refcount is always encoded as the first four bytes of the node memory. + let refcount_memory = memory.raw_slice_mut(self.pos, size_of::()); + let refcount = u32::from_le_bytes(refcount_memory.try_into().unwrap()); + let new_refcount = refcount.checked_add(1).unwrap(); + refcount_memory.copy_from_slice(new_refcount.to_le_bytes().as_ref()); new_refcount } /// Decrements the refcount, deallocating the node if it reaches zero. /// Returns the new refcount. pub(crate) fn remove_ref(&self, arena: &mut impl ArenaWithDealloc) -> u32 { - let mut ptr = self.as_ptr_mut(arena.memory_mut()); - let mut decoder = ptr.decoder_mut(); - let mut header = decoder.peek::(); - let new_refcount = header.refcount - 1; - header.refcount = new_refcount; - decoder.overwrite(header); + // Refcount is always encoded as the first four bytes of the node memory. + let refcount_memory = arena.memory_mut().raw_slice_mut(self.pos, size_of::()); + let refcount = u32::from_le_bytes(refcount_memory.try_into().unwrap()); + let new_refcount = refcount.checked_sub(1).unwrap(); + refcount_memory.copy_from_slice(new_refcount.to_le_bytes().as_ref()); if new_refcount == 0 { let mut children_to_unref: SmallVec<[ArenaPos; 16]> = SmallVec::new(); let node_ptr = self.as_ptr(arena.memory()); diff --git a/core/store/src/trie/mem/node/mod.rs b/core/store/src/trie/mem/node/mod.rs index d23e2bdf98e..1690b67dacc 100644 --- a/core/store/src/trie/mem/node/mod.rs +++ b/core/store/src/trie/mem/node/mod.rs @@ -1,13 +1,14 @@ -use super::arena::{Arena, ArenaMemory, ArenaPos, ArenaPtr, ArenaPtrMut}; +use super::arena::{Arena, ArenaMemory, ArenaPos, ArenaPtr}; use super::flexible_data::children::ChildrenView; use super::flexible_data::value::ValueView; +use crate::trie::{Children, TRIE_COSTS}; +use crate::{RawTrieNode, RawTrieNodeWithSize}; use derive_where::derive_where; use near_primitives::hash::CryptoHash; use near_primitives::state::FlatStateValue; use std::fmt::{Debug, Formatter}; mod encoding; -mod mutation; #[cfg(test)] mod tests; mod view; @@ -42,13 +43,6 @@ impl MemTrieNodeId { pub fn as_ptr<'a, M: ArenaMemory>(&self, arena: &'a M) -> MemTrieNodePtr<'a, M> { MemTrieNodePtr { ptr: arena.ptr(self.pos) } } - - pub(crate) fn as_ptr_mut<'a, M: ArenaMemory>( - &self, - arena: &'a mut M, - ) -> MemTrieNodePtrMut<'a, M> { - MemTrieNodePtrMut { ptr: arena.ptr_mut(self.pos) } - } } /// This is for internal use only, so that we can put `MemTrieNodeId` in an @@ -66,13 +60,6 @@ pub struct MemTrieNodePtr<'a, M: ArenaMemory> { ptr: ArenaPtr<'a, M>, } -/// Pointer to an in-memory trie node that allows mutable access to the node -/// and all its descendants. This is only for computing hashes, and internal -/// reference counting. -pub struct MemTrieNodePtrMut<'a, M: ArenaMemory> { - ptr: ArenaPtrMut<'a, M>, -} - impl<'a, M: ArenaMemory> Debug for MemTrieNodePtr<'a, M> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.id().fmt(f) @@ -128,3 +115,60 @@ pub enum MemTrieNodeView<'a, M: ArenaMemory> { value: ValueView<'a>, }, } + +impl<'a> InputMemTrieNode<'a> { + /// Converts the input node into a `RawTrieNodeWithSize`; this is used to initialize + /// memory usage and to calculate hash when constructing the memtrie node. + /// + /// This must not be called if the node is a leaf. + pub fn to_raw_trie_node_with_size_non_leaf( + &self, + arena: &Memory, + ) -> RawTrieNodeWithSize { + match self { + Self::Leaf { .. } => { + unreachable!("Leaf nodes do not need hash computation") + } + Self::Extension { extension, child, .. } => { + let view = child.as_ptr(arena).view(); + let memory_usage = TRIE_COSTS.node_cost + + extension.len() as u64 * TRIE_COSTS.byte_of_key + + view.memory_usage(); + let node = RawTrieNode::Extension(extension.to_vec(), view.node_hash()); + RawTrieNodeWithSize { node, memory_usage } + } + Self::Branch { children, .. } => { + let mut memory_usage = TRIE_COSTS.node_cost; + let mut hashes = [None; 16]; + for (i, child) in children.iter().enumerate() { + if let Some(child) = child { + let view = child.as_ptr(arena).view(); + hashes[i] = Some(view.node_hash()); + memory_usage += view.memory_usage(); + } + } + let node = RawTrieNode::BranchNoValue(Children(hashes)); + RawTrieNodeWithSize { node, memory_usage } + } + Self::BranchWithValue { children, value, .. } => { + let value_len = match value { + FlatStateValue::Ref(value_ref) => value_ref.len(), + FlatStateValue::Inlined(value) => value.len(), + }; + let mut memory_usage = TRIE_COSTS.node_cost + + value_len as u64 * TRIE_COSTS.byte_of_value + + TRIE_COSTS.node_cost; + let mut hashes = [None; 16]; + for (i, child) in children.iter().enumerate() { + if let Some(child) = child { + let view = child.as_ptr(arena).view(); + hashes[i] = Some(view.node_hash()); + memory_usage += view.memory_usage(); + } + } + let node = RawTrieNode::BranchWithValue(value.to_value_ref(), Children(hashes)); + RawTrieNodeWithSize { node, memory_usage } + } + } + } +} diff --git a/core/store/src/trie/mem/node/mutation.rs b/core/store/src/trie/mem/node/mutation.rs deleted file mode 100644 index f0c9543aa49..00000000000 --- a/core/store/src/trie/mem/node/mutation.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::encoding::{CommonHeader, NodeKind, NonLeafHeader}; -use super::{MemTrieNodePtr, MemTrieNodePtrMut}; -use crate::trie::mem::arena::ArenaMemory; -use crate::trie::mem::flexible_data::encoding::RawDecoderMut; -use near_primitives::hash::{hash, CryptoHash}; - -impl<'a, M: ArenaMemory> MemTrieNodePtrMut<'a, M> { - fn as_const<'b>(&'b self) -> MemTrieNodePtr<'b, M> { - MemTrieNodePtr { ptr: self.ptr.ptr() } - } - - pub(crate) fn decoder_mut(&mut self) -> RawDecoderMut { - RawDecoderMut::new(self.ptr.ptr_mut()) - } - - /// Obtains a list of mutable references to the children of this node, - /// destroying this mutable reference. - /// - /// Despite being implemented with unsafe code, this is a safe operation - /// because the children subtrees are disjoint (even if there are multiple - /// roots). It is very similar to `split_at_mut` on mutable slices. - fn split_children_mut(mut self) -> Vec> { - let arena_mut = self.ptr.arena_mut() as *mut M; - let mut result = Vec::new(); - let view = self.as_const().view(); - for child in view.iter_children() { - let child_id = child.id(); - let arena_mut_ref = unsafe { &mut *arena_mut }; - result.push(child_id.as_ptr_mut(arena_mut_ref)); - } - result - } - - /// Like `split_children_mut`, but does not destroy the reference itself. - /// This is possible because of the returned references can only be used - /// while this reference is being mutably held, but it does result in a - /// different lifetime. - fn children_mut<'b>(&'b mut self) -> Vec> { - let arena_mut = self.ptr.arena_mut() as *mut M; - let mut result = Vec::new(); - let view = self.as_const().view(); - for child in view.iter_children() { - let child_id = child.id(); - let arena_mut_ref = unsafe { &mut *arena_mut }; - result.push(child_id.as_ptr_mut(arena_mut_ref)); - } - result - } - - /// Computes the hash for this node, assuming children nodes already have - /// computed hashes. - fn compute_hash(&mut self) { - let raw_trie_node_with_size = self.as_const().view().to_raw_trie_node_with_size(); - let mut decoder = self.decoder_mut(); - match decoder.decode::().kind { - NodeKind::Leaf => {} - _ => { - let mut nonleaf = decoder.peek::(); - nonleaf.hash = hash(&borsh::to_vec(&raw_trie_node_with_size).unwrap()); - decoder.overwrite(nonleaf); - } - } - } - - /// Whether the hash is computed for this node. - fn is_hash_computed(&self) -> bool { - let mut decoder = self.as_const().decoder(); - match decoder.decode::().kind { - NodeKind::Leaf => true, - _ => decoder.peek::().hash != CryptoHash::default(), - } - } - - /// Computes the hashes of this subtree recursively, stopping at any nodes - /// whose hashes are already computed. - pub(crate) fn compute_hash_recursively(&mut self) { - if self.is_hash_computed() { - return; - } - for mut child in self.children_mut() { - child.compute_hash_recursively(); - } - self.compute_hash(); - } - - /// Recursively expand the current subtree until we arrive at subtrees - /// that are small enough (by memory usage); we store these subtrees in - /// the provided vector. The returned subtrees cover all leaves but are - /// disjoint. - pub(crate) fn take_small_subtrees( - self, - threshold_memory_usage: u64, - trees: &mut Vec>, - ) { - if self.as_const().view().memory_usage() < threshold_memory_usage { - trees.push(self); - } else { - for child in self.split_children_mut() { - child.take_small_subtrees(threshold_memory_usage, trees); - } - } - } -} diff --git a/core/store/src/trie/mem/node/tests.rs b/core/store/src/trie/mem/node/tests.rs index 200e21b7cfb..5dd61d3faed 100644 --- a/core/store/src/trie/mem/node/tests.rs +++ b/core/store/src/trie/mem/node/tests.rs @@ -110,7 +110,6 @@ fn test_basic_extension_node() { &mut arena, InputMemTrieNode::Extension { extension: &[5, 6, 7, 8, 9], child }, ); - node.as_ptr_mut(arena.memory_mut()).compute_hash_recursively(); let child_ptr = child.as_ptr(arena.memory()); let node_ptr = node.as_ptr(arena.memory()); assert_eq!( @@ -159,7 +158,6 @@ fn test_basic_branch_node() { &mut arena, InputMemTrieNode::Branch { children: branch_array(vec![(3, child1), (5, child2)]) }, ); - node.as_ptr_mut(arena.memory_mut()).compute_hash_recursively(); let child1_ptr = child1.as_ptr(arena.memory()); let child2_ptr = child2.as_ptr(arena.memory()); let node_ptr = node.as_ptr(arena.memory()); @@ -227,8 +225,6 @@ fn test_basic_branch_with_value_node() { }, ); - node.as_ptr_mut(arena.memory_mut()).compute_hash_recursively(); - let child1_ptr = child1.as_ptr(arena.memory()); let child2_ptr = child2.as_ptr(arena.memory()); let node_ptr = node.as_ptr(arena.memory()); diff --git a/core/store/src/trie/mem/parallel_loader.rs b/core/store/src/trie/mem/parallel_loader.rs new file mode 100644 index 00000000000..8917a35d2ad --- /dev/null +++ b/core/store/src/trie/mem/parallel_loader.rs @@ -0,0 +1,463 @@ +use super::arena::concurrent::{ConcurrentArena, ConcurrentArenaForThread}; +use super::arena::{Arena, STArena}; +use super::construction::TrieConstructor; +use super::node::{InputMemTrieNode, MemTrieNodeId}; +use crate::flat::FlatStorageError; +use crate::trie::Children; +use crate::{DBCol, NibbleSlice, RawTrieNode, RawTrieNodeWithSize, Store}; +use borsh::BorshDeserialize; +use near_primitives::errors::{MissingTrieValueContext, StorageError}; +use near_primitives::hash::CryptoHash; +use near_primitives::shard_layout::ShardUId; +use near_primitives::state::FlatStateValue; +use near_primitives::types::StateRoot; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; +use std::fmt::Debug; +use std::sync::Mutex; + +/// Top-level entry function to load a memtrie in parallel. +pub fn load_memtrie_in_parallel( + store: Store, + shard_uid: ShardUId, + root: StateRoot, + num_subtrees_desired: usize, + name: String, +) -> Result<(STArena, MemTrieNodeId), StorageError> { + let reader = ParallelMemTrieLoader::new(store, shard_uid, root, num_subtrees_desired); + let plan = reader.make_loading_plan()?; + tracing::info!("Loading {} subtrees in parallel", plan.subtrees_to_load.len()); + reader.load_in_parallel(plan, name) +} + +/// Logic to load a memtrie in parallel. It consists of three stages: +/// - First, we use the State column to visit the trie starting from the root. We recursively +/// expand the trie until all the unexpanded subtrees are small enough +/// (memory_usage <= `subtree_size`). The trie we have expanded is represented as a "plan", +/// which is a structure similar to the trie itself. +/// - Then, we load each small subtree (the keys under which all share a common prefix) in +/// parallel, by reading the FlatState column for keys that correspond to the prefix of that +/// subtree. The result of each construction is a `MemTrieNodeId` representing the root of that +/// subtree. +/// - Finally, We construct the final trie by using the loaded subtree roots and converting the +/// plan into a complete memtrie, returning the final root. +/// +/// This loader is only suitable for loading a single trie. It does not load multiple state roots, +/// or multiple shards. +pub struct ParallelMemTrieLoader { + store: Store, + shard_uid: ShardUId, + root: StateRoot, + num_subtrees_desired: usize, +} + +impl ParallelMemTrieLoader { + pub fn new( + store: Store, + shard_uid: ShardUId, + root: StateRoot, + num_subtrees_desired: usize, + ) -> Self { + Self { store, shard_uid, root, num_subtrees_desired } + } + + /// Implements stage 1; recursively expanding the trie until all subtrees are small enough. + fn make_loading_plan(&self) -> Result { + let subtrees_to_load = Mutex::new(Vec::new()); + let root = self.make_loading_plan_recursive( + self.root, + NibblePrefix::new(), + &subtrees_to_load, + None, + )?; + Ok(PartialTrieLoadingPlan { + root, + subtrees_to_load: subtrees_to_load.into_inner().unwrap(), + }) + } + + /// Helper function to implement stage 1, visiting a single node identified by this hash, + /// whose prefix is the given prefix. While expanding this node, any small subtrees + /// encountered are appended to the `subtrees_to_load` array. + fn make_loading_plan_recursive( + &self, + hash: CryptoHash, + mut prefix: NibblePrefix, + subtrees_to_load: &Mutex>, + max_subtree_size: Option, + ) -> Result { + // Read the node from the State column. + let mut key = [0u8; 40]; + key[0..8].copy_from_slice(&self.shard_uid.to_bytes()); + key[8..40].copy_from_slice(&hash.0); + let node = RawTrieNodeWithSize::try_from_slice( + &self + .store + .get(DBCol::State, &key) + .map_err(|e| StorageError::StorageInconsistentState(e.to_string()))? + .ok_or(StorageError::MissingTrieValue(MissingTrieValueContext::TrieStorage, hash))? + .as_slice(), + ) + .map_err(|e| StorageError::StorageInconsistentState(e.to_string()))?; + + let max_subtree_size = max_subtree_size + .unwrap_or_else(|| node.memory_usage / self.num_subtrees_desired as u64); + + // If subtree is small enough, add it to the list of subtrees to load, and we're done. + if node.memory_usage <= max_subtree_size { + let mut lock = subtrees_to_load.lock().unwrap(); + let subtree_id = lock.len(); + lock.push(prefix); + return Ok(TrieLoadingPlanNode::Load { subtree_id }); + } + + match node.node { + RawTrieNode::Leaf(extension, value_ref) => { + // If we happen to visit a leaf, we'll have to just read the leaf's value. This is + // almost like a corner case because we're not really interested in values here + // (that's the job of the parallel loading part), but if we do get here, we have to + // deal with it. + key[8..40].copy_from_slice(&value_ref.hash.0); + let value = self + .store + .get(DBCol::State, &key) + .map_err(|e| StorageError::StorageInconsistentState(e.to_string()))? + .ok_or(StorageError::MissingTrieValue( + MissingTrieValueContext::TrieStorage, + hash, + ))?; + let flat_value = FlatStateValue::on_disk(&value); + Ok(TrieLoadingPlanNode::Leaf { + extension: extension.into_boxed_slice(), + value: flat_value, + }) + } + RawTrieNode::BranchNoValue(children_hashes) => { + // If we visit a branch, recursively visit all children. + let children = self.make_children_plans_in_parallel( + children_hashes, + &prefix, + subtrees_to_load, + max_subtree_size, + )?; + + Ok(TrieLoadingPlanNode::Branch { children, value: None }) + } + RawTrieNode::BranchWithValue(value_ref, children_hashes) => { + // Similar here, except we have to also look up the value. + key[8..40].copy_from_slice(&value_ref.hash.0); + let value = self + .store + .get(DBCol::State, &key) + .map_err(|e| StorageError::StorageInconsistentState(e.to_string()))? + .ok_or(StorageError::MissingTrieValue( + MissingTrieValueContext::TrieStorage, + hash, + ))?; + let flat_value = FlatStateValue::on_disk(&value); + + let children = self.make_children_plans_in_parallel( + children_hashes, + &prefix, + subtrees_to_load, + max_subtree_size, + )?; + + Ok(TrieLoadingPlanNode::Branch { children, value: Some(flat_value) }) + } + RawTrieNode::Extension(extension, child) => { + let nibbles = NibbleSlice::from_encoded(&extension).0; + prefix.append(&nibbles); + let child = self.make_loading_plan_recursive( + child, + prefix, + subtrees_to_load, + Some(max_subtree_size), + )?; + Ok(TrieLoadingPlanNode::Extension { + extension: extension.into_boxed_slice(), + child: Box::new(child), + }) + } + } + } + + fn make_children_plans_in_parallel( + &self, + children_hashes: Children, + prefix: &NibblePrefix, + subtrees_to_load: &Mutex>, + max_subtree_size: u64, + ) -> Result)>, StorageError> { + let existing_children = children_hashes.iter().collect::>(); + let children = existing_children + .into_par_iter() + .map(|(i, child_hash)| -> Result<_, StorageError> { + let mut prefix = prefix.clone(); + prefix.push(i as u8); + let node = self.make_loading_plan_recursive( + *child_hash, + prefix, + subtrees_to_load, + Some(max_subtree_size), + )?; + Ok((i, Box::new(node))) + }) + .collect::, _>>()?; + Ok(children) + } + + /// This implements the loading of each subtree in stage 2. + fn load_one_subtree( + &self, + subtree_to_load: &NibblePrefix, + arena: &mut impl Arena, + ) -> Result { + // Figure out which range corresponds to the prefix of this subtree. + let (start, end) = subtree_to_load.to_iter_range(self.shard_uid); + + // Load all the keys in this range from the FlatState column. + let mut recon = TrieConstructor::new(arena); + for item in self.store.iter_range(DBCol::FlatState, Some(&start), Some(&end)) { + let (key, value) = item.map_err(|err| { + FlatStorageError::StorageInternalError(format!( + "Error iterating over FlatState: {err}" + )) + })?; + let key = NibbleSlice::new(&key[8..]).mid(subtree_to_load.num_nibbles()); + let value = FlatStateValue::try_from_slice(&value).map_err(|err| { + FlatStorageError::StorageInternalError(format!( + "invalid FlatState value format: {err}" + )) + })?; + recon.add_leaf(key, value); + } + Ok(recon.finalize().unwrap()) + } + + /// This implements stage 2 and 3, loading the subtrees in parallel an then constructing the + /// final trie. + fn load_in_parallel( + &self, + plan: PartialTrieLoadingPlan, + name: String, + ) -> Result<(STArena, MemTrieNodeId), StorageError> { + let arena = ConcurrentArena::new(); + + // A bit of an awkward Rayon dance. We run a multi-threaded fold; the fold state contains + // both a sparse vector of the loading results as well as the arena used for the thread. + // We need to collect both in the end, so fold is the only suitable method. + let (roots, threads): ( + Vec>>, + Vec, + ) = plan + .subtrees_to_load + .into_par_iter() + .enumerate() + .fold(|| -> (Vec>, ConcurrentArenaForThread) { + (Vec::new(), arena.for_thread()) + }, |(mut roots, mut arena), (i, prefix)| { + roots.push(self.load_one_subtree(&prefix, &mut arena).map(|root| (i, root))); + (roots, arena) + }) + .unzip(); + + let mut roots = roots.into_iter().flatten().collect::, _>>()?; + roots.sort_by_key(|(i, _)| *i); + let roots = roots.into_iter().map(|(_, root)| root).collect::>(); + + let mut arena = arena.to_single_threaded(name, threads); + let root = plan.root.to_node(&mut arena, &roots); + Ok((arena, root)) + } +} + +/// Specifies exactly what to do to create a node in the final trie. +#[derive(Debug)] +enum TrieLoadingPlanNode { + // The first three cases correspond exactly to the trie structure. + Branch { children: Vec<(u8, Box)>, value: Option }, + Extension { extension: Box<[u8]>, child: Box }, + Leaf { extension: Box<[u8]>, value: FlatStateValue }, + // This means this trie node is whatever loading this subtree yields. + Load { subtree_id: usize }, +} + +impl TrieLoadingPlanNode { + /// This implements the construction part of stage 3, where we convert a plan node to + /// a memtrie node. The `subtree_roots` is the parallel loading results. + fn to_node(self, arena: &mut impl Arena, subtree_roots: &[MemTrieNodeId]) -> MemTrieNodeId { + match self { + TrieLoadingPlanNode::Branch { children, value } => { + let mut res_children = [None; 16]; + for (nibble, child) in children { + res_children[nibble as usize] = Some(child.to_node(arena, subtree_roots)); + } + let input = match &value { + Some(value) => { + InputMemTrieNode::BranchWithValue { children: res_children, value } + } + None => InputMemTrieNode::Branch { children: res_children }, + }; + MemTrieNodeId::new(arena, input) + } + TrieLoadingPlanNode::Extension { extension, child } => { + let child = child.to_node(arena, subtree_roots); + let input = InputMemTrieNode::Extension { extension: &extension, child }; + MemTrieNodeId::new(arena, input) + } + TrieLoadingPlanNode::Leaf { extension, value } => { + let input = InputMemTrieNode::Leaf { extension: &extension, value: &value }; + MemTrieNodeId::new(arena, input) + } + TrieLoadingPlanNode::Load { subtree_id } => subtree_roots[subtree_id], + } + } +} + +#[derive(Debug)] +struct PartialTrieLoadingPlan { + root: TrieLoadingPlanNode, + subtrees_to_load: Vec, +} + +/// Represents a prefix of nibbles. Allows appending to the prefix, and implements logic of +/// calculating a range of keys that correspond to this prefix. +/// +/// A nibble just means a 4 bit number. +#[derive(Clone)] +struct NibblePrefix { + /// Big endian encoding of the nibbles. If there are an odd number of nibbles, this is + /// the encoding of the nibbles as if there were one more nibble at the end being zero. + prefix: Vec, + /// Whether the last byte of `prefix` represents one nibble rather than two. + odd: bool, +} + +impl Debug for NibblePrefix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.odd { + write!( + f, + "{}{:x}", + hex::encode(&self.prefix[..self.prefix.len() - 1]), + self.prefix.last().unwrap() >> 4 + ) + } else { + write!(f, "{}", hex::encode(&self.prefix)) + } + } +} + +impl NibblePrefix { + pub fn new() -> Self { + Self { prefix: Vec::new(), odd: false } + } + + pub fn num_nibbles(&self) -> usize { + self.prefix.len() * 2 - if self.odd { 1 } else { 0 } + } + + pub fn push(&mut self, nibble: u8) { + debug_assert!(nibble < 16, "nibble must be less than 16"); + if self.odd { + *self.prefix.last_mut().unwrap() |= nibble; + } else { + self.prefix.push(nibble << 4); + } + self.odd = !self.odd; + } + + pub fn append(&mut self, nibbles: &NibbleSlice) { + for nibble in nibbles.iter() { + self.push(nibble); + } + } + + /// Converts the nibble prefix to an equivalent range of FlatState keys. + /// + /// If the number of nibbles is even, this is straight-forward; the keys will be in the form of + /// e.g. 0x123456 - 0x123457. If the number of nibbles is odd, the keys will cover the whole + /// range for the last 4 bits, e.g. 0x123450 - 0x123460. + pub fn to_iter_range(&self, shard_uid: ShardUId) -> (Vec, Vec) { + let start = shard_uid + .to_bytes() + .into_iter() + .chain(self.prefix.clone().into_iter()) + .collect::>(); + // The end key should always exist because we have a shard UID prefix to absorb the overflow. + let end = + calculate_end_key(&start, if self.odd { 16 } else { 1 }).expect("Should not overflow"); + (start, end) + } +} + +/// Calculates the end key of a lexically ordered key range where all the keys start with `start_key` +/// except that the i-th byte may be within [b, b + last_byte_increment), where i == start_key.len() - 1, +/// and b == start_key[i]. Returns None is the end key is unbounded. +fn calculate_end_key(start_key: &Vec, last_byte_increment: u8) -> Option> { + let mut v = start_key.clone(); + let mut carry = last_byte_increment; + for i in (0..v.len()).rev() { + let (new_val, overflowing) = v[i].overflowing_add(carry); + if overflowing { + carry = 1; + v.pop(); + } else { + v[i] = new_val; + return Some(v); + } + } + return None; +} + +#[cfg(test)] +mod tests { + use super::NibblePrefix; + use crate::trie::mem::parallel_loader::calculate_end_key; + use crate::NibbleSlice; + use near_primitives::shard_layout::ShardUId; + + #[test] + fn test_increment_vec_as_num() { + assert_eq!(calculate_end_key(&vec![0, 0, 0], 1), Some(vec![0, 0, 1])); + assert_eq!(calculate_end_key(&vec![0, 0, 255], 1), Some(vec![0, 1])); + assert_eq!(calculate_end_key(&vec![0, 5, 255], 1), Some(vec![0, 6])); + assert_eq!(calculate_end_key(&vec![0, 255, 255], 1), Some(vec![1])); + assert_eq!(calculate_end_key(&vec![255, 255, 254], 2), None); + } + + #[test] + fn test_nibble_prefix() { + let shard_uid = ShardUId { shard_id: 3, version: 2 }; + let iter_range = |prefix: &NibblePrefix| { + let (start, end) = prefix.to_iter_range(shard_uid); + format!("{}..{}", hex::encode(&start), hex::encode(&end)) + }; + + let mut prefix = NibblePrefix::new(); + assert_eq!(format!("{:?}", prefix), ""); + assert_eq!(iter_range(&prefix), "0200000003000000..0200000003000001"); + + prefix.push(4); + assert_eq!(format!("{:?}", prefix), "4"); + assert_eq!(iter_range(&prefix), "020000000300000040..020000000300000050"); + + prefix.push(15); + assert_eq!(format!("{:?}", prefix), "4f"); + assert_eq!(iter_range(&prefix), "02000000030000004f..020000000300000050"); + + prefix.append(&NibbleSlice::new(&hex::decode("5123").unwrap()).mid(1)); + assert_eq!(format!("{:?}", prefix), "4f123"); + assert_eq!(iter_range(&prefix), "02000000030000004f1230..02000000030000004f1240"); + + prefix.append(&NibbleSlice::new(&hex::decode("ff").unwrap())); + assert_eq!(format!("{:?}", prefix), "4f123ff"); + assert_eq!(iter_range(&prefix), "02000000030000004f123ff0..02000000030000004f1240"); + + let mut prefix = NibblePrefix::new(); + prefix.push(15); + prefix.push(15); + assert_eq!(format!("{:?}", prefix), "ff"); + assert_eq!(iter_range(&prefix), "0200000003000000ff..0200000003000001"); + } +} diff --git a/core/store/src/trie/mod.rs b/core/store/src/trie/mod.rs index c2edea9a262..ae18f47c8cd 100644 --- a/core/store/src/trie/mod.rs +++ b/core/store/src/trie/mod.rs @@ -38,7 +38,6 @@ use std::cell::RefCell; use std::collections::{BTreeMap, HashSet}; use std::fmt::Write; use std::hash::Hash; -use std::rc::Rc; use std::str; use std::sync::{Arc, RwLock, RwLockReadGuard}; @@ -336,7 +335,7 @@ impl std::fmt::Debug for TrieNode { } pub struct Trie { - storage: Rc, + storage: Arc, memtries: Option>>, root: StateRoot, /// If present, flat storage is used to look up keys (if asked for). @@ -629,7 +628,7 @@ impl Trie { /// By default, the accounting cache is not enabled. To enable or disable it /// (only in this crate), call self.accounting_cache.borrow_mut().set_enabled(). pub fn new( - storage: Rc, + storage: Arc, root: StateRoot, flat_storage_chunk_view: Option, ) -> Self { @@ -637,7 +636,7 @@ impl Trie { } pub fn new_with_memtries( - storage: Rc, + storage: Arc, memtries: Option>>, root: StateRoot, flat_storage_chunk_view: Option, @@ -711,7 +710,7 @@ impl Trie { ) -> Self { let PartialState::TrieValues(nodes) = partial_storage.nodes; let recorded_storage = nodes.into_iter().map(|value| (hash(&value), value)).collect(); - let storage = Rc::new(TrieMemoryPartialStorage::new(recorded_storage)); + let storage = Arc::new(TrieMemoryPartialStorage::new(recorded_storage)); let mut trie = Self::new(storage, root, None); trie.charge_gas_for_trie_node_access = !flat_storage_used; trie @@ -752,6 +751,24 @@ impl Trie { } } + #[cfg(feature = "test_features")] + pub fn record_storage_garbage(&self, size_mbs: usize) -> bool { + let Some(recorder) = &self.recorder else { + return false; + }; + let mut data = vec![0u8; (size_mbs as usize) * 1000_000]; + rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut data); + // We want to have at most 1 instance of garbage data included per chunk so + // that it is possible to generated continuous stream of witnesses with a fixed + // size. Using static key achieves that since in case of multiple receipts garbage + // data will simply be overwritten, not accumulated. + recorder.borrow_mut().record_unaccounted( + &CryptoHash::hash_bytes(b"__garbage_data_key_1720025071757228"), + data.into(), + ); + true + } + /// All access to trie nodes or values must go through this method, so it /// can be properly cached and recorded. /// diff --git a/core/store/src/trie/prefetching_trie_storage.rs b/core/store/src/trie/prefetching_trie_storage.rs index 8cb4b53264c..e6a60924550 100644 --- a/core/store/src/trie/prefetching_trie_storage.rs +++ b/core/store/src/trie/prefetching_trie_storage.rs @@ -13,7 +13,6 @@ use near_primitives::shard_layout::ShardUId; use near_primitives::trie_key::TrieKey; use near_primitives::types::{AccountId, ShardId, StateRoot}; use std::collections::HashMap; -use std::rc::Rc; use std::sync::Arc; use std::thread; @@ -446,8 +445,8 @@ impl PrefetchApi { }) } - pub fn make_storage(&self) -> Rc { - Rc::new(TriePrefetchingStorage::new( + pub fn make_storage(&self) -> Arc { + Arc::new(TriePrefetchingStorage::new( self.store.clone(), self.shard_uid, self.shard_cache.clone(), @@ -488,7 +487,7 @@ impl PrefetchApi { // the clone only clones a few `Arc`s, so the performance // hit is small. let prefetcher_trie = - Trie::new(Rc::new(prefetcher_storage.clone()), trie_root, None); + Trie::new(Arc::new(prefetcher_storage.clone()), trie_root, None); let storage_key = trie_key.to_vec(); metric_prefetch_sent.inc(); match prefetcher_trie.get(&storage_key) { diff --git a/core/store/src/trie/raw_node.rs b/core/store/src/trie/raw_node.rs index 427e25fc3d7..5c1d117cde2 100644 --- a/core/store/src/trie/raw_node.rs +++ b/core/store/src/trie/raw_node.rs @@ -12,6 +12,12 @@ pub struct RawTrieNodeWithSize { pub(super) memory_usage: u64, } +impl RawTrieNodeWithSize { + pub fn hash(&self) -> CryptoHash { + CryptoHash::hash_bytes(&borsh::to_vec(self).unwrap()) + } +} + /// Trie node. #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq)] #[allow(clippy::large_enum_variant)] diff --git a/core/store/src/trie/shard_tries.rs b/core/store/src/trie/shard_tries.rs index 39e87a5261d..87fe8af4678 100644 --- a/core/store/src/trie/shard_tries.rs +++ b/core/store/src/trie/shard_tries.rs @@ -20,7 +20,6 @@ use near_primitives::types::{ }; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use std::collections::HashMap; -use std::rc::Rc; use std::sync::{Arc, Mutex, RwLock}; use tracing::info; @@ -139,7 +138,7 @@ impl ShardTries { .clone() }); - let storage = Rc::new(TrieCachingStorage::new( + let storage = Arc::new(TrieCachingStorage::new( self.0.store.clone(), cache, shard_uid, @@ -148,12 +147,10 @@ impl ShardTries { )); let flat_storage_chunk_view = block_hash .and_then(|block_hash| self.0.flat_storage_manager.chunk_view(shard_uid, block_hash)); - Trie::new_with_memtries( - storage, - self.get_mem_tries(shard_uid), - state_root, - flat_storage_chunk_view, - ) + // Do not use memtries for view queries, for two reasons: memtries do not provide historical state, + // and also this can introduce lock contention on memtries. + let memtries = if is_view { None } else { self.get_mem_tries(shard_uid) }; + Trie::new_with_memtries(storage, memtries, state_root, flat_storage_chunk_view) } pub fn get_trie_for_shard(&self, shard_uid: ShardUId, state_root: StateRoot) -> Trie { @@ -168,7 +165,7 @@ impl ShardTries { ) -> Result { let (store, flat_storage_manager) = self.get_state_snapshot(block_hash)?; let cache = self.get_trie_cache_for(shard_uid, true); - let storage = Rc::new(TrieCachingStorage::new(store, cache, shard_uid, true, None)); + let storage = Arc::new(TrieCachingStorage::new(store, cache, shard_uid, true, None)); let flat_storage_chunk_view = flat_storage_manager.chunk_view(shard_uid, *block_hash); Ok(Trie::new(storage, state_root, flat_storage_chunk_view)) @@ -417,9 +414,15 @@ impl ShardTries { &self, shard_uid: &ShardUId, state_root: Option, + parallelize: bool, ) -> Result<(), StorageError> { info!(target: "memtrie", "Loading trie to memory for shard {:?}...", shard_uid); - let mem_tries = load_trie_from_flat_state_and_delta(&self.0.store, *shard_uid, state_root)?; + let mem_tries = load_trie_from_flat_state_and_delta( + &self.0.store, + *shard_uid, + state_root, + parallelize, + )?; self.0.mem_tries.write().unwrap().insert(*shard_uid, Arc::new(RwLock::new(mem_tries))); info!(target: "memtrie", "Memtrie loading complete for shard {:?}", shard_uid); Ok(()) @@ -438,7 +441,7 @@ impl ShardTries { // It should not happen that memtrie is already loaded for a shard // for which we just did state sync. debug_assert!(!self.0.mem_tries.read().unwrap().contains_key(shard_uid)); - self.load_mem_trie(shard_uid, Some(*state_root)) + self.load_mem_trie(shard_uid, Some(*state_root), false) } /// Loads in-memory tries upon startup. The given shard_uids are possible candidates to load, @@ -447,6 +450,7 @@ impl ShardTries { pub fn load_mem_tries_for_enabled_shards( &self, tracked_shards: &[ShardUId], + parallelize: bool, ) -> Result<(), StorageError> { let trie_config = &self.0.trie_config; let shard_uids_to_load = tracked_shards @@ -461,7 +465,7 @@ impl ShardTries { info!(target: "memtrie", "Loading tries to memory for shards {:?}...", shard_uids_to_load); shard_uids_to_load .par_iter() - .map(|shard_uid| self.load_mem_trie(shard_uid, None)) + .map(|shard_uid| self.load_mem_trie(shard_uid, None, parallelize)) .collect::>>() .into_iter() .collect::>()?; diff --git a/core/store/src/trie/state_parts.rs b/core/store/src/trie/state_parts.rs index 00f0ba76888..7a0ee49ad20 100644 --- a/core/store/src/trie/state_parts.rs +++ b/core/store/src/trie/state_parts.rs @@ -33,7 +33,6 @@ use near_primitives::state_record::is_contract_code_key; use near_primitives::types::{ShardId, StateRoot}; use near_vm_runner::ContractCode; use std::collections::{HashMap, HashSet}; -use std::rc::Rc; use std::sync::Arc; use super::TrieRefcountDeltaMap; @@ -243,7 +242,7 @@ impl Trie { .with_label_values(&[&shard_id.to_string()]) .start_timer(); let local_state_part_trie = - Trie::new(Rc::new(TrieMemoryPartialStorage::default()), StateRoot::new(), None); + Trie::new(Arc::new(TrieMemoryPartialStorage::default()), StateRoot::new(), None); let local_state_part_nodes = local_state_part_trie.update(all_state_part_items.into_iter())?.insertions; let local_trie_creation_duration = local_trie_creation_timer.stop_and_record(); @@ -264,7 +263,7 @@ impl Trie { .map(|entry| (*entry.hash(), entry.payload().to_vec().into())), ); let final_trie = - Trie::new(Rc::new(TrieMemoryPartialStorage::new(all_nodes)), self.root, None); + Trie::new(Arc::new(TrieMemoryPartialStorage::new(all_nodes)), self.root, None); final_trie.visit_nodes_for_state_part(part_id)?; let final_trie_storage = final_trie.storage.as_partial_storage().unwrap(); @@ -434,7 +433,7 @@ impl Trie { trie.visit_nodes_for_state_part(part_id)?; let storage = trie.storage.as_partial_storage().unwrap(); - if storage.visited_nodes.borrow().len() != num_nodes { + if storage.visited_nodes.read().expect("read visited_nodes").len() != num_nodes { // As all nodes belonging to state part were visited, there is some // unexpected data in downloaded state part. return Err(StorageError::UnexpectedTrieValue); diff --git a/core/store/src/trie/state_snapshot.rs b/core/store/src/trie/state_snapshot.rs index 77b89cf047a..9611467cc87 100644 --- a/core/store/src/trie/state_snapshot.rs +++ b/core/store/src/trie/state_snapshot.rs @@ -101,7 +101,7 @@ impl StateSnapshot { // Flat state snapshot needs to be at a height that lets it // replay the last chunk of the shard. let desired_flat_head = chunk.prev_block_hash(); - match flat_storage.update_flat_head(desired_flat_head, true) { + match flat_storage.update_flat_head(desired_flat_head) { Ok(_) => { tracing::debug!(target: "state_snapshot", ?shard_uid, ?current_flat_head, ?desired_flat_head, "Successfully moved FlatStorage head of the snapshot"); } diff --git a/core/store/src/trie/trie_recording.rs b/core/store/src/trie/trie_recording.rs index 112b682d498..9768ce142e3 100644 --- a/core/store/src/trie/trie_recording.rs +++ b/core/store/src/trie/trie_recording.rs @@ -29,6 +29,14 @@ impl TrieRecorder { } } + /// Records value without increasing the recorded size. + /// This is used to bypass witness size checks in order to generate + /// large witness for testing. + #[cfg(feature = "test_features")] + pub fn record_unaccounted(&mut self, hash: &CryptoHash, node: Arc<[u8]>) { + self.recorded.insert(*hash, node); + } + pub fn record(&mut self, hash: &CryptoHash, node: Arc<[u8]>) { let size = node.len(); if self.recorded.insert(*hash, node).is_none() { @@ -51,7 +59,6 @@ impl TrieRecorder { } pub fn recorded_storage_size(&self) -> usize { - debug_assert!(self.size == self.recorded.values().map(|v| v.len()).sum::()); self.size } @@ -321,7 +328,7 @@ mod trie_recording_tests { // Let's capture the baseline node counts - this is what will happen // in production. let trie = get_trie_for_shard(&tries, shard_uid, state_root, use_flat_storage); - trie.accounting_cache.borrow_mut().set_enabled(enable_accounting_cache); + trie.accounting_cache.borrow().enable_switch().set(enable_accounting_cache); for key in &keys_to_get { assert_eq!(trie.get(key).unwrap(), data_in_trie.get(key).cloned()); } @@ -341,7 +348,7 @@ mod trie_recording_tests { // we get are exactly the same. let trie = get_trie_for_shard(&tries, shard_uid, state_root, use_flat_storage) .recording_reads(); - trie.accounting_cache.borrow_mut().set_enabled(enable_accounting_cache); + trie.accounting_cache.borrow().enable_switch().set(enable_accounting_cache); for key in &keys_to_get { assert_eq!(trie.get(key).unwrap(), data_in_trie.get(key).cloned()); } @@ -360,13 +367,13 @@ mod trie_recording_tests { // Now let's do this again with memtries enabled. Check that counters // are the same. assert_eq!(MEM_TRIE_NUM_LOOKUPS.get(), mem_trie_lookup_counts_before); - tries.load_mem_trie(&shard_uid, None).unwrap(); + tries.load_mem_trie(&shard_uid, None, false).unwrap(); // Delete the on-disk state so that we really know we're using // in-memory tries. destructively_delete_in_memory_state_from_disk(&store, &data_in_trie); let trie = get_trie_for_shard(&tries, shard_uid, state_root, use_flat_storage) .recording_reads(); - trie.accounting_cache.borrow_mut().set_enabled(enable_accounting_cache); + trie.accounting_cache.borrow().enable_switch().set(enable_accounting_cache); for key in &keys_to_get { assert_eq!(trie.get(key).unwrap(), data_in_trie.get(key).cloned()); } @@ -392,7 +399,7 @@ mod trie_recording_tests { ); let trie = Trie::from_recorded_storage(partial_storage.clone(), state_root, use_flat_storage); - trie.accounting_cache.borrow_mut().set_enabled(enable_accounting_cache); + trie.accounting_cache.borrow().enable_switch().set(enable_accounting_cache); for key in &keys_to_get { assert_eq!(trie.get(key).unwrap(), data_in_trie.get(key).cloned()); } @@ -410,7 +417,7 @@ mod trie_recording_tests { // Build a Trie using recorded storage and enable recording_reads on this Trie let trie = Trie::from_recorded_storage(partial_storage, state_root, use_flat_storage) .recording_reads(); - trie.accounting_cache.borrow_mut().set_enabled(enable_accounting_cache); + trie.accounting_cache.borrow().enable_switch().set(enable_accounting_cache); for key in &keys_to_get { assert_eq!(trie.get(key).unwrap(), data_in_trie.get(key).cloned()); } diff --git a/core/store/src/trie/trie_storage.rs b/core/store/src/trie/trie_storage.rs index 7c589c09575..4293c08a406 100644 --- a/core/store/src/trie/trie_storage.rs +++ b/core/store/src/trie/trie_storage.rs @@ -10,8 +10,8 @@ use near_primitives::challenge::PartialState; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardUId; use near_primitives::types::ShardId; -use std::cell::RefCell; use std::collections::{HashMap, HashSet, VecDeque}; +use std::num::NonZeroUsize; use std::sync::{Arc, Mutex}; pub(crate) struct BoundedQueue { @@ -121,7 +121,7 @@ impl TrieCacheInner { let max_elements = total_size_limit.div_ceil(Self::PER_ENTRY_OVERHEAD); let max_elements = usize::try_from(max_elements).unwrap(); Self { - cache: LruCache::new(max_elements), + cache: LruCache::new(NonZeroUsize::new(max_elements).unwrap()), deletions: BoundedQueue::new(deletions_queue_capacity), total_size: 0, total_size_limit, @@ -142,7 +142,8 @@ impl TrieCacheInner { } pub(crate) fn put(&mut self, key: CryptoHash, value: Arc<[u8]>) { - while self.total_size > self.total_size_limit || self.cache.len() == self.cache.cap() { + while self.total_size > self.total_size_limit || self.cache.len() == self.cache.cap().get() + { // First, try to evict value using the key from deletions queue. match self.deletions.pop() { Some(key) => match self.cache.pop(&key) { @@ -280,7 +281,7 @@ impl TrieCache { } } -pub trait TrieStorage { +pub trait TrieStorage: Send + Sync { /// Get bytes of a serialized `TrieNode`. /// /// # Errors @@ -305,7 +306,7 @@ pub trait TrieStorage { #[derive(Default)] pub struct TrieMemoryPartialStorage { pub(crate) recorded_storage: HashMap>, - pub(crate) visited_nodes: RefCell>, + pub(crate) visited_nodes: std::sync::RwLock>, } impl TrieStorage for TrieMemoryPartialStorage { @@ -316,7 +317,7 @@ impl TrieStorage for TrieMemoryPartialStorage { *hash, )); if result.is_ok() { - self.visited_nodes.borrow_mut().insert(*hash); + self.visited_nodes.write().expect("write visited_nodes").insert(*hash); } result } @@ -332,7 +333,7 @@ impl TrieMemoryPartialStorage { } pub fn partial_state(&self) -> PartialState { - let touched_nodes = self.visited_nodes.borrow(); + let touched_nodes = self.visited_nodes.read().expect("read visited_nodes"); let mut nodes: Vec<_> = self.recorded_storage .iter() diff --git a/core/store/src/trie/trie_tests.rs b/core/store/src/trie/trie_tests.rs index b4e6cf9244c..68b23311189 100644 --- a/core/store/src/trie/trie_tests.rs +++ b/core/store/src/trie/trie_tests.rs @@ -9,16 +9,14 @@ use near_primitives::hash::{hash, CryptoHash}; use near_primitives::shard_layout::ShardUId; use rand::seq::SliceRandom; use rand::Rng; -use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; -use std::rc::Rc; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; /// TrieMemoryPartialStorage, but contains only the first n requested nodes. pub struct IncompletePartialStorage { pub(crate) recorded_storage: HashMap>, - pub(crate) visited_nodes: RefCell>, + pub(crate) visited_nodes: RwLock>, pub node_count_to_fail_after: usize, } @@ -42,9 +40,10 @@ impl TrieStorage for IncompletePartialStorage { .cloned() .expect("Recorded storage is missing the given hash"); - self.visited_nodes.borrow_mut().insert(*hash); + let mut lock = self.visited_nodes.write().unwrap(); + lock.insert(*hash); - if self.visited_nodes.borrow().len() > self.node_count_to_fail_after { + if lock.len() > self.node_count_to_fail_after { Err(StorageError::MissingTrieValue( MissingTrieValueContext::TrieMemoryPartialStorage, *hash, @@ -80,7 +79,7 @@ where println!("Test touches {} nodes, expected result {:?}...", size, expected); for i in 0..(size + 1) { let storage = IncompletePartialStorage::new(storage.clone(), i); - let new_trie = Trie::new(Rc::new(storage), *trie.get_root(), None); + let new_trie = Trie::new(Arc::new(storage), *trie.get_root(), None); let result = test(new_trie).map(|v| v.1); if i < size { assert_matches!( @@ -150,17 +149,17 @@ mod nodes_counter_tests { NibbleSlice::encode_nibbles(&nibbles, false).into_vec() } - fn create_trie(items: &[(Vec, Option>)]) -> Rc { + fn create_trie(items: &[(Vec, Option>)]) -> Trie { let tries = TestTriesBuilder::new().build(); let shard_uid = ShardUId { version: 1, shard_id: 0 }; let trie_changes = simplify_changes(&items); let state_root = test_populate_trie(&tries, &Trie::EMPTY_ROOT, shard_uid, trie_changes); let trie = tries.get_trie_for_shard(shard_uid, state_root); - Rc::new(trie) + trie } // Get values corresponding to keys one by one, returning vector of numbers of touched nodes for each `get`. - fn get_touched_nodes_numbers(trie: Rc, items: &[(Vec, Option>)]) -> Vec { + fn get_touched_nodes_numbers(trie: &Trie, items: &[(Vec, Option>)]) -> Vec { items .iter() .map(|(key, value)| { @@ -183,7 +182,7 @@ mod nodes_counter_tests { (create_trie_key(&[1, 0, 0]), Some(vec![2])), ]; let trie = create_trie(&trie_items); - assert_eq!(get_touched_nodes_numbers(trie, &trie_items), vec![5, 5, 4]); + assert_eq!(get_touched_nodes_numbers(&trie, &trie_items), vec![5, 5, 4]); } // Check that same values are stored in the same trie node. @@ -197,7 +196,7 @@ mod nodes_counter_tests { (create_trie_key(&[1, 1]), Some(vec![1])), ]; let trie = create_trie(&trie_items); - assert_eq!(get_touched_nodes_numbers(trie, &trie_items), vec![4, 4]); + assert_eq!(get_touched_nodes_numbers(&trie, &trie_items), vec![4, 4]); } } @@ -303,7 +302,7 @@ mod trie_storage_tests { let mut accounting_cache = TrieAccountingCache::new(None); let key = hash(&value); - accounting_cache.set_enabled(true); + accounting_cache.enable_switch().set(true); let _ = accounting_cache.retrieve_raw_bytes_with_accounting(&key, &trie_caching_storage); let count_before: TrieNodesCount = accounting_cache.get_trie_nodes_count(); @@ -339,7 +338,7 @@ mod trie_storage_tests { // Move to CachingChunk mode. Retrieval should increment the counter, because it is the first time we accessed // item while caching chunk. - accounting_cache.set_enabled(true); + accounting_cache.enable_switch().set(true); let count_before = accounting_cache.get_trie_nodes_count(); let result = accounting_cache.retrieve_raw_bytes_with_accounting(&key, &trie_caching_storage); @@ -361,7 +360,7 @@ mod trie_storage_tests { // Even if we switch to caching shard, retrieval shouldn't increment the counter. Accounting cache only grows and is // dropped only when trie caching storage is dropped. - accounting_cache.set_enabled(true); + accounting_cache.enable_switch().set(true); let count_before = accounting_cache.get_trie_nodes_count(); let result = accounting_cache.retrieve_raw_bytes_with_accounting(&key, &trie_caching_storage); @@ -393,12 +392,12 @@ mod trie_storage_tests { let value = &values[0]; let key = hash(&value); - accounting_cache.set_enabled(true); + accounting_cache.enable_switch().set(true); let result = accounting_cache.retrieve_raw_bytes_with_accounting(&key, &trie_caching_storage); assert_eq!(result.unwrap().as_ref(), value); - accounting_cache.set_enabled(true); + accounting_cache.enable_switch().set(true); for value in values[1..].iter() { let result = accounting_cache .retrieve_raw_bytes_with_accounting(&hash(value), &trie_caching_storage); diff --git a/core/store/src/trie/update.rs b/core/store/src/trie/update.rs index 4c3640c59b3..d5632e0a5b0 100644 --- a/core/store/src/trie/update.rs +++ b/core/store/src/trie/update.rs @@ -1,4 +1,5 @@ pub use self::iterator::TrieUpdateIterator; +use super::accounting_cache::TrieAccountingCacheSwitch; use super::{OptimizedValueRef, Trie, TrieWithReadLock}; use crate::trie::{KeyLookupMode, TrieChanges}; use crate::{StorageError, TrieStorage}; @@ -10,7 +11,7 @@ use near_primitives::types::{ }; use near_vm_runner::ContractCode; use std::collections::BTreeMap; -use std::rc::Rc; +use std::sync::Arc; mod iterator; @@ -19,11 +20,11 @@ mod iterator; /// requesting and compiling contracts, as any contract code read and /// compilation is a major bottleneck during chunk execution. struct ContractStorage { - storage: Rc, + storage: Arc, } impl ContractStorage { - fn new(storage: Rc) -> Self { + fn new(storage: Arc) -> Self { Self { storage } } @@ -172,10 +173,18 @@ impl TrieUpdate { } pub fn remove(&mut self, trie_key: TrieKey) { - self.prospective.insert(trie_key.to_vec(), TrieKeyValueUpdate { trie_key, value: None }); + // We count removals performed by the contracts and charge extra for them. + // A malicious contract could generate a lot of storage proof by a removal, + // charging extra provides a safe upper bound. (https://github.com/near/nearcore/issues/10890) + // This only applies to removals performed by the contracts. Removals performed + // by the runtime are assumed to be non-malicious and we don't charge extra for them. if let Some(recorder) = &self.trie.recorder { - recorder.borrow_mut().record_removal(); + if matches!(trie_key, TrieKey::ContractData { .. }) { + recorder.borrow_mut().record_removal(); + } } + + self.prospective.insert(trie_key.to_vec(), TrieKeyValueUpdate { trie_key, value: None }); } pub fn commit(&mut self, event: StateChangeCause) { @@ -253,8 +262,18 @@ impl TrieUpdate { self.trie.get_root() } - pub fn set_trie_cache_mode(&self, state: TrieCacheMode) { - self.trie.accounting_cache.borrow_mut().set_enabled(state == TrieCacheMode::CachingChunk); + /// Returns a guard-style type that will reset the trie cache mode back to the initial state + /// once dropped. + /// + /// Only changes the cache mode if `mode` is `Some`. Will always restore the previous cache + /// mode upon drop. The type should not be `std::mem::forget`-ten, as it will leak memory. + pub fn with_trie_cache_mode(&self, mode: Option) -> TrieCacheModeGuard { + let switch = self.trie.accounting_cache.borrow().enable_switch(); + let previous = switch.enabled(); + if let Some(mode) = mode { + switch.set(mode == TrieCacheMode::CachingChunk); + } + TrieCacheModeGuard(previous, switch) } } @@ -268,6 +287,13 @@ impl crate::TrieAccess for TrieUpdate { } } +pub struct TrieCacheModeGuard(bool, TrieAccountingCacheSwitch); +impl Drop for TrieCacheModeGuard { + fn drop(&mut self) { + self.1.set(self.0); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/time/Cargo.toml b/core/time/Cargo.toml new file mode 100644 index 00000000000..f3bcb23119a --- /dev/null +++ b/core/time/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "near-time" +version.workspace = true +authors.workspace = true +edition.workspace = true +description = "This crate contains the time helper specific to nearcore" +repository.workspace = true +license.workspace = true +publish = true + +[lints] +workspace = true + +[dependencies] +time = { workspace = true, features = ["formatting", "parsing"] } +once_cell = { workspace = true, optional = true } +tokio = { workspace = true, features = ["time", "sync"], optional = true } +serde = { workspace = true, optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +clock = ["tokio", "once_cell"] +serde = ["dep:serde", "time/serde"] diff --git a/core/time/LICENSE-APACHE b/core/time/LICENSE-APACHE new file mode 120000 index 00000000000..0eb30ff1b50 --- /dev/null +++ b/core/time/LICENSE-APACHE @@ -0,0 +1 @@ +../../licenses/LICENSE-APACHE \ No newline at end of file diff --git a/core/time/LICENSE-MIT b/core/time/LICENSE-MIT new file mode 120000 index 00000000000..df3ae884029 --- /dev/null +++ b/core/time/LICENSE-MIT @@ -0,0 +1 @@ +../../licenses/LICENSE-MIT \ No newline at end of file diff --git a/core/time/src/clock.rs b/core/time/src/clock.rs new file mode 100644 index 00000000000..d75186d9c8a --- /dev/null +++ b/core/time/src/clock.rs @@ -0,0 +1,262 @@ +use std::cmp::Ordering; +use std::collections::BinaryHeap; +use std::sync::{Arc, Mutex}; +use time::ext::InstantExt; + +use once_cell::sync::Lazy; + +use crate::{Deadline, Duration, Instant, Utc}; + +// Instant doesn't have a deterministic constructor, +// however since Instant is not convertible to an unix timestamp, +// we can snapshot Instant::now() once and treat it as a constant. +// All observable effects will be then deterministic. +static FAKE_CLOCK_MONO_START: Lazy = Lazy::new(Instant::now); + +// An arbitrary non-trivial deterministic Utc timestamp. +const FAKE_CLOCK_UTC_START: Lazy = Lazy::new(|| Utc::from_unix_timestamp(89108233).unwrap()); + +#[derive(Clone)] +enum ClockInner { + Real, + Fake(FakeClock), +} + +/// Clock encapsulates a system clock, allowing to replace it +/// with a fake in tests. +/// Since system clock is a source of external information, +/// it has to be replaced with a fake double, if we want our +/// tests to be deterministic. +/// +/// TODO: add tests. +#[derive(Clone)] +pub struct Clock(ClockInner); + +impl Clock { + /// Constructor of the real clock. Use it in production code. + /// Preferably construct it directly in the main() function, + /// so that it can be faked out in every other function. + pub fn real() -> Clock { + Clock(ClockInner::Real) + } + + /// Current time according to the monotone clock. + pub fn now(&self) -> Instant { + match &self.0 { + ClockInner::Real => Instant::now(), + ClockInner::Fake(fake) => fake.now(), + } + } + + /// Current time according to the system/walltime clock. + pub fn now_utc(&self) -> Utc { + match &self.0 { + ClockInner::Real => Utc::now_utc(), + ClockInner::Fake(fake) => fake.now_utc(), + } + } + + /// Cancellable. + pub async fn sleep_until_deadline(&self, t: Deadline) { + match t { + Deadline::Infinite => std::future::pending().await, + Deadline::Finite(t) => self.sleep_until(t).await, + } + } + + /// Cancellable. + pub async fn sleep_until(&self, t: Instant) { + match &self.0 { + ClockInner::Real => tokio::time::sleep_until(t.into()).await, + ClockInner::Fake(fake) => fake.sleep_until(t).await, + } + } + + /// Cancellable. + pub async fn sleep(&self, d: Duration) { + match &self.0 { + ClockInner::Real => tokio::time::sleep(d.try_into().unwrap()).await, + ClockInner::Fake(fake) => fake.sleep(d).await, + } + } +} + +struct FakeClockInner { + utc: Utc, + instant: Instant, + waiters: BinaryHeap, +} + +/// Whenever a user of a FakeClock calls `sleep` for `sleep_until`, we create a +/// `ClockWaiterInHeap` so that the returned future can be completed when the +/// clock advances past the desired deadline. +struct ClockWaiterInHeap { + deadline: Instant, + waker: tokio::sync::oneshot::Sender<()>, +} + +impl PartialEq for ClockWaiterInHeap { + fn eq(&self, other: &Self) -> bool { + self.deadline == other.deadline + } +} + +impl PartialOrd for ClockWaiterInHeap { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for ClockWaiterInHeap {} + +impl Ord for ClockWaiterInHeap { + fn cmp(&self, other: &Self) -> Ordering { + other.deadline.cmp(&self.deadline) + } +} + +impl FakeClockInner { + pub fn new(utc: Utc) -> Self { + Self { utc, instant: *FAKE_CLOCK_MONO_START, waiters: BinaryHeap::new() } + } + + pub fn now(&mut self) -> Instant { + self.instant + } + pub fn now_utc(&mut self) -> Utc { + self.utc + } + pub fn advance(&mut self, d: Duration) { + assert!(d >= Duration::ZERO); + if d == Duration::ZERO { + return; + } + self.instant += d; + self.utc += d; + while let Some(earliest_waiter) = self.waiters.peek() { + if earliest_waiter.deadline <= self.instant { + self.waiters.pop().unwrap().waker.send(()).ok(); + } else { + break; + } + } + } + pub fn advance_until(&mut self, t: Instant) { + let by = t.signed_duration_since(self.now()); + self.advance(by); + } +} + +/// TEST-ONLY +#[derive(Clone)] +pub struct FakeClock(Arc>); + +impl FakeClock { + /// Constructor of a fake clock. Use it in tests. + /// It supports manually moving time forward (via advance()). + /// You can also arbitrarily set the UTC time in runtime. + /// Use FakeClock::clock() when calling prod code from tests. + pub fn new(utc: Utc) -> Self { + Self(Arc::new(Mutex::new(FakeClockInner::new(utc)))) + } + pub fn now(&self) -> Instant { + self.0.lock().unwrap().now() + } + + pub fn now_utc(&self) -> Utc { + self.0.lock().unwrap().now_utc() + } + pub fn advance(&self, d: Duration) { + self.0.lock().unwrap().advance(d); + } + pub fn advance_until(&self, t: Instant) { + self.0.lock().unwrap().advance_until(t); + } + pub fn clock(&self) -> Clock { + Clock(ClockInner::Fake(self.clone())) + } + pub fn set_utc(&self, utc: Utc) { + self.0.lock().unwrap().utc = utc; + } + + /// Cancel-safe. + pub async fn sleep(&self, d: Duration) { + if d <= Duration::ZERO { + return; + } + let receiver = { + let mut inner = self.0.lock().unwrap(); + let (sender, receiver) = tokio::sync::oneshot::channel(); + let waiter = ClockWaiterInHeap { waker: sender, deadline: inner.now() + d }; + inner.waiters.push(waiter); + receiver + }; + receiver.await.unwrap(); + } + + /// Cancel-safe. + pub async fn sleep_until(&self, t: Instant) { + let receiver = { + let mut inner = self.0.lock().unwrap(); + if inner.now() >= t { + return; + } + let (sender, receiver) = tokio::sync::oneshot::channel(); + let waiter = ClockWaiterInHeap { waker: sender, deadline: t }; + inner.waiters.push(waiter); + receiver + }; + receiver.await.unwrap(); + } + + /// Returns the earliest waiter, or None if no one is waiting on the clock. + /// The returned instant is guaranteed to be <= any waiter that is currently + /// waiting on the clock to advance. + pub fn first_waiter(&self) -> Option { + let inner = self.0.lock().unwrap(); + inner.waiters.peek().map(|waiter| waiter.deadline) + } +} + +impl Default for FakeClock { + fn default() -> FakeClock { + Self::new(*FAKE_CLOCK_UTC_START) + } +} + +/// Interval equivalent to tokio::time::Interval with +/// MissedTickBehavior::Skip. +pub struct Interval { + next: Instant, + period: time::Duration, +} + +impl Interval { + pub fn new(next: Instant, period: time::Duration) -> Self { + Self { next, period } + } + + /// Cancel-safe. + pub async fn tick(&mut self, clock: &Clock) { + clock.sleep_until(self.next).await; + let now = clock.now(); + // Implementation of `tokio::time::MissedTickBehavior::Skip`. + // Please refer to https://docs.rs/tokio/latest/tokio/time/enum.MissedTickBehavior.html# + // for details. In essence, if more than `period` of time passes between consecutive + // calls to tick, then the second tick completes immediately and the next one will be + // aligned to the original schedule. + self.next = now.add_signed(self.period).sub_signed(Duration::nanoseconds( + ((now.signed_duration_since(self.next)).whole_nanoseconds() + % self.period.whole_nanoseconds()) + .try_into() + // This operation is practically guaranteed not to + // fail, as in order for it to fail, `period` would + // have to be longer than `now - timeout`, and both + // would have to be longer than 584 years. + // + // If it did fail, there's not a good way to pass + // the error along to the user, so we just panic. + .expect("too much time has elapsed since the interval was supposed to tick"), + )); + } +} diff --git a/core/time/src/lib.rs b/core/time/src/lib.rs new file mode 100644 index 00000000000..a16c6ad29a3 --- /dev/null +++ b/core/time/src/lib.rs @@ -0,0 +1,60 @@ +//! Time module provides a non-global clock, which should be passed +//! as an argument to functions which need to read the current time. +//! In particular try to avoid storing the clock instances in the objects. +//! Functions which use system clock directly are non-hermetic, which +//! makes them effectively non-deterministic and hard to test. +//! +//! Clock provides 2 types of time reads: +//! 1. now() (aka POSIX CLOCK_MONOTONIC, aka time::Instant) +//! time as perceived by the machine making the measurement. +//! The subsequent calls to now() are guaranteed to return monotonic +//! results. It should be used for measuring the latency of operations +//! as observed by the machine. The time::Instant itself doesn't +//! translate to any specific timestamp, so it is not meaningful for +//! anyone other than the machine doing the measurement. +//! 2. now_utc() (aka POSIX CLOCK_REALTIME, aka time::Utc) +//! expected to approximate the (global) UTC time. +//! There is NO guarantee that the subsequent reads will be monotonic, +//! as CLOCK_REALTIME it configurable in the OS settings, or can be updated +//! during NTP sync. Should be used whenever you need to communicate a timestamp +//! over the network, or store it for later use. Remember that clocks +//! of different machines are not perfectly synchronized, and in extreme +//! cases can be totally skewed. + +#[cfg(feature = "clock")] +pub mod clock; + +#[cfg(feature = "clock")] +pub use clock::*; + +#[cfg(feature = "serde")] +pub mod serde; + +#[cfg(feature = "serde")] +pub use serde::*; + +pub use time::error; + +// TODO: consider wrapping these types to prevent interactions +// with other time libraries, especially to prevent the direct access +// to the realtime (i.e. not through the Clock). +pub type Instant = std::time::Instant; +// TODO: OffsetDateTime stores the timestamp in a decomposed form of +// (year,month,day,hour,...). If we find it inefficient, we should +// probably migrate to a pure UNIX timestamp and convert is to datetime +// only when needed. +pub type Utc = time::OffsetDateTime; +pub type Duration = time::Duration; + +// By the definition of derive(PartialEq), Finite(...) < Infinite. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Deadline { + Finite(Instant), + Infinite, +} + +impl From for Deadline { + fn from(t: Instant) -> Deadline { + Deadline::Finite(t) + } +} diff --git a/core/time/src/serde.rs b/core/time/src/serde.rs new file mode 100644 index 00000000000..6967b7af805 --- /dev/null +++ b/core/time/src/serde.rs @@ -0,0 +1,178 @@ +/// Provides serialization of Duration as std::time::Duration. +pub mod serde_duration_as_std { + use crate::Duration; + use serde::Deserialize; + use serde::Serialize; + + pub fn serialize(dur: &Duration, s: S) -> Result + where + S: serde::Serializer, + { + let std: std::time::Duration = (*dur) + .try_into() + .map_err(|_| serde::ser::Error::custom("Duration conversion failed"))?; + std.serialize(s) + } + + pub fn deserialize<'de, D>(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + let std: std::time::Duration = Deserialize::deserialize(d)?; + Ok(std.try_into().map_err(|_| serde::de::Error::custom("Duration conversion failed"))?) + } +} + +/// Provides serialization of Duration as std::time::Duration. +pub mod serde_opt_duration_as_std { + use crate::Duration; + use serde::Deserialize; + use serde::Serialize; + + pub fn serialize(dur: &Option, s: S) -> Result + where + S: serde::Serializer, + { + match dur { + Some(dur) => { + let std: std::time::Duration = (*dur) + .try_into() + .map_err(|_| serde::ser::Error::custom("Duration conversion failed"))?; + std.serialize(s) + } + None => s.serialize_none(), + } + } + + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let std: Option = Deserialize::deserialize(d)?; + Ok(std + .map(|std| { + std.try_into().map_err(|_| serde::de::Error::custom("Duration conversion failed")) + }) + .transpose()?) + } +} + +pub mod serde_utc_as_iso { + use crate::Utc; + use serde::{Deserialize, Serialize}; + use time::format_description::well_known::Iso8601; + + pub fn serialize(utc: &Utc, s: S) -> Result + where + S: serde::Serializer, + { + utc.format(&Iso8601::DEFAULT).map_err(::custom)?.serialize(s) + } + + pub fn deserialize<'de, D>(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + let str: String = Deserialize::deserialize(d)?; + Utc::parse(&str, &Iso8601::DEFAULT).map_err(::custom) + } +} + +pub mod serde_opt_utc_as_iso { + use crate::Utc; + use serde::{Deserialize, Serialize}; + use time::format_description::well_known::Iso8601; + + pub fn serialize(utc: &Option, s: S) -> Result + where + S: serde::Serializer, + { + match utc { + Some(utc) => utc + .format(&Iso8601::DEFAULT) + .map_err(::custom)? + .serialize(s), + None => s.serialize_none(), + } + } + + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let str: Option = Deserialize::deserialize(d)?; + Ok(str + .map(|str| { + Utc::parse(&str, &Iso8601::DEFAULT).map_err(::custom) + }) + .transpose()?) + } +} + +#[cfg(test)] +mod tests { + use crate::Duration; + use serde_json; + + #[test] + fn test_serde_duration() { + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] + struct Test(#[serde(with = "super::serde_duration_as_std")] Duration); + + let expected = Test(Duration::milliseconds(1234)); + let str = serde_json::to_string(&expected).unwrap(); + assert_eq!(str, r#"{"secs":1,"nanos":234000000}"#); + let actual: Test = serde_json::from_str(&str).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn test_serde_opt_duration() { + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] + struct Test(#[serde(with = "super::serde_opt_duration_as_std")] Option); + + let expected = Test(Some(Duration::milliseconds(1234))); + let str = serde_json::to_string(&expected).unwrap(); + assert_eq!(str, r#"{"secs":1,"nanos":234000000}"#); + let actual: Test = serde_json::from_str(&str).unwrap(); + assert_eq!(actual, expected); + + let expected = Test(None); + let str = serde_json::to_string(&expected).unwrap(); + assert_eq!(str, r#"null"#); + let actual: Test = serde_json::from_str(&str).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn test_serde_utc() { + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] + struct Test(#[serde(with = "super::serde_utc_as_iso")] crate::Utc); + + let expected = + Test(crate::Utc::from_unix_timestamp_nanos(1709582343123456789i128).unwrap()); + let str = serde_json::to_string(&expected).unwrap(); + assert_eq!(str, r#""2024-03-04T19:59:03.123456789Z""#); + let actual: Test = serde_json::from_str(&str).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn test_serde_opt_utc() { + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] + struct Test(#[serde(with = "super::serde_opt_utc_as_iso")] Option); + + let expected = + Test(Some(crate::Utc::from_unix_timestamp_nanos(1709582343123456789i128).unwrap())); + let str = serde_json::to_string(&expected).unwrap(); + assert_eq!(str, r#""2024-03-04T19:59:03.123456789Z""#); + let actual: Test = serde_json::from_str(&str).unwrap(); + assert_eq!(actual, expected); + + let expected = Test(None); + let str = serde_json::to_string(&expected).unwrap(); + assert_eq!(str, r#"null"#); + let actual: Test = serde_json::from_str(&str).unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/debug_scripts/Pipfile.lock b/debug_scripts/Pipfile.lock index 2f5fbf4b352..349250fe099 100644 --- a/debug_scripts/Pipfile.lock +++ b/debug_scripts/Pipfile.lock @@ -35,11 +35,12 @@ }, "certifi": { "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], + "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==2024.2.2" + "version": "==2024.7.4" }, "charset-normalizer": { "hashes": [ @@ -188,11 +189,12 @@ }, "urllib3": { "hashes": [ - "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", - "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3", + "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429" ], + "index": "pypi", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.18" + "version": "==1.26.19" } }, "develop": {} diff --git a/deny.toml b/deny.toml index 38b454a247b..78f19321476 100644 --- a/deny.toml +++ b/deny.toml @@ -1,3 +1,4 @@ +[graph] targets = [ { triple = "x86_64-unknown-linux-musl" }, { triple = "x86_64-pc-windows-msvc" }, @@ -5,12 +6,8 @@ targets = [ ] [licenses] -unlicensed = "deny" +version = 2 allow = ["MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception"] -deny = ["AGPL-1.0", "AGPL-3.0"] -copyleft = "warn" -allow-osi-fsf-free = "either" -default = "deny" confidence-threshold = 0.8 exceptions = [] private = { ignore = false, registries = [] } @@ -37,7 +34,6 @@ deny = [ ] skip = [ - { name = "tracing-log", version = "0.1.3" }, # wasmer 0.17 and wasmtime 0.17 uses older versions of some crates { name = "generic-array", version = "=0.12.4" }, @@ -66,7 +62,6 @@ skip = [ { name = "object", version = "=0.30.4" }, { name = "memoffset", version = "=0.6.5" }, { name = "memoffset", version = "=0.8.0" }, - { name = "linux-raw-sys", version = "=0.3.8" }, { name = "addr2line", version = "=0.19.0" }, { name = "gimli", version = "=0.27.2" }, @@ -89,13 +84,8 @@ skip = [ { name = "windows_x86_64_msvc", version = "=0.36.1" }, # old version of rustix, wasmtime, is-terminal, etc. - { name = "rustix", version = "0.36.6" }, - { name = "rustix", version = "0.37.20" }, - { name = "linux-raw-sys", version = "0.1.4" }, { name = "windows-sys", version = "=0.42.0" }, - { name = "windows-sys", version = "=0.45.0" }, { name = "windows_x86_64_msvc", version = "=0.42.2" }, - { name = "windows-targets", version = "=0.42.1" }, # ed25519-dalek uses older versions of rand and rand_core { name = "rand_core", version = "=0.5.1" }, @@ -116,9 +106,6 @@ skip = [ { name = "rustc_version", version = "=0.2.3" }, { name = "errno", version = "=0.2.8" }, - # paperclip-macros, strum_macros, walrus-macro depend on this while clap3.1.6 uses heck=0.4.0 - { name = "heck", version = "=0.3.3" }, - # Bolero requires a newer version and the rest of the ecosystem hasn't caught up yet. { name = "hashbrown", version = "0.11.0" }, { name = "hashbrown", version = "0.12.0" }, @@ -129,9 +116,6 @@ skip = [ # prometheus depends on an old version of protobuf { name = "protobuf", version = "=2.27.1" }, - # opentelemetry-otlp depends on an old version of tonic which depends on an old version of tokio-util - { name = "tokio-util", version = "=0.6.10" }, - # rust-s3 is using an old version of smartstring { name = "smartstring", version = "=0.2.10" }, diff --git a/docs/architecture/README.md b/docs/architecture/README.md index b994a4d260b..99f48693034 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -209,7 +209,7 @@ the other hand, are sent to `ClientActor` for further processing. This crate contains the main entry point to runtime -- `Runtime::apply`. This function takes `ApplyState`, which contains necessary information passed from chain to runtime, a list of `SignedTransaction` and a list of `Receipt`, and -returns a `ApplyResult`, which includes state changes, execution outcomes, etc. +returns an `ApplyResult`, which includes state changes, execution outcomes, etc. **Architecture Invariant:** The state update is only finalized at the end of `apply`. During all intermediate steps state changes can be reverted. @@ -299,4 +299,4 @@ Note that what counts as a slow test isn’t exactly defined as of now. If it takes just a couple seconds then it’s probably fine. Anything slower should probably be classified as an expensive test. In particular, if libtest complains the test takes more than 60 seconds -then it definitely is and expensive test. +then it definitely is an expensive test. diff --git a/docs/architecture/how/receipt-congestion.md b/docs/architecture/how/receipt-congestion.md index b013c4127c8..0b0010a055d 100644 --- a/docs/architecture/how/receipt-congestion.md +++ b/docs/architecture/how/receipt-congestion.md @@ -27,7 +27,7 @@ through the network links. But if we only look at that limit, we can send very many receipts with a lot of gas attached to them. Thus, the model considers it unlimited. -Okay, we have the capacities of the network modeled. Now let's look how a +Okay, we have the capacities of the network modeled. Now let's look at how a receipt execution maps onto it. Let's say a receipt starts at shard 1 with 300 Tgas. While executing, it burns 100 Tgas and @@ -36,7 +36,7 @@ creates an outgoing receipts with 200 Tgas to another shard. We can represent th ![graph](../../images/congestion/receipt_flow_example_0.svg) -Note: The graph includes the execution of the next block with the 200 Tgas to th +Note: The graph includes the execution of the next block with the 200 Tgas to the sink of shard 2. This should be interpreted as if we continue sending the exact same workload on all shards every block. Then we reach this steady state where we continue to have these gas assignments per edge. @@ -46,7 +46,7 @@ outflow per is limited to N * 1000 Tgas but the incoming flow is unlimited. For a finite amount of time, we can accept more inflow than outflow, we just have to add buffers to store what we cannot execute, yet. But to stay within finite memory requirements, we need to fall back to a flow diagram where outflows are greater or equal to inflows within a finite time frame. -Next, we look at a ideas one at the time before combining some of them into the +Next, we look at ideas one at a time before combining some of them into the cross-shard congestion design proposed in [NEP-539](https://github.com/near/NEPs/pull/539). @@ -142,7 +142,7 @@ receiving shard already has too high memory usage. ## Idea 4: Keep minimum incoming queue length to avoid deadlocks This is the final idea we need. To avoid deadlocks, we ensure that we can always -send receipts to a shard that has not enough work in the delayed receipts queue +send receipts to a shard that does not have enough work in the delayed receipts queue already. Basically, the backpressure limits from idea 3 are only applied to incoming diff --git a/docs/architecture/how/resharding.md b/docs/architecture/how/resharding.md index ea97e10ee23..26c6c3b655f 100644 --- a/docs/architecture/how/resharding.md +++ b/docs/architecture/how/resharding.md @@ -11,7 +11,7 @@ number of shards. The resharding is described in more detail in the following NEPs: * [NEP-0040](https://github.com/near/NEPs/blob/master/specs/Proposals/0040-split-states.md) -* [NEP-0508](https://github.com/near/NEPs/pull/508) - TODO - once merged use the master link +* [NEP-0508](https://github.com/near/NEPs/blob/master/neps/nep-0508.md) ## Shard layout diff --git a/docs/misc/state_witness_size_limits.md b/docs/misc/state_witness_size_limits.md new file mode 100644 index 00000000000..e461d444fb3 --- /dev/null +++ b/docs/misc/state_witness_size_limits.md @@ -0,0 +1,44 @@ +## State witness size limits + +Some limits were introduced to keep the size of `ChunkStateWitness` reasonable. +`ChunkStateWitness` contains all the incoming transactions and receipts that will be processed during chunk application and in theory a single receipt could be tens of megabatytes in size. Distributing a `ChunkStateWitness` this large would be troublesome, so we limit the size and number of transactions, receipts, etc. The limits aim to keep the total uncompressed size of `ChunkStateWitness` under 16MiB. + +There are two types of size limits: +* Hard limit - the size must be below this limit, anything else is considered invalid +* Soft limit - things are added until the limit is exceeded, after that things stop being added. The last added thing is allowed to slightly exceed the limit. + +The limits are: +* `max_transaction_size = 1.5 MiB` + * All transactions must be below 1.5 MiB, otherwise they'll be considered invalid and rejected. + * Previously was 4MiB, now reduced to 1.5MiB +* `max_receipt_size - 4 MiB`: + * All receipts must be below 4 MiB, otherwise they'll be considered invalid and rejected. + * Previously there was no limit on receipt size. Set to 4MiB, might be reduced to 1.5MiB in the future to match the transaction limit. +* `combined_transactions_size_limit - 4 MiB` + * Hard limit on total size of transactions from this and previous chunk. `ChunkStateWitness` contains transactions from two chunks, this limit applies to the sum of their sizes. +* `new_transactions_validation_state_size_soft_limit - 500 KiB` + * Validating new transactions generates storage proof (recorded trie nodes), which has to be limited. Once transaction validation generates more storage proof than this limit, the chunk producer stops adding new transactions to the chunk. +* `per_receipt_storage_proof_size_limit - 4 MB` + * Executing a receipt generates storage proof. A single receipt is allowed to generate at most 4MB of storage proof. This is a hard limit, receipts which generate more than that will fail. +* `main_storage_proof_size_soft_limit - 3 MB` + * This is a limit on the total size of storage proof generated by receipts in one chunk. Once receipts generate more storage proof than this limit, the chunk producer stops processing receipts and moves the rest to the delayed queue. + * It's a soft limit, which means that the total size of storage proof could reach 7 MB (2.99MB + one receipt which generates 4MB of storage proof) + * Due to implementation details it's hard to find the exact amount of storage proof generated by a receipt, so an upper bound estimation is used instead. This upper bound assumes that every removal generates additional 2000 bytes of storage proof, so receipts which perform a lot of trie removals might be limited more than theoretically applicable. +* `outgoing_receipts_usual_size_limit - 100 KiB` + * Limit on the size of outgoing receipts to another shard. Needed to keep the size of `source_receipt_proofs` small. + * On most block heights a shard isn't allowed to send receipts larger than 100 KiB to another shard. +* `outgoing_receipts_big_size_limit - 4.5 MiB` + * On every block height there's one special "allowed shard" which is allowed to send larger receipts, up to 4.5 MiB in total. + * A receiving shard will receive receipts from `num_shards - 1` shards using the usual limit and one shard using the big limit. + +In total that gives 4 MiB + 500 KiB + 7MB + 5*100 KiB + 4.5 MiB ~= 16 MiB of maximum witness size. Possibly a little more on missing chunks. + +### Validating the limits + +Chunk validators have to verify that chunk producer respected all of the limits while producing the chunk. This means that validators also have to keep track of recorded storage proof by recording all trie accesses and they have to enforce the limits. +If it turns out that some limits weren't respected, the validators will generate a different result of chunk application and they won't endorse the chunk. + +### Missing chunks + +When a chunk is mising on a shard, this shard will receive receipts from more than one block height. This could lead to large `source_receipt_proofs` so a mechanism is added to reduce the impact. If there are two or more missing chunks in a row, +the shard is considered fully congested and no new receipts will be sent to it (unless it's the `allowed_shard` to avoid deadlocks). diff --git a/docs/practices/style.md b/docs/practices/style.md index 6d55daaa9f8..a584a0a1542 100644 --- a/docs/practices/style.md +++ b/docs/practices/style.md @@ -352,7 +352,7 @@ When emitting events and spans with `tracing` prefer adding variable data via // GOOD debug!( target: "client", - validator_id = self.client.validator_signer.as_ref().map(|vs| { + validator_id = self.client.validator_signer.get().map(|vs| { tracing::field::display(vs.validator_id()) }), %hash, @@ -372,7 +372,7 @@ form of formatting, as seen in the following example: debug!( target: "client", "{:?} Received block {} <- {} at {} from {}, requested: {}", - self.client.validator_signer.as_ref().map(|vs| vs.validator_id()), + self.client.validator_signer.get().map(|vs| vs.validator_id()), hash, block.header().prev_hash(), block.header().height(), diff --git a/docs/practices/workflows/io_trace.md b/docs/practices/workflows/io_trace.md index 5622a36c9c3..c2c8353875e 100644 --- a/docs/practices/workflows/io_trace.md +++ b/docs/practices/workflows/io_trace.md @@ -309,7 +309,6 @@ apply_transactions shard_id=3 block=AUcauGxisMqNmZu5Ln7LLu8Li31H1sYD7wgd7AP6nQZR top-level: GET 8854 Block 981 BlockHeader 16556 BlockHeight 59155 BlockInfo 2 BlockMerkleTree 330009 BlockMisc 1 BlockOrdinal 31924 BlockPerHeight 863 BlockRefCount 1609 BlocksToCatchup 1557 ChallengedBlocks 4 ChunkExtra 5135 ChunkHashesByHeight 128788 Chunks 35 EpochInfo 1 EpochStart 98361 FlatState 1150 HeaderHashesByHeight 8113 InvalidChunks 263 NextBlockHashes 22 OutgoingReceipts 131114 PartialChunks 1116 ProcessedBlockHeights 968698 State SET 865 BlockHeight 1026 BlockMerkleTree 12428 BlockMisc 1636 BlockOrdinal 865 BlockPerHeight 865 BlockRefCount 3460 ChunkExtra 3446 ChunkHashesByHeight 339142 FlatState 3460 FlatStateDeltas 3460 FlatStateMisc 865 HeaderHashesByHeight 3460 IncomingReceipts 865 NextBlockHashes 3442 OutcomeIds 3442 OutgoingReceipts 863 ProcessedBlockHeights 340093 StateChanges 3460 TrieChanges - UPDATE_RC 13517 ReceiptIdToShardId 13526 Receipts 1517322 State 6059 Transactions ``` The output contains one `apply_transactions` for each chunk, with the block hash diff --git a/genesis-tools/genesis-csv-to-json/src/csv_to_json_configs.rs b/genesis-tools/genesis-csv-to-json/src/csv_to_json_configs.rs index 37ecadc8880..b5b1fd18f65 100644 --- a/genesis-tools/genesis-csv-to-json/src/csv_to_json_configs.rs +++ b/genesis-tools/genesis-csv-to-json/src/csv_to_json_configs.rs @@ -1,9 +1,9 @@ use near_chain_configs::{ Genesis, GenesisConfig, BLOCK_PRODUCER_KICKOUT_THRESHOLD, CHUNK_PRODUCER_KICKOUT_THRESHOLD, - EXPECTED_EPOCH_LENGTH, FISHERMEN_THRESHOLD, GAS_PRICE_ADJUSTMENT_RATE, GENESIS_CONFIG_FILENAME, - INITIAL_GAS_LIMIT, MAX_INFLATION_RATE, MIN_GAS_PRICE, NEAR_BASE, NUM_BLOCKS_PER_YEAR, - NUM_BLOCK_PRODUCER_SEATS, PROTOCOL_REWARD_RATE, PROTOCOL_UPGRADE_STAKE_THRESHOLD, - TRANSACTION_VALIDITY_PERIOD, + CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD, EXPECTED_EPOCH_LENGTH, FISHERMEN_THRESHOLD, + GAS_PRICE_ADJUSTMENT_RATE, GENESIS_CONFIG_FILENAME, INITIAL_GAS_LIMIT, MAX_INFLATION_RATE, + MIN_GAS_PRICE, NEAR_BASE, NUM_BLOCKS_PER_YEAR, NUM_BLOCK_PRODUCER_SEATS, PROTOCOL_REWARD_RATE, + PROTOCOL_UPGRADE_STAKE_THRESHOLD, TRANSACTION_VALIDITY_PERIOD, }; use near_primitives::types::{Balance, NumShards, ShardId}; use near_primitives::utils::get_num_seats_per_shard; @@ -70,13 +70,14 @@ pub fn csv_to_json_configs(home: &Path, chain_id: String, tracked_shards: Vec Result<()> { let shard_ids: Vec<_> = self.genesis.config.shard_layout.shard_ids().collect(); + + let state_roots = self.roots.values().cloned().collect(); + let congestion_infos = get_genesis_congestion_infos( + self.epoch_manager.as_ref(), + self.runtime.as_ref(), + &state_roots, + )?; + let genesis_chunks = genesis_chunks( - self.roots.values().cloned().collect(), + state_roots, + congestion_infos, &shard_ids, self.genesis.config.gas_limit, self.genesis.config.genesis_height, @@ -249,17 +260,20 @@ impl GenesisBuilder { store_update.save_block(genesis.clone()); let protocol_version = self.genesis.config.protocol_version; - let congestion_info = Self::get_congestion_control(protocol_version); - for (chunk_header, state_root) in genesis.chunks().iter().zip(self.roots.values()) { + for (chunk_header, &state_root) in genesis.chunks().iter().zip(self.roots.values()) { + let shard_layout = &self.genesis.config.shard_layout; + let shard_id = chunk_header.shard_id(); + let shard_uid = ShardUId::from_shard_id_and_layout(shard_id, &shard_layout); + + let congestion_info = + self.get_congestion_info(protocol_version, &genesis, shard_id, state_root)?; + store_update.save_chunk_extra( genesis.hash(), - &ShardUId::from_shard_id_and_layout( - chunk_header.shard_id(), - &self.genesis.config.shard_layout, - ), + &shard_uid, ChunkExtra::new( self.genesis.config.protocol_version, - state_root, + &state_root, CryptoHash::default(), vec![], 0, @@ -278,13 +292,22 @@ impl GenesisBuilder { Ok(()) } - fn get_congestion_control(protocol_version: ProtocolVersion) -> Option { - if ProtocolFeature::CongestionControl.enabled(protocol_version) { - // TODO(congestion_control) - properly initialize - Some(CongestionInfo::default()) - } else { - None + fn get_congestion_info( + &self, + protocol_version: ProtocolVersion, + genesis: &Block, + shard_id: u64, + state_root: CryptoHash, + ) -> Result> { + if !ProtocolFeature::CongestionControl.enabled(protocol_version) { + return Ok(None); } + let prev_hash = genesis.header().prev_hash(); + let trie = self.runtime.get_trie_for_shard(shard_id, prev_hash, state_root, true)?; + let protocol_config = self.runtime.get_protocol_config(genesis.header().epoch_id())?; + let runtime_config = protocol_config.runtime_config; + let congestion_info = bootstrap_congestion_info(&trie, &runtime_config, shard_id)?; + Ok(Some(congestion_info)) } fn add_additional_account(&mut self, account_id: AccountId) -> Result<()> { diff --git a/genesis-tools/keypair-generator/src/main.rs b/genesis-tools/keypair-generator/src/main.rs index ea281201833..96ebd234087 100644 --- a/genesis-tools/keypair-generator/src/main.rs +++ b/genesis-tools/keypair-generator/src/main.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use clap::{Arg, Command}; -use near_crypto::{InMemorySigner, KeyType, SecretKey, Signer}; +use near_crypto::{InMemorySigner, KeyType, SecretKey}; use nearcore::get_default_home; fn generate_key_to_file(account_id: &str, key: SecretKey, path: &PathBuf) -> std::io::Result<()> { diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index efcf647c37c..928bc9c85dc 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -60,13 +60,14 @@ near-o11y.workspace = true near-telemetry.workspace = true near-test-contracts.workspace = true near-performance-metrics.workspace = true -near-vm-runner = { workspace = true, features = [ "prepare" ] } +near-vm-runner = { workspace = true, features = ["prepare"] } near-wallet-contract.workspace = true nearcore.workspace = true node-runtime.workspace = true testlib.workspace = true [dev-dependencies] +near-primitives = { workspace = true, features = ["clock", "solomon", "rand"] } assert_matches.workspace = true aurora-engine-transactions.workspace = true aurora-engine-types.workspace = true @@ -76,6 +77,7 @@ insta.workspace = true near-undo-block.workspace = true rlp.workspace = true sha3.workspace = true +regex.workspace = true [features] performance_stats = [ @@ -87,6 +89,7 @@ test_features = [ "nearcore/test_features", "near-store/test_features", "near-vm-runner/test_features", + "near-test-contracts/test_features", ] protocol_feature_fix_contract_loading_cost = [ "nearcore/protocol_feature_fix_contract_loading_cost", @@ -161,7 +164,13 @@ statelessnet_protocol = [ "near-chain/statelessnet_protocol", "near-primitives/statelessnet_protocol", ] -sandbox = ["near-chain/sandbox", "node-runtime/sandbox", "near-client/sandbox"] +sandbox = [ + "near-chain/sandbox", + "near-client/sandbox", + "near-o11y/sandbox", + "node-runtime/sandbox", +] no_cache = ["nearcore/no_cache"] calimero_zero_storage = [] new_epoch_sync = ["nearcore/new_epoch_sync"] +testloop = ["near-chain/testloop"] diff --git a/integration-tests/README.md b/integration-tests/README.md new file mode 100644 index 00000000000..a0ebe7f3ccf --- /dev/null +++ b/integration-tests/README.md @@ -0,0 +1,134 @@ +# Integration tests + +## TestLoopEnv + +`TestLoopEnv` is a framework that enables writing multi-node tests for NEAR protocol +components. It simulates an entire blockchain environment within a single test, +allowing for synchronous testing of complex scenarios. + +We recommend to use `TestLoopEnv` for writing multi-node tests and put new +tests into `src/test_loop/tests` folder. This framework is an attempt to +achieve the best of all our previous frameworks, to make tests powerful, +deterministic and easy to write and understand. The doc how it works is on +`core/async/src/test_loop.rs`. + +Here's a step-by-step guide on how to create a test. + +## 1. Build the environment + +Most important parameters are configured through the genesis. +The main part of building the environment involves constructing genesis data, +including the initial state, using `TestGenesisBuilder`: + +```rust +let builder = TestLoopBuilder::new(); + +let initial_balance = 10000 * ONE_NEAR; +let accounts = (0..NUM_ACCOUNTS) + .map(|i| format!("account{}", i).parse().unwrap()) + .collect::>(); + +let mut genesis_builder = TestGenesisBuilder::new(); +genesis_builder + .genesis_time_from_clock(&builder.clock()) + .protocol_version_latest() + .genesis_height(10000) + .epoch_length(EPOCH_LENGTH) + .shard_layout_simple_v1(&["account2", "account4", "account6"]) + // ...more configuration if needed... + +for account in &accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); +} +let genesis = genesis_builder.build(); + +let TestLoopEnv { mut test_loop, datas: node_datas } = + builder.genesis(genesis).clients(client_accounts).build(); +``` + +## 2. Trigger and execute events + +First, query the clients for desired chain information, such as which nodes are +responsible for tracking specific shards. Refer to the `ClientQueries` implementation +for more details on available queries. + +```rust +let first_epoch_tracked_shards = { + let clients = node_datas + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + clients.tracked_shards_for_each_client() +}; +``` + +Perform the actions you want to test, such as money transfers, contract +deployment and execution, specific validator selection, etc. See +`execute_money_transfers` implementation for inspiration. + +```rust +execute_money_transfers(&mut test_loop, &node_datas, &accounts); +``` + +Then, use the `run_until` method to progress the blockchain until a certain +condition is met: + +```rust +let client_handle = node_datas[0].client_sender.actor_handle(); +test_loop.run_until( + |test_loop_data| { + test_loop_data.get(&client_handle).client.chain.head().unwrap().height > 10020 + }, + Duration::seconds(20), +); +``` + +Note: The time here is not actual real-world time. `TestLoopEnv` simulates the clock +to ensure high speed and reproducibility of test results. This allows tests to +run quickly while still accurately modeling time-dependent blockchain behavior. + +## 3. Assert expected outcomes + +Verify that the test produced the expected results. For example, if your test +environment is designed to have nodes change the shards they track, you can +assert this behavior as follows: + +```rust +let clients = node_datas + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); +let later_epoch_tracked_shards = clients.tracked_shards_for_each_client(); +assert_ne!(first_epoch_tracked_shards, later_epoch_tracked_shards); +``` + +After that, properly shut down the test environment: + +```rust +TestLoopEnv { test_loop, datas: node_datas } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +``` + +## Migration + +For historical context, there are multiple existing ways for writing such +tests. The following list presents these methods in order of their development: +* `run_actix(... setup_mock_all_validators(...))` - very powerful, spawns all +actors required for multi-node chain to operate and supports network +communication among them. However, very hard to understand, uses a lot of +resources and almost not maintained. +* pytest - quite powerful as well, spawns actual nodes in Python and uses +exposed RPC handlers to test different behaviour. Quite obsolete as well, +exposed to flakiness. +* different environments spawning clients: `TestEnv`, `TestReshardingEnv`, ... +Good middle ground for testing specific features, but doesn't test actual +network behaviour. Modifications like forcing skipping chunks require a lot +of manual intervention. + +If test became problematic, it is encouraged to migrate it to `TestLoopEnv`. +However, it would be _extremely_ hard to migrate the logic precisely. Instead, +migrate tests only if they make sense to you and their current implementation +became a huge burden. We hope that reproducing such logic in `TestLoopEnv` is +much easier. + +Enjoy! diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index b08014da457..40762bb5a25 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -1,9 +1,8 @@ -pub mod genesis_helpers; -pub mod nearcore_utils; pub mod node; pub mod runtime_utils; -pub mod test_helpers; pub mod user; +#[cfg(test)] +mod test_loop; #[cfg(test)] mod tests; diff --git a/integration-tests/src/node/mod.rs b/integration-tests/src/node/mod.rs index 3b7439323da..e33ae3ffd5f 100644 --- a/integration-tests/src/node/mod.rs +++ b/integration-tests/src/node/mod.rs @@ -6,13 +6,14 @@ pub use crate::node::runtime_node::RuntimeNode; pub use crate::node::thread_node::ThreadNode; use crate::user::{AsyncUser, User}; use near_chain_configs::Genesis; +use near_chain_configs::MutableConfigValue; use near_crypto::{InMemorySigner, Signer}; use near_jsonrpc_primitives::errors::ServerError; use near_primitives::num_rational::Ratio; use near_primitives::state_record::StateRecord; use near_primitives::transaction::SignedTransaction; use near_primitives::types::{AccountId, Balance, NumSeats}; -use near_primitives::validator_signer::InMemoryValidatorSigner; +use near_primitives::validator_signer::ValidatorSigner; use near_primitives::views::AccountView; use near_vm_runner::ContractCode; use nearcore::config::{create_testnet_configs, create_testnet_configs_from_seeds, Config}; @@ -39,7 +40,7 @@ pub enum NodeConfig { /// Should be the default choice for the tests, since it provides the most control through the /// internal access. Thread(NearConfig), - /// A complete noe running in a subprocess. Can be started and stopped, but besides that all + /// A complete node running in a subprocess. Can be started and stopped, but besides that all /// interactions are limited to what is exposed through RPC. Process(NearConfig), } @@ -69,9 +70,9 @@ pub trait Node: Send + Sync { self.user().add_transaction(transaction) } - fn signer(&self) -> Arc; + fn signer(&self) -> Arc; - fn block_signer(&self) -> Arc { + fn block_signer(&self) -> Arc { unimplemented!() } @@ -122,7 +123,7 @@ impl dyn Node { fn near_configs_to_node_configs( configs: Vec, - validator_signers: Vec, + validator_signers: Vec, network_signers: Vec, genesis: Genesis, ) -> Vec { @@ -133,7 +134,10 @@ fn near_configs_to_node_configs( configs[i].clone(), genesis.clone(), (&network_signers[i]).into(), - Some(Arc::new(validator_signers[i].clone())), + MutableConfigValue::new( + Some(Arc::new(validator_signers[i].clone())), + "validator_signer", + ), ) .unwrap(), )) diff --git a/integration-tests/src/node/process_node.rs b/integration-tests/src/node/process_node.rs index 85d21d12d9b..f1420eaf74c 100644 --- a/integration-tests/src/node/process_node.rs +++ b/integration-tests/src/node/process_node.rs @@ -29,7 +29,8 @@ pub struct ProcessNode { pub work_dir: PathBuf, pub config: NearConfig, pub state: ProcessNodeState, - pub signer: Arc, + pub signer: Arc, + account_id: AccountId, } impl Node for ProcessNode { @@ -38,7 +39,7 @@ impl Node for ProcessNode { } fn account_id(&self) -> Option { - self.config.validator_signer.as_ref().map(|vs| vs.validator_id().clone()) + self.config.validator_signer.get().map(|vs| vs.validator_id().clone()) } fn start(&mut self) { @@ -78,7 +79,7 @@ impl Node for ProcessNode { } } - fn signer(&self) -> Arc { + fn signer(&self) -> Arc { self.signer.clone() } @@ -90,8 +91,11 @@ impl Node for ProcessNode { } fn user(&self) -> Box { - let account_id = self.signer.account_id.clone(); - Box::new(RpcUser::new(&self.config.rpc_addr().unwrap(), account_id, self.signer.clone())) + Box::new(RpcUser::new( + &self.config.rpc_addr().unwrap(), + self.account_id.clone(), + self.signer.clone(), + )) } fn as_process_ref(&self) -> &ProcessNode { @@ -108,12 +112,13 @@ impl ProcessNode { pub fn new(config: NearConfig) -> ProcessNode { let mut rng = rand::thread_rng(); let work_dir = env::temp_dir().join(format!("process_node_{}", rng.gen::())); - let signer = Arc::new(InMemorySigner::from_seed( - config.validator_signer.as_ref().unwrap().validator_id().clone(), - KeyType::ED25519, - config.validator_signer.as_ref().unwrap().validator_id().as_ref(), - )); - let result = ProcessNode { config, work_dir, state: ProcessNodeState::Stopped, signer }; + let account_id = config.validator_signer.get().unwrap().validator_id().clone(); + let signer = Arc::new( + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()) + .into(), + ); + let result = + ProcessNode { config, work_dir, state: ProcessNodeState::Stopped, signer, account_id }; result.reset_storage(); result } diff --git a/integration-tests/src/node/runtime_node.rs b/integration-tests/src/node/runtime_node.rs index f3f99a32a3f..d024cef7890 100644 --- a/integration-tests/src/node/runtime_node.rs +++ b/integration-tests/src/node/runtime_node.rs @@ -13,8 +13,9 @@ use crate::user::{RuntimeUser, User}; pub struct RuntimeNode { pub client: Arc>, - pub signer: Arc, + pub signer: Arc, pub genesis: Genesis, + account_id: AccountId, } impl RuntimeNode { @@ -31,11 +32,10 @@ impl RuntimeNode { genesis: Genesis, runtime_config: RuntimeConfig, ) -> Self { - let signer = Arc::new(InMemorySigner::from_seed( - account_id.clone(), - KeyType::ED25519, - account_id.as_ref(), - )); + let signer = Arc::new( + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()) + .into(), + ); let (runtime, tries, root) = get_runtime_and_trie_from_genesis(&genesis); let client = Arc::new(RwLock::new(MockClient { runtime, @@ -44,13 +44,28 @@ impl RuntimeNode { epoch_length: genesis.config.epoch_length, runtime_config, })); - RuntimeNode { signer, client, genesis } + RuntimeNode { signer, client, genesis, account_id: account_id.clone() } } pub fn new_from_genesis(account_id: &AccountId, genesis: Genesis) -> Self { Self::new_from_genesis_and_config(account_id, genesis, RuntimeConfig::test()) } + /// Same as `RuntimeNode::new`, but allows to modify the RuntimeConfig. + pub fn new_with_modified_config( + account_id: &AccountId, + modify_config: impl FnOnce(&mut RuntimeConfig), + ) -> Self { + let mut genesis = Genesis::test(vec![alice_account(), bob_account(), carol_account()], 3); + add_test_contract(&mut genesis, &alice_account()); + add_test_contract(&mut genesis, &bob_account()); + add_test_contract(&mut genesis, &carol_account()); + + let mut runtime_config = RuntimeConfig::test(); + modify_config(&mut runtime_config); + Self::new_from_genesis_and_config(account_id, genesis, runtime_config) + } + pub fn free(account_id: &AccountId) -> Self { let mut genesis = Genesis::test(vec![alice_account(), bob_account(), "carol.near".parse().unwrap()], 3); @@ -65,18 +80,18 @@ impl Node for RuntimeNode { } fn account_id(&self) -> Option { - Some(self.signer.account_id.clone()) + Some(self.account_id.clone()) } fn start(&mut self) {} fn kill(&mut self) {} - fn signer(&self) -> Arc { + fn signer(&self) -> Arc { self.signer.clone() } - fn block_signer(&self) -> Arc { + fn block_signer(&self) -> Arc { self.signer.clone() } @@ -86,7 +101,7 @@ impl Node for RuntimeNode { fn user(&self) -> Box { Box::new(RuntimeUser::new( - self.signer.account_id.clone(), + self.account_id.clone(), self.signer.clone(), self.client.clone(), )) diff --git a/integration-tests/src/node/thread_node.rs b/integration-tests/src/node/thread_node.rs index 1435aa1ca0b..d3d5d631abc 100644 --- a/integration-tests/src/node/thread_node.rs +++ b/integration-tests/src/node/thread_node.rs @@ -19,8 +19,9 @@ pub enum ThreadNodeState { pub struct ThreadNode { pub config: NearConfig, pub state: ThreadNodeState, - pub signer: Arc, + pub signer: Arc, pub dir: tempfile::TempDir, + account_id: AccountId, } fn start_thread(config: NearConfig, path: PathBuf) -> ShutdownableThread { @@ -35,7 +36,7 @@ impl Node for ThreadNode { } fn account_id(&self) -> Option { - self.config.validator_signer.as_ref().map(|vs| vs.validator_id().clone()) + self.config.validator_signer.get().map(|vs| vs.validator_id().clone()) } fn start(&mut self) { @@ -54,7 +55,7 @@ impl Node for ThreadNode { } } - fn signer(&self) -> Arc { + fn signer(&self) -> Arc { self.signer.clone() } @@ -66,8 +67,11 @@ impl Node for ThreadNode { } fn user(&self) -> Box { - let account_id = self.signer.account_id.clone(); - Box::new(RpcUser::new(&self.config.rpc_addr().unwrap(), account_id, self.signer.clone())) + Box::new(RpcUser::new( + &self.config.rpc_addr().unwrap(), + self.account_id.clone(), + self.signer.clone(), + )) } fn as_thread_ref(&self) -> &ThreadNode { @@ -82,16 +86,15 @@ impl Node for ThreadNode { impl ThreadNode { /// Side effects: create storage, open database, lock database pub fn new(config: NearConfig) -> ThreadNode { - let signer = Arc::new(InMemorySigner::from_seed( - config.validator_signer.as_ref().unwrap().validator_id().clone(), - KeyType::ED25519, - config.validator_signer.as_ref().unwrap().validator_id().as_ref(), - )); + let account_id = config.validator_signer.get().unwrap().validator_id().clone(); + let signer = + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()); ThreadNode { config, state: ThreadNodeState::Stopped, - signer, + signer: Arc::new(signer.into()), dir: tempfile::Builder::new().prefix("thread_node").tempdir().unwrap(), + account_id, } } } diff --git a/integration-tests/src/runtime_utils.rs b/integration-tests/src/runtime_utils.rs index acf239a8099..214dc62046a 100644 --- a/integration-tests/src/runtime_utils.rs +++ b/integration-tests/src/runtime_utils.rs @@ -1,3 +1,6 @@ +//! This file is mainly used in integration-tests/src/tests/runtime/state_viewer.rs +//! Additionally used in integration-tests/src/node/runtime_node.rs +//! use std::collections::HashSet; use near_chain_configs::Genesis; diff --git a/integration-tests/src/test_helpers.rs b/integration-tests/src/test_helpers.rs deleted file mode 100644 index d2a5a90d423..00000000000 --- a/integration-tests/src/test_helpers.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::node::Node; -use once_cell::sync::Lazy; -use std::process::Output; -use std::sync::{Arc, Mutex, RwLock}; -use std::thread; -use std::time::Duration; - -static HEAVY_TESTS_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); - -pub fn heavy_test(f: F) -where - F: FnOnce(), -{ - let _guard = HEAVY_TESTS_LOCK.lock(); - f(); -} - -pub fn check_result(output: Output) -> Result { - let mut result = String::from_utf8_lossy(output.stdout.as_slice()); - if !output.status.success() { - if result.is_empty() { - result = String::from_utf8_lossy(output.stderr.as_slice()); - } - return Err(result.into_owned()); - } - Ok(result.into_owned()) -} - -pub fn wait(mut f: F, check_interval_ms: u64, max_wait_ms: u64) -where - F: FnMut() -> bool, -{ - let mut ms_slept = 0; - while !f() { - thread::sleep(Duration::from_millis(check_interval_ms)); - ms_slept += check_interval_ms; - if ms_slept > max_wait_ms { - println!("BBBB Slept {}; max_wait_ms {}", ms_slept, max_wait_ms); - panic!("Timed out waiting for the condition"); - } - } -} - -/// TODO it makes sense to have three types of wait checks: -/// Wait until sufficient number of nodes is caught up (> 2/3). This can be checked by looking at the block heights and verifying that the blocks are produced; -/// Wait until a certain node is caught up and participating in a consensus. Check first-layer BLS signatures; -/// Wait until all nodes are more-or-less caught up. Check that the max_height - min_height < threshold; -/// -pub fn wait_for_catchup(nodes: &[Arc>]) { - wait( - || { - let tips: Vec<_> = nodes - .iter() - .filter(|node| node.read().unwrap().is_running()) - .map(|node| node.read().unwrap().user().get_best_height()) - .collect(); - tips.iter().min() == tips.iter().max() - }, - 1000, - 10000, - ); -} diff --git a/integration-tests/src/test_loop/builder.rs b/integration-tests/src/test_loop/builder.rs new file mode 100644 index 00000000000..cd56c0096d7 --- /dev/null +++ b/integration-tests/src/test_loop/builder.rs @@ -0,0 +1,380 @@ +use std::sync::{Arc, Mutex, RwLock}; + +use near_async::futures::FutureSpawner; +use near_async::messaging::{noop, IntoMultiSender, IntoSender, LateBoundSender}; +use near_async::test_loop::sender::TestLoopSender; +use near_async::test_loop::TestLoopV2; +use near_async::time::{Clock, Duration}; +use near_chain::chunks_store::ReadOnlyChunksStore; +use near_chain::runtime::NightshadeRuntime; +use near_chain::state_snapshot_actor::{ + get_delete_snapshot_callback, get_make_snapshot_callback, SnapshotCallbacks, StateSnapshotActor, +}; +use near_chain::types::RuntimeAdapter; +use near_chain::ChainGenesis; +use near_chain_configs::{ + ClientConfig, DumpConfig, ExternalStorageConfig, ExternalStorageLocation, Genesis, + MutableConfigValue, StateSyncConfig, SyncConfig, +}; +use near_chunks::shards_manager_actor::ShardsManagerActor; +use near_client::client_actor::ClientActorInner; +use near_client::gc_actor::GCActor; +use near_client::sync_jobs_actor::SyncJobsActor; +use near_client::test_utils::test_loop::test_loop_sync_actor_maker; +use near_client::{Client, PartialWitnessActor, SyncAdapter}; +use near_epoch_manager::shard_tracker::{ShardTracker, TrackedConfig}; +use near_epoch_manager::{EpochManager, EpochManagerAdapter}; +use near_network::test_loop::TestLoopPeerManagerActor; +use near_primitives::network::PeerId; +use near_primitives::test_utils::create_test_signer; +use near_primitives::types::AccountId; +use near_store::config::StateSnapshotType; +use near_store::genesis::initialize_genesis_state; +use near_store::test_utils::create_test_store; +use near_store::{StoreConfig, TrieConfig}; +use near_vm_runner::{ContractRuntimeCache, FilesystemContractRuntimeCache}; +use nearcore::state_sync::StateSyncDumper; +use tempfile::TempDir; + +use super::env::{ClientToShardsManagerSender, TestData, TestLoopChunksStorage, TestLoopEnv}; +use super::utils::network::partial_encoded_chunks_dropper; + +pub struct TestLoopBuilder { + test_loop: TestLoopV2, + genesis: Option, + clients: Vec, + /// Will store all chunks produced within the test loop. + chunks_storage: Arc>, + /// Whether test loop should drop all chunks validated by the given account. + drop_chunks_validated_by: Option, + gc: bool, +} + +impl TestLoopBuilder { + pub fn new() -> Self { + Self { + test_loop: TestLoopV2::new(), + genesis: None, + clients: vec![], + chunks_storage: Default::default(), + drop_chunks_validated_by: None, + gc: true, + } + } + + /// Get the clock for the test loop. + pub fn clock(&self) -> Clock { + self.test_loop.clock() + } + + /// Set the genesis configuration for the test loop. + pub fn genesis(mut self, genesis: Genesis) -> Self { + self.genesis = Some(genesis); + self + } + + /// Set the clients for the test loop. + pub fn clients(mut self, clients: Vec) -> Self { + self.clients = clients; + self + } + + pub fn drop_chunks_validated_by(mut self, account_id: &str) -> Self { + self.drop_chunks_validated_by = Some(account_id.parse().unwrap()); + self + } + + /// Build the test loop environment. + pub fn build(self) -> TestLoopEnv { + self.ensure_genesis().ensure_clients().build_impl() + } + + fn ensure_genesis(self) -> Self { + assert!(self.genesis.is_some(), "Genesis must be provided to the test loop"); + self + } + + fn ensure_clients(self) -> Self { + assert!(!self.clients.is_empty(), "Clients must be provided to the test loop"); + self + } + + fn build_impl(mut self) -> TestLoopEnv { + let mut datas = Vec::new(); + let mut network_adapters = Vec::new(); + let mut epoch_manager_adapters = Vec::new(); + let tempdir = tempfile::tempdir().unwrap(); + for idx in 0..self.clients.len() { + let (data, network_adapter, epoch_manager_adapter) = self.setup_client(idx, &tempdir); + datas.push(data); + network_adapters.push(network_adapter); + epoch_manager_adapters.push(epoch_manager_adapter); + } + self.setup_network(&datas, &network_adapters, &epoch_manager_adapters); + + let env = TestLoopEnv { test_loop: self.test_loop, datas, tempdir }; + env.warmup() + } + + fn setup_client( + &mut self, + idx: usize, + tempdir: &TempDir, + ) -> ( + TestData, + Arc>>, + Arc, + ) { + let client_adapter = LateBoundSender::new(); + let network_adapter = LateBoundSender::new(); + let state_snapshot_adapter = LateBoundSender::new(); + let partial_witness_adapter = LateBoundSender::new(); + let sync_jobs_adapter = LateBoundSender::new(); + + let genesis = self.genesis.clone().unwrap(); + let mut client_config = ClientConfig::test(true, 600, 2000, 4, false, true, false, false); + client_config.max_block_wait_delay = Duration::seconds(6); + client_config.state_sync_enabled = true; + client_config.state_sync_timeout = Duration::milliseconds(100); + let external_storage_location = + ExternalStorageLocation::Filesystem { root_dir: tempdir.path().join("state_sync") }; + client_config.state_sync = StateSyncConfig { + dump: Some(DumpConfig { + iteration_delay: Some(Duration::seconds(1)), + location: external_storage_location.clone(), + credentials_file: None, + restart_dump_for_shards: None, + }), + sync: SyncConfig::ExternalStorage(ExternalStorageConfig { + location: external_storage_location, + num_concurrent_requests: 1, + num_concurrent_requests_during_catchup: 1, + }), + }; + + // Configure tracked shards. + // * single shard tracking for validators + // * all shard tracking for RPCs + let num_block_producer = genesis.config.num_block_producer_seats; + let num_chunk_producer = genesis.config.num_chunk_producer_seats; + let num_chunk_validator = genesis.config.num_chunk_validator_seats; + let validator_num = + num_block_producer.max(num_chunk_producer).max(num_chunk_validator) as usize; + if idx < validator_num { + client_config.tracked_shards = Vec::new(); + } else { + client_config.tracked_shards = vec![666]; + } + + let homedir = tempdir.path().join(format!("{}", idx)); + std::fs::create_dir_all(&homedir).expect("Unable to create homedir"); + + let store_config = StoreConfig { + path: Some(homedir.clone()), + load_mem_tries_for_tracked_shards: true, + ..Default::default() + }; + let store = create_test_store(); + initialize_genesis_state(store.clone(), &genesis, None); + + let sync_jobs_actor = SyncJobsActor::new(client_adapter.as_multi_sender()); + let chain_genesis = ChainGenesis::new(&genesis.config); + let epoch_manager = EpochManager::new_arc_handle(store.clone(), &genesis.config); + let shard_tracker = + ShardTracker::new(TrackedConfig::from_config(&client_config), epoch_manager.clone()); + + let state_sync_adapter = Arc::new(RwLock::new(SyncAdapter::new( + client_adapter.as_sender(), + network_adapter.as_sender(), + test_loop_sync_actor_maker(idx, self.test_loop.sender().for_index(idx)), + ))); + let contract_cache = FilesystemContractRuntimeCache::new(&homedir, None::<&str>) + .expect("filesystem contract cache") + .handle(); + let runtime_adapter = NightshadeRuntime::test_with_trie_config( + &homedir, + store.clone(), + contract_cache, + &genesis.config, + epoch_manager.clone(), + None, + TrieConfig::from_store_config(&store_config), + StateSnapshotType::EveryEpoch, + ); + + let state_snapshot = StateSnapshotActor::new( + runtime_adapter.get_flat_storage_manager(), + network_adapter.as_multi_sender(), + runtime_adapter.get_tries(), + state_snapshot_adapter.as_multi_sender(), + ); + + let delete_snapshot_callback = + get_delete_snapshot_callback(state_snapshot_adapter.as_multi_sender()); + let make_snapshot_callback = get_make_snapshot_callback( + state_snapshot_adapter.as_multi_sender(), + runtime_adapter.get_flat_storage_manager(), + ); + let snapshot_callbacks = + SnapshotCallbacks { make_snapshot_callback, delete_snapshot_callback }; + + let validator_signer = MutableConfigValue::new( + Some(Arc::new(create_test_signer(self.clients[idx].as_str()))), + "validator_signer", + ); + + let shards_manager_adapter = LateBoundSender::new(); + let client_to_shards_manager_sender = Arc::new(ClientToShardsManagerSender { + sender: shards_manager_adapter.clone(), + chunks_storage: self.chunks_storage.clone(), + }); + + let client = Client::new( + self.test_loop.clock(), + client_config.clone(), + chain_genesis.clone(), + epoch_manager.clone(), + shard_tracker.clone(), + state_sync_adapter, + runtime_adapter.clone(), + network_adapter.as_multi_sender(), + client_to_shards_manager_sender.as_sender(), + validator_signer.clone(), + true, + [0; 32], + Some(snapshot_callbacks), + Arc::new(self.test_loop.async_computation_spawner(|_| Duration::milliseconds(80))), + partial_witness_adapter.as_multi_sender(), + ) + .unwrap(); + + let shards_manager = ShardsManagerActor::new( + self.test_loop.clock(), + validator_signer.clone(), + epoch_manager.clone(), + shard_tracker.clone(), + network_adapter.as_sender(), + client_adapter.as_sender(), + ReadOnlyChunksStore::new(store.clone()), + client.chain.head().unwrap(), + client.chain.header_head().unwrap(), + Duration::milliseconds(100), + ); + + let client_actor = ClientActorInner::new( + self.test_loop.clock(), + client, + client_adapter.as_multi_sender(), + client_config.clone(), + PeerId::random(), + network_adapter.as_multi_sender(), + noop().into_sender(), + None, + Default::default(), + None, + sync_jobs_adapter.as_multi_sender(), + Box::new(self.test_loop.future_spawner()), + ) + .unwrap(); + + let partial_witness_actor = PartialWitnessActor::new( + self.test_loop.clock(), + network_adapter.as_multi_sender(), + client_adapter.as_multi_sender(), + validator_signer.clone(), + epoch_manager.clone(), + store, + ); + + if self.gc { + let gc_actor = GCActor::new( + runtime_adapter.store().clone(), + chain_genesis.height, + runtime_adapter.clone(), + epoch_manager.clone(), + client_config.gc.clone(), + client_config.archive, + ); + // We don't send messages to `GCActor` so adapter is not needed. + self.test_loop.register_actor_for_index(idx, gc_actor, None); + } + + let future_spawner = self.test_loop.future_spawner(); + let state_sync_dumper = StateSyncDumper { + clock: self.test_loop.clock(), + client_config, + chain_genesis, + epoch_manager: epoch_manager.clone(), + shard_tracker, + runtime: runtime_adapter, + validator: validator_signer, + dump_future_runner: Box::new(move |future| { + future_spawner.spawn_boxed("state_sync_dumper", future); + Box::new(|| {}) + }), + handle: None, + }; + let state_sync_dumper_handle = self.test_loop.data.register_data(state_sync_dumper); + + let client_sender = + self.test_loop.register_actor_for_index(idx, client_actor, Some(client_adapter)); + let shards_manager_sender = self.test_loop.register_actor_for_index( + idx, + shards_manager, + Some(shards_manager_adapter), + ); + let partial_witness_sender = self.test_loop.register_actor_for_index( + idx, + partial_witness_actor, + Some(partial_witness_adapter), + ); + self.test_loop.register_actor_for_index(idx, sync_jobs_actor, Some(sync_jobs_adapter)); + self.test_loop.register_actor_for_index(idx, state_snapshot, Some(state_snapshot_adapter)); + + // State sync dumper is not an Actor, handle starting separately. + let state_sync_dumper_handle_clone = state_sync_dumper_handle.clone(); + self.test_loop.send_adhoc_event( + "start_state_sync_dumper".to_owned(), + move |test_loop_data| { + test_loop_data.get_mut(&state_sync_dumper_handle_clone).start().unwrap(); + }, + ); + + let data = TestData { + account_id: self.clients[idx].clone(), + client_sender, + shards_manager_sender, + partial_witness_sender, + state_sync_dumper_handle, + }; + (data, network_adapter, epoch_manager) + } + + // TODO: we assume that all `Vec`s have the same length, consider + // joining them into one structure. + fn setup_network( + &mut self, + datas: &Vec, + network_adapters: &Vec>>>, + epoch_manager_adapters: &Vec>, + ) { + for (idx, data) in datas.iter().enumerate() { + let mut peer_manager_actor = + TestLoopPeerManagerActor::new(self.test_loop.clock(), &data.account_id, datas); + + if let Some(account_id) = &self.drop_chunks_validated_by { + peer_manager_actor.register_override_handler(partial_encoded_chunks_dropper( + self.chunks_storage.clone(), + epoch_manager_adapters[idx].clone(), + account_id.clone(), + )); + } + + self.test_loop.register_actor_for_index( + idx, + peer_manager_actor, + Some(network_adapters[idx].clone()), + ); + } + } +} diff --git a/integration-tests/src/test_loop/env.rs b/integration-tests/src/test_loop/env.rs new file mode 100644 index 00000000000..7f6f5c3bce7 --- /dev/null +++ b/integration-tests/src/test_loop/env.rs @@ -0,0 +1,165 @@ +use near_async::messaging::{CanSend, IntoMultiSender, IntoSender, LateBoundSender, Sender}; +use near_async::test_loop::data::{TestLoopData, TestLoopDataHandle}; +use near_async::test_loop::sender::TestLoopSender; +use near_async::test_loop::TestLoopV2; +use near_async::time::Duration; +use near_chunks::adapter::ShardsManagerRequestFromClient; +use near_chunks::shards_manager_actor::ShardsManagerActor; +use near_client::client_actor::ClientActorInner; +use near_client::PartialWitnessActor; +use near_network::shards_manager::ShardsManagerRequestFromNetwork; +use near_network::state_witness::PartialWitnessSenderForNetwork; +use near_network::test_loop::ClientSenderForTestLoopNetwork; +use near_primitives::sharding::{ChunkHash, ShardChunkHeader}; +use near_primitives::types::AccountId; +use near_primitives_core::types::BlockHeight; +use nearcore::state_sync::StateSyncDumper; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use tempfile::TempDir; + +const NETWORK_DELAY: Duration = Duration::milliseconds(10); + +pub struct TestLoopEnv { + pub test_loop: TestLoopV2, + pub datas: Vec, + pub tempdir: TempDir, +} + +impl TestLoopEnv { + /// Reach block with height `genesis_height + 3`. Check that it can be done + /// within 5 seconds. Ensure that all clients have block + /// `genesis_height + 2` and it has all chunks. + /// Needed because for smaller heights blocks may not get all chunks and/or + /// approvals. + pub fn warmup(self) -> Self { + let Self { mut test_loop, datas, tempdir } = self; + + let client_handle = datas[0].client_sender.actor_handle(); + let genesis_height = test_loop.data.get(&client_handle).client.chain.genesis().height(); + test_loop.run_until( + |test_loop_data| { + let client_actor = test_loop_data.get(&client_handle); + client_actor.client.chain.head().unwrap().height == genesis_height + 3 + }, + Duration::seconds(5), + ); + for idx in 0..datas.len() { + let client_handle = datas[idx].client_sender.actor_handle(); + let event = move |test_loop_data: &mut TestLoopData| { + let client_actor = test_loop_data.get(&client_handle); + let block = + client_actor.client.chain.get_block_by_height(genesis_height + 2).unwrap(); + let num_shards = block.header().chunk_mask().len(); + assert_eq!(block.header().chunk_mask(), vec![true; num_shards]); + }; + test_loop.send_adhoc_event("assertions".to_owned(), Box::new(event)); + } + test_loop.run_instant(); + + Self { test_loop, datas, tempdir } + } + + /// Used to finish off remaining events that are still in the loop. This can be necessary if the + /// destructor of some components wait for certain condition to become true. Otherwise, the + /// destructors may end up waiting forever. This also helps avoid a panic when destructing + /// TestLoop itself, as it asserts that all events have been handled. + pub fn shutdown_and_drain_remaining_events(mut self, timeout: Duration) { + // State sync dumper is not an Actor, handle stopping separately. + for node_data in self.datas { + self.test_loop.data.get_mut(&node_data.state_sync_dumper_handle).stop(); + } + + self.test_loop.shutdown_and_drain_remaining_events(timeout); + } +} + +/// Stores all chunks ever observed on chain. Determines if a chunk can be +/// dropped within a test loop. +/// +/// Needed to intercept network messages storing chunk hash only, while +/// interception requires more detailed information like shard id. +#[derive(Default)] +pub struct TestLoopChunksStorage { + /// Mapping from chunk hashes to headers. + storage: HashMap, + /// Minimal chunk height ever observed. + min_chunk_height: Option, +} + +impl TestLoopChunksStorage { + pub fn insert(&mut self, chunk_header: ShardChunkHeader) { + let chunk_height = chunk_header.height_created(); + self.min_chunk_height = Some( + self.min_chunk_height + .map_or(chunk_height, |current_height| current_height.min(chunk_height)), + ); + self.storage.insert(chunk_header.chunk_hash(), chunk_header); + } + + pub fn get(&self, chunk_hash: &ChunkHash) -> Option<&ShardChunkHeader> { + self.storage.get(chunk_hash) + } + + /// If chunk height is too low, don't drop chunk, allow the chain to warm + /// up. + pub fn can_drop_chunk(&self, chunk_header: &ShardChunkHeader) -> bool { + self.min_chunk_height + .is_some_and(|min_height| chunk_header.height_created() >= min_height + 3) + } +} + +/// Custom implementation of `Sender` for messages from `Client` to +/// `ShardsManagerActor` that allows to intercept all messages indicating +/// any chunk production and storing all chunks. +pub struct ClientToShardsManagerSender { + pub sender: Arc>>, + /// Storage of chunks shared between all test loop nodes. + pub chunks_storage: Arc>, +} + +impl CanSend for ClientToShardsManagerSender { + fn send(&self, message: ShardsManagerRequestFromClient) { + // `DistributeEncodedChunk` indicates that a certain chunk was produced. + if let ShardsManagerRequestFromClient::DistributeEncodedChunk { partial_chunk, .. } = + &message + { + let mut chunks_storage = self.chunks_storage.lock().unwrap(); + chunks_storage.insert(partial_chunk.cloned_header()); + } + // After maybe storing the chunk, send the message as usual. + self.sender.send(message); + } +} + +pub struct TestData { + pub account_id: AccountId, + pub client_sender: TestLoopSender, + pub shards_manager_sender: TestLoopSender, + pub partial_witness_sender: TestLoopSender, + pub state_sync_dumper_handle: TestLoopDataHandle, +} + +impl From<&TestData> for AccountId { + fn from(data: &TestData) -> AccountId { + data.account_id.clone() + } +} + +impl From<&TestData> for ClientSenderForTestLoopNetwork { + fn from(data: &TestData) -> ClientSenderForTestLoopNetwork { + data.client_sender.clone().with_delay(NETWORK_DELAY).into_multi_sender() + } +} + +impl From<&TestData> for PartialWitnessSenderForNetwork { + fn from(data: &TestData) -> PartialWitnessSenderForNetwork { + data.partial_witness_sender.clone().with_delay(NETWORK_DELAY).into_multi_sender() + } +} + +impl From<&TestData> for Sender { + fn from(data: &TestData) -> Sender { + data.shards_manager_sender.clone().with_delay(NETWORK_DELAY).into_sender() + } +} diff --git a/integration-tests/src/test_loop/mod.rs b/integration-tests/src/test_loop/mod.rs new file mode 100644 index 00000000000..08e5d7ad057 --- /dev/null +++ b/integration-tests/src/test_loop/mod.rs @@ -0,0 +1,4 @@ +mod builder; +mod env; +mod tests; +mod utils; diff --git a/integration-tests/src/test_loop/tests/chunk_validator_kickout.rs b/integration-tests/src/test_loop/tests/chunk_validator_kickout.rs new file mode 100644 index 00000000000..9fe1d4acfc7 --- /dev/null +++ b/integration-tests/src/test_loop/tests/chunk_validator_kickout.rs @@ -0,0 +1,122 @@ +use crate::test_loop::builder::TestLoopBuilder; +use crate::test_loop::env::TestLoopEnv; +use crate::test_loop::utils::ONE_NEAR; +use itertools::Itertools; +use near_async::test_loop::data::TestLoopData; +use near_async::time::Duration; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_client::Client; +use near_o11y::testonly::init_test_logger; +use near_primitives::types::AccountId; +use near_primitives_core::checked_feature; +use near_primitives_core::version::PROTOCOL_VERSION; +use std::string::ToString; + +fn run_test_chunk_validator_kickout(select_chunk_validator_only: bool) { + if !checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { + println!("Test not applicable without StatelessValidation enabled"); + return; + } + + init_test_logger(); + let builder = TestLoopBuilder::new(); + + let initial_balance = 10000 * ONE_NEAR; + let epoch_length = 10; + let accounts = + (0..8).map(|i| format!("account{}", i).parse().unwrap()).collect::>(); + let clients = accounts.iter().cloned().collect_vec(); + let accounts_str = accounts.iter().map(|a| a.as_str()).collect_vec(); + let (block_and_chunk_producers, chunk_validators_only) = accounts_str.split_at(6); + + // Select the account to kick out. + // Only chunk validator-only node can be kicked out for low endorsement + // stats. + let account_id = if select_chunk_validator_only { + chunk_validators_only[0] + } else { + block_and_chunk_producers[3] + }; + let expect_kickout = select_chunk_validator_only; + + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&builder.clock()) + .shard_layout_simple_v1(&["account2", "account4", "account6"]) + .epoch_length(epoch_length) + // Select 6 block&chunk producers and 2 chunk validators. + .validators_desired_roles(block_and_chunk_producers, chunk_validators_only) + // Set up config to kick out only chunk validators for low performance. + .kickouts_for_chunk_validators_only() + // Target giving one mandate to each chunk validator, which results in + // every chunk validator validating only one shard in most cases. + .target_validator_mandates_per_shard(1); + for account in &accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); + } + let genesis = genesis_builder.build(); + + let TestLoopEnv { mut test_loop, datas: node_datas, tempdir } = builder + .genesis(genesis) + .clients(clients) + // Drop only chunks validated by `account_id`. + // By how our endorsement stats are computed, this will count as this + // validator validating zero chunks. + .drop_chunks_validated_by(account_id) + .build(); + + // Run chain until our targeted chunk validator is (not) kicked out. + let client_handle = node_datas[0].client_sender.actor_handle(); + let initial_validators = get_epoch_all_validators(&test_loop.data.get(&client_handle).client); + assert_eq!(initial_validators.len(), 8); + assert!(initial_validators.contains(&account_id.to_string())); + let success_condition = |test_loop_data: &mut TestLoopData| -> bool { + let client = &test_loop_data.get(&client_handle).client; + let validators = get_epoch_all_validators(client); + let tip = client.chain.head().unwrap(); + let epoch_height = + client.epoch_manager.get_epoch_height_from_prev_block(&tip.prev_block_hash).unwrap(); + + if expect_kickout { + assert!(epoch_height < 4); + return if validators.len() == 7 { + assert!(!validators.contains(&account_id.to_string())); + true + } else { + false + }; + } else { + assert_eq!(validators.len(), 8, "No kickouts are expected"); + epoch_height >= 4 + } + }; + + test_loop.run_until( + success_condition, + // Timeout at producing 5 epochs, approximately. + Duration::seconds((5 * epoch_length) as i64), + ); + + TestLoopEnv { test_loop, datas: node_datas, tempdir } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +} + +/// Get all validator account names for the latest epoch. +fn get_epoch_all_validators(client: &Client) -> Vec { + let tip = client.chain.head().unwrap(); + let epoch_id = tip.epoch_id; + let all_validators = client.epoch_manager.get_epoch_all_validators(&epoch_id).unwrap(); + all_validators.into_iter().map(|vs| vs.account_id().to_string()).collect() +} + +/// Checks that chunk validator with low endorsement stats is kicked out. +#[test] +fn test_chunk_validator_kicked_out() { + run_test_chunk_validator_kickout(true); +} + +/// Checks that block producer with low chunk endorsement stats is not kicked out. +#[test] +fn test_block_producer_not_kicked_out() { + run_test_chunk_validator_kickout(false); +} diff --git a/integration-tests/src/test_loop/tests/congestion_control.rs b/integration-tests/src/test_loop/tests/congestion_control.rs new file mode 100644 index 00000000000..0c37e2c4d1d --- /dev/null +++ b/integration-tests/src/test_loop/tests/congestion_control.rs @@ -0,0 +1,177 @@ +use core::panic; + +use assert_matches::assert_matches; +use itertools::Itertools; +use near_async::test_loop::data::{TestLoopData, TestLoopDataHandle}; +use near_async::test_loop::TestLoopV2; +use near_async::time::Duration; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_client::client_actor::ClientActorInner; +use near_client::Client; +use near_o11y::testonly::init_test_logger; +use near_primitives::hash::CryptoHash; +use near_primitives::types::{AccountId, BlockHeight}; +use near_primitives::views::FinalExecutionStatus; + +use crate::test_loop::builder::TestLoopBuilder; +use crate::test_loop::env::{TestData, TestLoopEnv}; +use crate::test_loop::utils::transactions::{call_contract, deploy_contract, get_node_data}; +use crate::test_loop::utils::ONE_NEAR; + +const NUM_PRODUCERS: usize = 2; +const NUM_VALIDATORS: usize = 2; +const NUM_RPC: usize = 1; +const NUM_CLIENTS: usize = NUM_PRODUCERS + NUM_VALIDATORS + NUM_RPC; + +/// A very simple test that exercises congestion control in the typical setup +/// with producers, validators, rpc nodes, single shard tracking and state sync. +#[cfg_attr(not(feature = "test_features"), ignore)] +#[test] +fn test_congestion_control_simple() { + init_test_logger(); + + // Test setup + + let contract_id: AccountId = "000".parse().unwrap(); + let mut accounts = (0..100).map(make_account).collect_vec(); + accounts.push(contract_id.clone()); + + let (env, rpc_id) = setup(&accounts); + let TestLoopEnv { mut test_loop, datas: node_datas, tempdir } = env; + + // Test + + // Deploy the contract. + do_deploy_contract(&mut test_loop, &node_datas, &rpc_id, &contract_id); + + // Call the contract from all accounts. + do_call_contract(&mut test_loop, &node_datas, &rpc_id, &contract_id, &accounts); + + // Make sure the chain progresses for several epochs. + let client_handle = node_datas[0].client_sender.actor_handle(); + test_loop.run_until( + |test_loop_data: &mut TestLoopData| height_condition(test_loop_data, &client_handle, 10050), + Duration::seconds(100), + ); + + // Give the test a chance to finish off remaining events in the event loop, which can + // be important for properly shutting down the nodes. + TestLoopEnv { test_loop, datas: node_datas, tempdir } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +} + +fn setup(accounts: &Vec) -> (TestLoopEnv, AccountId) { + let initial_balance = 10000 * ONE_NEAR; + let clients = accounts.iter().take(NUM_CLIENTS).cloned().collect_vec(); + + // split the clients into producers, validators, and rpc nodes + let tmp = clients.clone(); + let (producers, tmp) = tmp.split_at(NUM_PRODUCERS); + let (validators, tmp) = tmp.split_at(NUM_VALIDATORS); + let (rpcs, tmp) = tmp.split_at(NUM_RPC); + assert!(tmp.is_empty()); + + let producers = producers.iter().map(|account| account.as_str()).collect_vec(); + let validators = validators.iter().map(|account| account.as_str()).collect_vec(); + let [rpc_id] = rpcs else { panic!("Expected exactly one rpc node") }; + + let builder = TestLoopBuilder::new(); + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&builder.clock()) + .protocol_version_latest() + .genesis_height(10000) + .gas_prices_free() + .gas_limit_one_petagas() + .shard_layout_simple_v1(&["account3", "account5", "account7"]) + .transaction_validity_period(1000) + .epoch_length(10) + .validators_desired_roles(&producers, &validators) + .shuffle_shard_assignment_for_chunk_producers(true); + for account in accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); + } + let genesis = genesis_builder.build(); + + let env = builder.genesis(genesis).clients(clients).build(); + (env, rpc_id.clone()) +} + +/// Deploy the contract and wait until the transaction is executed. +fn do_deploy_contract( + test_loop: &mut TestLoopV2, + node_datas: &Vec, + rpc_id: &AccountId, + contract_id: &AccountId, +) { + tracing::info!(target: "test", ?rpc_id, ?contract_id, "Deploying contract."); + let tx = deploy_contract(test_loop, node_datas, rpc_id, contract_id); + test_loop.run_for(Duration::seconds(5)); + check_txs(&*test_loop, node_datas, rpc_id, &[tx]); +} + +/// Call the contract from all accounts and wait until the transactions are executed. +fn do_call_contract( + test_loop: &mut TestLoopV2, + node_datas: &Vec, + rpc_id: &AccountId, + contract_id: &AccountId, + accounts: &Vec, +) { + tracing::info!(target: "test", ?rpc_id, ?contract_id, "Calling contract."); + let mut txs = vec![]; + for sender_id in accounts { + let tx = call_contract(test_loop, node_datas, &sender_id, &contract_id); + txs.push(tx); + } + test_loop.run_for(Duration::seconds(20)); + check_txs(&*test_loop, node_datas, &rpc_id, &txs); +} + +/// Check the status of the transactions and assert that they are successful. +/// +/// Please note that it's important to use an rpc node that tracks all shards. +/// Otherwise, the transactions may not be found. +fn check_txs( + test_loop: &TestLoopV2, + node_datas: &Vec, + rpc: &AccountId, + txs: &[CryptoHash], +) { + let rpc = rpc_client(test_loop, node_datas, rpc); + + for &tx in txs { + let tx_outcome = rpc.chain.get_partial_transaction_result(&tx); + let status = tx_outcome.as_ref().map(|o| o.status.clone()); + let status = status.unwrap(); + tracing::info!(target: "test", ?tx, ?status, "transaction status"); + assert_matches!(status, FinalExecutionStatus::SuccessValue(_)); + } +} + +/// The condition that can be used for the test loop to wait until the chain +/// height is greater than the target height. +fn height_condition( + test_loop_data: &mut TestLoopData, + client_handle: &TestLoopDataHandle, + target_height: BlockHeight, +) -> bool { + test_loop_data.get(&client_handle).client.chain.head().unwrap().height > target_height +} + +/// Get the client for the provided rpd node account id. +fn rpc_client<'a>( + test_loop: &'a TestLoopV2, + node_datas: &'a Vec, + rpc_id: &AccountId, +) -> &'a Client { + let node_data = get_node_data(node_datas, rpc_id); + let client_actor_handle = node_data.client_sender.actor_handle(); + let client_actor = test_loop.data.get(&client_actor_handle); + &client_actor.client +} + +/// Make the account id for the provided index. +fn make_account(i: i32) -> AccountId { + format!("account{}", i).parse().unwrap() +} diff --git a/integration-tests/src/test_loop/tests/congestion_control_genesis_bootstrap.rs b/integration-tests/src/test_loop/tests/congestion_control_genesis_bootstrap.rs new file mode 100644 index 00000000000..530a4931d23 --- /dev/null +++ b/integration-tests/src/test_loop/tests/congestion_control_genesis_bootstrap.rs @@ -0,0 +1,72 @@ +use near_async::time::Duration; +use near_chain::ChainStoreAccess; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_client::Client; +use near_o11y::testonly::init_test_logger; +use near_primitives::types::AccountId; +use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; + +use crate::test_loop::builder::TestLoopBuilder; +use crate::test_loop::env::TestLoopEnv; +use crate::test_loop::utils::ONE_NEAR; + +const NUM_SHARDS: usize = 4; + +/// This test checks that the genesis congestion control info is saved into DB and not cleaned during GC, +/// so that client can use it to bootstrap the genesis congestion control info after restarting. +/// Restarting is the node is not checked here but in python/nayduck tests. +#[test] +fn test_congestion_control_genesis_bootstrap() { + if !ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { + return; + } + + init_test_logger(); + + let builder = TestLoopBuilder::new(); + + let initial_balance = 10000 * ONE_NEAR; + let accounts = ["test0", "test1"]; + let clients: Vec = accounts.iter().map(|account| account.parse().unwrap()).collect(); + + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&builder.clock()) + .protocol_version_latest() + .shard_layout_simple_v1(&["account3", "account5", "account7"]) + .validators_desired_roles(&accounts[0..1], &accounts[1..2]) + .minimum_validators_per_shard(1); + + for i in 0..clients.len() { + genesis_builder.add_user_account_simple(clients[i].clone(), initial_balance); + } + + let TestLoopEnv { mut test_loop, datas: node_datas, tempdir } = + builder.genesis(genesis_builder.build()).clients(clients.clone()).build(); + + test_loop.run_for(Duration::seconds(5)); + + for i in 0..clients.len() { + check_genesis_congestion_info_in_store( + &mut test_loop.data.get_mut(&node_datas[i].client_sender.actor_handle()).client, + ); + } + + TestLoopEnv { test_loop, datas: node_datas, tempdir } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +} + +fn check_genesis_congestion_info_in_store(client: &mut Client) { + let gc_config = client.config.gc.clone(); + client.chain.clear_data(&gc_config).unwrap(); + + let infos = near_store::get_genesis_congestion_infos(client.chain.chain_store().store()) + .unwrap() + .unwrap(); + assert_eq!(infos.len(), NUM_SHARDS); + for i in 0..NUM_SHARDS { + assert_eq!(infos[i].buffered_receipts_gas(), 0); + assert_eq!(infos[i].delayed_receipts_gas(), 0); + assert_eq!(infos[i].receipt_bytes(), 0); + } +} diff --git a/integration-tests/src/test_loop/tests/in_memory_tries.rs b/integration-tests/src/test_loop/tests/in_memory_tries.rs new file mode 100644 index 00000000000..4506eabcc39 --- /dev/null +++ b/integration-tests/src/test_loop/tests/in_memory_tries.rs @@ -0,0 +1,98 @@ +use itertools::Itertools; +use near_async::time::Duration; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_client::test_utils::test_loop::ClientQueries; +use near_o11y::testonly::init_test_logger; +use near_primitives::types::AccountId; +use near_store::ShardUId; + +use crate::test_loop::builder::TestLoopBuilder; +use crate::test_loop::env::TestLoopEnv; +use crate::test_loop::utils::transactions::execute_money_transfers; +use crate::test_loop::utils::ONE_NEAR; + +/// Runs chain with sequence of chunks with empty state changes, long enough to +/// cover 5 epochs which is default GC period. +/// After that, it checks that memtrie for the shard can be loaded. +/// This is a repro for #11583 where flat storage head was not moved at all at +/// this scenario, so chain data related to that block was garbage collected, +/// and loading memtrie failed because of missing `ChunkExtra` with desired +/// state root. +#[test] +fn test_load_memtrie_after_empty_chunks() { + init_test_logger(); + let builder = TestLoopBuilder::new(); + + let num_accounts = 3; + let num_clients = 2; + let epoch_length = 5; + let initial_balance = 10000 * ONE_NEAR; + let accounts = (num_accounts - num_clients..num_accounts) + .map(|i| format!("account{}", i).parse().unwrap()) + .collect::>(); + let client_accounts = accounts.iter().take(num_clients).cloned().collect_vec(); + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&builder.clock()) + .protocol_version_latest() + .genesis_height(10000) + .gas_prices_free() + .gas_limit_one_petagas() + // Set 2 shards, first of which doesn't have any validators. + .shard_layout_simple_v1(&["account1"]) + .transaction_validity_period(1000) + .epoch_length(epoch_length) + .validators_desired_roles(&client_accounts.iter().map(|t| t.as_str()).collect_vec(), &[]); + for account in &accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); + } + let genesis = genesis_builder.build(); + + let TestLoopEnv { mut test_loop, datas: node_datas, tempdir } = + builder.genesis(genesis).clients(client_accounts).build(); + + execute_money_transfers(&mut test_loop, &node_datas, &accounts); + + // Make sure the chain progresses for several epochs. + let client_handle = node_datas[0].client_sender.actor_handle(); + test_loop.run_until( + |test_loop_data| { + test_loop_data.get(&client_handle).client.chain.head().unwrap().height + > 10000 + epoch_length * 10 + }, + Duration::seconds(10), + ); + + // Find client currently tracking shard 0. + let clients = node_datas + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + let idx = { + let current_tracked_shards = clients.tracked_shards_for_each_client(); + tracing::info!("Current tracked shards: {:?}", current_tracked_shards); + current_tracked_shards + .iter() + .enumerate() + .find_map(|(idx, shards)| if shards.contains(&0) { Some(idx) } else { None }) + .expect("Not found any client tracking shard 0") + }; + + // Unload memtrie and load it back, check that it doesn't panic. + let tip = clients[idx].chain.head().unwrap(); + let shard_layout = clients[idx].epoch_manager.get_shard_layout(&tip.epoch_id).unwrap(); + clients[idx] + .runtime_adapter + .get_tries() + .unload_mem_trie(&ShardUId::from_shard_id_and_layout(0, &shard_layout)); + clients[idx] + .runtime_adapter + .get_tries() + .load_mem_trie(&ShardUId::from_shard_id_and_layout(0, &shard_layout), None, true) + .expect("Couldn't load memtrie"); + + // Give the test a chance to finish off remaining events in the event loop, which can + // be important for properly shutting down the nodes. + TestLoopEnv { test_loop, datas: node_datas, tempdir } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +} diff --git a/integration-tests/src/test_loop/tests/mod.rs b/integration-tests/src/test_loop/tests/mod.rs new file mode 100644 index 00000000000..679d8b6296b --- /dev/null +++ b/integration-tests/src/test_loop/tests/mod.rs @@ -0,0 +1,7 @@ +mod chunk_validator_kickout; +pub mod congestion_control; +pub mod congestion_control_genesis_bootstrap; +pub mod in_memory_tries; +pub mod multinode_stateless_validators; +pub mod multinode_test_loop_example; +pub mod simple_test_loop_example; diff --git a/integration-tests/src/test_loop/tests/multinode_stateless_validators.rs b/integration-tests/src/test_loop/tests/multinode_stateless_validators.rs new file mode 100644 index 00000000000..943bc0ed609 --- /dev/null +++ b/integration-tests/src/test_loop/tests/multinode_stateless_validators.rs @@ -0,0 +1,173 @@ +use std::collections::HashMap; + +use itertools::Itertools; +use near_async::time::Duration; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_client::Client; +use near_o11y::testonly::init_test_logger; +use near_primitives::types::{AccountId, EpochId, ValidatorInfoIdentifier}; +use near_primitives::version::ProtocolFeature::StatelessValidationV0; +use near_primitives::version::PROTOCOL_VERSION; +use near_primitives::views::CurrentEpochValidatorInfo; + +use crate::test_loop::builder::TestLoopBuilder; +use crate::test_loop::env::TestLoopEnv; +use crate::test_loop::utils::transactions::execute_money_transfers; +use crate::test_loop::utils::ONE_NEAR; + +const NUM_ACCOUNTS: usize = 20; +const NUM_SHARDS: u64 = 4; +const EPOCH_LENGTH: u64 = 12; + +const NUM_BLOCK_AND_CHUNK_PRODUCERS: usize = 4; +const NUM_CHUNK_VALIDATORS_ONLY: usize = 4; +const NUM_VALIDATORS: usize = NUM_BLOCK_AND_CHUNK_PRODUCERS + NUM_CHUNK_VALIDATORS_ONLY; + +#[test] +fn test_stateless_validators_with_multi_test_loop() { + if !StatelessValidationV0.enabled(PROTOCOL_VERSION) { + println!("Test not applicable without StatelessValidation enabled"); + return; + } + + init_test_logger(); + let builder = TestLoopBuilder::new(); + + let initial_balance = 10000 * ONE_NEAR; + let accounts = (0..NUM_ACCOUNTS) + .map(|i| format!("account{}", i).parse().unwrap()) + .collect::>(); + + // All block_and_chunk_producers will be both block and chunk validators. + let block_and_chunk_producers = + (0..NUM_BLOCK_AND_CHUNK_PRODUCERS).map(|idx| accounts[idx].as_str()).collect_vec(); + // These are the accounts that are only chunk validators, but not block/chunk producers. + let chunk_validators_only = (NUM_BLOCK_AND_CHUNK_PRODUCERS..NUM_VALIDATORS) + .map(|idx| accounts[idx].as_str()) + .collect_vec(); + let clients = accounts.iter().take(NUM_VALIDATORS).cloned().collect_vec(); + + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&builder.clock()) + .protocol_version_latest() + .genesis_height(10000) + .gas_prices_free() + .gas_limit_one_petagas() + .shard_layout_simple_v1(&["account3", "account5", "account7"]) + .transaction_validity_period(1000) + .epoch_length(EPOCH_LENGTH) + .validators_desired_roles(&block_and_chunk_producers, &chunk_validators_only) + .shuffle_shard_assignment_for_chunk_producers(true); + for account in &accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); + } + let genesis = genesis_builder.build(); + + let TestLoopEnv { mut test_loop, datas: node_datas, tempdir } = + builder.genesis(genesis).clients(clients).build(); + + // Capture the initial validator info in the first epoch. + let client_handle = node_datas[0].client_sender.actor_handle(); + let chain = &test_loop.data.get(&client_handle).client.chain; + let initial_epoch_id = chain.head().unwrap().epoch_id; + + let non_validator_accounts = accounts.iter().skip(NUM_VALIDATORS).cloned().collect_vec(); + execute_money_transfers(&mut test_loop, &node_datas, &non_validator_accounts); + + // Capture the id of the epoch we will check for the correct validator information in assert_validator_info. + let prev_epoch_id = test_loop.data.get(&client_handle).client.chain.head().unwrap().epoch_id; + assert_ne!(prev_epoch_id, initial_epoch_id); + + // Run the chain until it transitions to a different epoch then prev_epoch_id. + test_loop.run_until( + |test_loop_data| { + test_loop_data.get(&client_handle).client.chain.head().unwrap().epoch_id + != prev_epoch_id + }, + Duration::seconds(EPOCH_LENGTH as i64), + ); + + // Check the validator information for the epoch with the prev_epoch_id. + assert_validator_info( + &test_loop.data.get(&client_handle).client, + prev_epoch_id, + initial_epoch_id, + &accounts, + ); + + // Give the test a chance to finish off remaining events in the event loop, which can + // be important for properly shutting down the nodes. + TestLoopEnv { test_loop, datas: node_datas, tempdir } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +} + +/// Returns the CurrentEpochValidatorInfo for each validator account for the given epoch id. +fn get_current_validators( + client: &Client, + epoch_id: EpochId, +) -> HashMap { + client + .epoch_manager + .get_validator_info(ValidatorInfoIdentifier::EpochId(epoch_id)) + .unwrap() + .current_validators + .iter() + .map(|v| (v.account_id.clone(), v.clone())) + .collect() +} + +/// Asserts the following: +/// 1. Block and chunk producers produce block and chunks and also validate chunk witnesses. +/// 2. Chunk validators only validate chunk witnesses. +/// 3. Stake of both the block/chunk producers and chunk validators increase (due to rewards). +/// TODO: Assert on the specific reward amount, currently it only checks that some amount is rewarded. +fn assert_validator_info( + client: &Client, + epoch_id: EpochId, + initial_epoch_id: EpochId, + accounts: &Vec, +) { + let validator_to_info = get_current_validators(client, epoch_id); + let initial_validator_to_info = get_current_validators(client, initial_epoch_id); + + // Check that block/chunk producers generate blocks/chunks and also endorse chunk witnesses. + for idx in 0..NUM_BLOCK_AND_CHUNK_PRODUCERS { + let account = &accounts[idx]; + let validator_info = validator_to_info.get(account).unwrap(); + assert!(validator_info.num_produced_blocks > 0); + assert!(validator_info.num_produced_blocks <= validator_info.num_expected_blocks); + assert!(validator_info.num_expected_blocks < EPOCH_LENGTH); + + assert!(0 < validator_info.num_produced_chunks); + assert!(validator_info.num_produced_chunks <= validator_info.num_expected_chunks); + assert!(validator_info.num_expected_chunks < EPOCH_LENGTH * NUM_SHARDS); + + assert!(validator_info.num_produced_endorsements > 0); + assert!( + validator_info.num_produced_endorsements <= validator_info.num_expected_endorsements + ); + assert!(validator_info.num_expected_endorsements <= EPOCH_LENGTH * NUM_SHARDS); + + let initial_validator_info = initial_validator_to_info.get(account).unwrap(); + assert!(initial_validator_info.stake < validator_info.stake); + } + // Check chunk validators only endorse chunk witnesses. + for idx in NUM_BLOCK_AND_CHUNK_PRODUCERS..NUM_VALIDATORS { + let account = &accounts[idx]; + let validator_info = validator_to_info.get(account).unwrap(); + assert_eq!(validator_info.num_expected_blocks, 0); + assert_eq!(validator_info.num_expected_chunks, 0); + assert_eq!(validator_info.num_produced_blocks, 0); + assert_eq!(validator_info.num_produced_chunks, 0); + + assert!(validator_info.num_produced_endorsements > 0); + assert!( + validator_info.num_produced_endorsements <= validator_info.num_expected_endorsements + ); + assert!(validator_info.num_expected_endorsements <= EPOCH_LENGTH * NUM_SHARDS); + + let initial_validator_info = initial_validator_to_info.get(account).unwrap(); + assert!(initial_validator_info.stake < validator_info.stake); + } +} diff --git a/integration-tests/src/test_loop/tests/multinode_test_loop_example.rs b/integration-tests/src/test_loop/tests/multinode_test_loop_example.rs new file mode 100644 index 00000000000..1af69a249a0 --- /dev/null +++ b/integration-tests/src/test_loop/tests/multinode_test_loop_example.rs @@ -0,0 +1,77 @@ +use itertools::Itertools; +use near_async::time::Duration; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_client::test_utils::test_loop::ClientQueries; +use near_o11y::testonly::init_test_logger; +use near_primitives::types::AccountId; + +use crate::test_loop::builder::TestLoopBuilder; +use crate::test_loop::env::TestLoopEnv; +use crate::test_loop::utils::transactions::execute_money_transfers; +use crate::test_loop::utils::ONE_NEAR; + +const NUM_CLIENTS: usize = 4; + +#[test] +fn test_client_with_multi_test_loop() { + init_test_logger(); + let builder = TestLoopBuilder::new(); + + let initial_balance = 10000 * ONE_NEAR; + let accounts = + (0..100).map(|i| format!("account{}", i).parse().unwrap()).collect::>(); + let clients = accounts.iter().take(NUM_CLIENTS).cloned().collect_vec(); + + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&builder.clock()) + .protocol_version_latest() + .genesis_height(10000) + .gas_prices_free() + .gas_limit_one_petagas() + .shard_layout_simple_v1(&["account3", "account5", "account7"]) + .transaction_validity_period(1000) + .epoch_length(10) + .validators_desired_roles(&clients.iter().map(|t| t.as_str()).collect_vec(), &[]) + .shuffle_shard_assignment_for_chunk_producers(true); + for account in &accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); + } + let genesis = genesis_builder.build(); + + let TestLoopEnv { mut test_loop, datas: node_datas, tempdir } = + builder.genesis(genesis).clients(clients).build(); + + let first_epoch_tracked_shards = { + let clients = node_datas + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + clients.tracked_shards_for_each_client() + }; + tracing::info!("First epoch tracked shards: {:?}", first_epoch_tracked_shards); + + execute_money_transfers(&mut test_loop, &node_datas, &accounts); + + // Make sure the chain progresses for several epochs. + let client_handle = node_datas[0].client_sender.actor_handle(); + test_loop.run_until( + |test_loop_data| { + test_loop_data.get(&client_handle).client.chain.head().unwrap().height > 10050 + }, + Duration::seconds(10), + ); + + let clients = node_datas + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + let later_epoch_tracked_shards = clients.tracked_shards_for_each_client(); + tracing::info!("Later epoch tracked shards: {:?}", later_epoch_tracked_shards); + assert_ne!(first_epoch_tracked_shards, later_epoch_tracked_shards); + + // Give the test a chance to finish off remaining events in the event loop, which can + // be important for properly shutting down the nodes. + TestLoopEnv { test_loop, datas: node_datas, tempdir } + .shutdown_and_drain_remaining_events(Duration::seconds(20)); +} diff --git a/integration-tests/src/test_loop/tests/simple_test_loop_example.rs b/integration-tests/src/test_loop/tests/simple_test_loop_example.rs new file mode 100644 index 00000000000..a5b082956e0 --- /dev/null +++ b/integration-tests/src/test_loop/tests/simple_test_loop_example.rs @@ -0,0 +1,147 @@ +use near_async::messaging::{noop, IntoMultiSender, IntoSender, LateBoundSender}; +use near_async::test_loop::TestLoopV2; +use near_async::time::Duration; +use near_chain::chunks_store::ReadOnlyChunksStore; +use near_chain::ChainGenesis; +use near_chain_configs::test_genesis::TestGenesisBuilder; +use near_chain_configs::{ClientConfig, MutableConfigValue}; +use near_chunks::shards_manager_actor::ShardsManagerActor; +use near_client::client_actor::ClientActorInner; +use near_client::sync_jobs_actor::SyncJobsActor; +use near_client::test_utils::{MAX_BLOCK_PROD_TIME, MIN_BLOCK_PROD_TIME}; +use near_client::{Client, SyncAdapter}; +use near_epoch_manager::shard_tracker::{ShardTracker, TrackedConfig}; +use near_epoch_manager::EpochManager; +use near_o11y::testonly::init_test_logger; +use near_primitives::network::PeerId; + +use near_primitives::test_utils::create_test_signer; +use near_primitives::types::AccountId; + +use crate::test_loop::utils::ONE_NEAR; +use near_store::genesis::initialize_genesis_state; +use near_store::test_utils::create_test_store; +use nearcore::NightshadeRuntime; +use std::path::Path; +use std::sync::{Arc, RwLock}; + +#[test] +fn test_client_with_simple_test_loop() { + init_test_logger(); + let mut test_loop = TestLoopV2::new(); + + let client_config = ClientConfig::test( + true, + MIN_BLOCK_PROD_TIME.whole_milliseconds() as u64, + MAX_BLOCK_PROD_TIME.whole_milliseconds() as u64, + 4, + false, + true, + false, + false, + ); + let initial_balance = 10000 * ONE_NEAR; + let accounts = + (0..100).map(|i| format!("account{}", i).parse().unwrap()).collect::>(); + + let mut genesis_builder = TestGenesisBuilder::new(); + genesis_builder + .genesis_time_from_clock(&test_loop.clock()) + .protocol_version_latest() + .genesis_height(10000) + .gas_prices_free() + .gas_limit_one_petagas() + .shard_layout_simple_v1(&["account3", "account5", "account7"]) + .transaction_validity_period(1000) + .epoch_length(10) + .validators_desired_roles(&["account0"], &[]) + .shuffle_shard_assignment_for_chunk_producers(true); + for account in &accounts { + genesis_builder.add_user_account_simple(account.clone(), initial_balance); + } + let genesis = genesis_builder.build(); + + let store = create_test_store(); + initialize_genesis_state(store.clone(), &genesis, None); + + let chain_genesis = ChainGenesis::new(&genesis.config); + let epoch_manager = EpochManager::new_arc_handle(store.clone(), &genesis.config); + let shard_tracker = ShardTracker::new(TrackedConfig::AllShards, epoch_manager.clone()); + let runtime_adapter = NightshadeRuntime::test( + Path::new("."), + store.clone(), + &genesis.config, + epoch_manager.clone(), + ); + let validator_signer = MutableConfigValue::new( + Some(Arc::new(create_test_signer(accounts[0].as_str()))), + "validator_signer", + ); + + let shards_manager_adapter = LateBoundSender::new(); + let sync_jobs_adapter = LateBoundSender::new(); + let client_adapter = LateBoundSender::new(); + + let sync_jobs_actor = SyncJobsActor::new(client_adapter.as_multi_sender()); + + let state_sync_adapter = Arc::new(RwLock::new(SyncAdapter::new( + client_adapter.as_sender(), + noop().into_sender(), + SyncAdapter::actix_actor_maker(), + ))); + + let client = Client::new( + test_loop.clock(), + client_config.clone(), + chain_genesis, + epoch_manager.clone(), + shard_tracker.clone(), + state_sync_adapter, + runtime_adapter, + noop().into_multi_sender(), + shards_manager_adapter.as_sender(), + validator_signer.clone(), + true, + [0; 32], + None, + Arc::new(test_loop.async_computation_spawner(|_| Duration::milliseconds(80))), + noop().into_multi_sender(), + ) + .unwrap(); + + let shards_manager = ShardsManagerActor::new( + test_loop.clock(), + validator_signer, + epoch_manager, + shard_tracker, + noop().into_sender(), + client_adapter.as_sender(), + ReadOnlyChunksStore::new(store), + client.chain.head().unwrap(), + client.chain.header_head().unwrap(), + Duration::milliseconds(100), + ); + + let client_actor = ClientActorInner::new( + test_loop.clock(), + client, + client_adapter.as_multi_sender(), + client_config, + PeerId::random(), + noop().into_multi_sender(), + noop().into_sender(), + None, + Default::default(), + None, + sync_jobs_adapter.as_multi_sender(), + Box::new(test_loop.future_spawner()), + ) + .unwrap(); + + test_loop.register_actor(sync_jobs_actor, Some(sync_jobs_adapter)); + test_loop.register_actor(shards_manager, Some(shards_manager_adapter)); + test_loop.register_actor(client_actor, Some(client_adapter)); + + test_loop.run_for(Duration::seconds(10)); + test_loop.shutdown_and_drain_remaining_events(Duration::seconds(1)); +} diff --git a/integration-tests/src/test_loop/utils/mod.rs b/integration-tests/src/test_loop/utils/mod.rs new file mode 100644 index 00000000000..a1d57690e5e --- /dev/null +++ b/integration-tests/src/test_loop/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod network; +pub mod transactions; + +pub(crate) const ONE_NEAR: u128 = 1_000_000_000_000_000_000_000_000; +pub(crate) const TGAS: u64 = 1_000_000_000_000; diff --git a/integration-tests/src/test_loop/utils/network.rs b/integration-tests/src/test_loop/utils/network.rs new file mode 100644 index 00000000000..d86f5b971af --- /dev/null +++ b/integration-tests/src/test_loop/utils/network.rs @@ -0,0 +1,79 @@ +use std::sync::{Arc, Mutex}; + +use near_epoch_manager::EpochManagerAdapter; +use near_network::types::NetworkRequests; +use near_primitives::types::AccountId; + +use crate::test_loop::env::TestLoopChunksStorage; + +/// Handler to drop all network messages relevant to chunk validated by +/// `validator_of_chunks_to_drop`. If number of nodes on chain is significant +/// enough (at least three?), this is enough to prevent chunk from being +/// included. +/// +/// This logic can be easily extended to dropping chunk based on any rule. +pub fn partial_encoded_chunks_dropper( + chunks_storage: Arc>, + epoch_manager_adapter: Arc, + validator_of_chunks_to_drop: AccountId, +) -> Arc Option> { + Arc::new(move |request| { + // Filter out only messages related to distributing chunk in the + // network; extract `chunk_hash` from the message. + let chunk_hash = match &request { + NetworkRequests::PartialEncodedChunkRequest { request, .. } => { + Some(request.chunk_hash.clone()) + } + NetworkRequests::PartialEncodedChunkResponse { response, .. } => { + Some(response.chunk_hash.clone()) + } + NetworkRequests::PartialEncodedChunkMessage { partial_encoded_chunk, .. } => { + Some(partial_encoded_chunk.header.chunk_hash()) + } + NetworkRequests::PartialEncodedChunkForward { forward, .. } => { + Some(forward.chunk_hash.clone()) + } + _ => None, + }; + + let Some(chunk_hash) = chunk_hash else { + return Some(request); + }; + + let chunk = { + let chunks_storage = chunks_storage.lock().unwrap(); + let chunk = chunks_storage.get(&chunk_hash).unwrap().clone(); + let can_drop_chunk = chunks_storage.can_drop_chunk(&chunk); + + if !can_drop_chunk { + return Some(request); + } + + chunk + }; + + let prev_block_hash = chunk.prev_block_hash(); + let shard_id = chunk.shard_id(); + let height_created = chunk.height_created(); + + // If we don't have block on top of which chunk is built, we can't + // retrieve epoch id. + // This case appears to be too rare to interfere with the goal of + // dropping chunk. + let Ok(epoch_id) = epoch_manager_adapter.get_epoch_id_from_prev_block(prev_block_hash) + else { + return Some(request); + }; + + // Finally, we drop chunk if the given account is present in the list + // of its validators. + let chunk_validators = epoch_manager_adapter + .get_chunk_validator_assignments(&epoch_id, shard_id, height_created) + .unwrap(); + if !chunk_validators.contains(&validator_of_chunks_to_drop) { + return Some(request); + } + + return None; + }) +} diff --git a/integration-tests/src/test_loop/utils/transactions.rs b/integration-tests/src/test_loop/utils/transactions.rs new file mode 100644 index 00000000000..8c66e8e5291 --- /dev/null +++ b/integration-tests/src/test_loop/utils/transactions.rs @@ -0,0 +1,203 @@ +use crate::test_loop::env::TestData; +use itertools::Itertools; +use near_async::messaging::SendAsync; +use near_async::test_loop::TestLoopV2; +use near_async::time::Duration; +use near_client::test_utils::test_loop::ClientQueries; +use near_network::client::ProcessTxRequest; +use near_primitives::hash::CryptoHash; +use near_primitives::test_utils::create_user_test_signer; +use near_primitives::transaction::SignedTransaction; +use near_primitives::types::AccountId; +use std::collections::HashMap; + +use super::{ONE_NEAR, TGAS}; + +/// Execute money transfers within given `TestLoop` between given accounts. +/// Runs chain long enough for the transfers to be optimistically executed. +/// Used to generate state changes and check that chain is able to update +/// balances correctly. +/// TODO: consider resending transactions which may be dropped because of +/// missing chunks. +pub(crate) fn execute_money_transfers( + test_loop: &mut TestLoopV2, + node_data: &[TestData], + accounts: &[AccountId], +) { + let clients = node_data + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + let mut balances = accounts + .iter() + .map(|account| (account.clone(), clients.query_balance(&account))) + .collect::>(); + let num_clients = clients.len(); + + // Transactions have to be built on top of some block in chain. To make + // sure all clients accept them, we select the head of the client with + // the smallest height. + let (_, anchor_hash) = clients + .iter() + .map(|client| { + let head = client.chain.head().unwrap(); + (head.height, head.last_block_hash) + }) + .min_by_key(|&(height, _)| height) + .unwrap(); + drop(clients); + + for i in 0..accounts.len() { + let amount = ONE_NEAR * (i as u128 + 1); + let sender = &accounts[i]; + let receiver = &accounts[(i + 1) % accounts.len()]; + let tx = SignedTransaction::send_money( + // TODO: set correct nonce. + 1, + sender.clone(), + receiver.clone(), + &create_user_test_signer(sender).into(), + amount, + anchor_hash, + ); + *balances.get_mut(sender).unwrap() -= amount; + *balances.get_mut(receiver).unwrap() += amount; + let future = node_data[i % num_clients] + .client_sender + .clone() + .with_delay(Duration::milliseconds(300 * i as i64)) + .send_async(ProcessTxRequest { + transaction: tx, + is_forwarded: false, + check_only: false, + }); + drop(future); + } + + // Give plenty of time for these transactions to complete. + // TODO: consider explicitly waiting for all execution outcomes. + test_loop.run_for(Duration::milliseconds(300 * accounts.len() as i64 + 20_000)); + + let clients = node_data + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + for account in accounts { + assert_eq!( + clients.query_balance(account), + *balances.get(account).unwrap(), + "Account balance mismatch for account {}", + account + ); + } +} + +/// Deploy the test contract to the provided contract_id account. The contract +/// account should already exits. The contract will be deployed from the contract +/// account itself. +/// +/// This function does not wait until the transactions is executed. +pub fn deploy_contract( + test_loop: &mut TestLoopV2, + node_datas: &[TestData], + rpc_id: &AccountId, + contract_id: &AccountId, +) -> CryptoHash { + let block_hash = get_shared_block_hash(node_datas, test_loop); + + // TOOD make nonce an argument + let nonce = 1; + let signer = create_user_test_signer(&contract_id).into(); + + let code = near_test_contracts::rs_contract(); + let code = code.to_vec(); + + let tx = SignedTransaction::deploy_contract(nonce, contract_id, code, &signer, block_hash); + let tx_hash = tx.get_hash(); + let process_tx_request = + ProcessTxRequest { transaction: tx, is_forwarded: false, check_only: false }; + + let rpc_node_data = get_node_data(node_datas, rpc_id); + let rpc_node_data_sender = &rpc_node_data.client_sender.clone(); + + let future = rpc_node_data_sender.send_async(process_tx_request); + drop(future); + + tracing::debug!(target: "test", ?contract_id, ?tx_hash, "deployed contract"); + tx_hash +} + +/// Call the contract deployed at contract id from the sender id. +/// +/// This function does not wait until the transactions is executed. +pub fn call_contract( + test_loop: &mut TestLoopV2, + node_datas: &[TestData], + sender_id: &AccountId, + contract_id: &AccountId, +) -> CryptoHash { + let block_hash = get_shared_block_hash(node_datas, test_loop); + + // TOOD make nonce an argument + let nonce = 2; + let signer = create_user_test_signer(sender_id); + + let burn_gas = 250 * TGAS; + let attach_gas = 300 * TGAS; + + let deposit = 0; + + // TODO make method and args arguments + let method_name = "burn_gas_raw".to_owned(); + let args = burn_gas.to_le_bytes().to_vec(); + + let tx = SignedTransaction::call( + nonce, + sender_id.clone(), + contract_id.clone(), + &signer.into(), + deposit, + method_name, + args, + attach_gas, + block_hash, + ); + + let tx_hash = tx.get_hash(); + + let process_tx_request = + ProcessTxRequest { transaction: tx, is_forwarded: false, check_only: false }; + let future = node_datas[0].client_sender.clone().send_async(process_tx_request); + drop(future); + + tracing::debug!(target: "test", ?sender_id, ?contract_id, ?tx_hash, "called contract"); + tx_hash +} + +/// Finds a block that all clients have on their chain and return its hash. +fn get_shared_block_hash(node_datas: &[TestData], test_loop: &mut TestLoopV2) -> CryptoHash { + let clients = node_datas + .iter() + .map(|data| &test_loop.data.get(&data.client_sender.actor_handle()).client) + .collect_vec(); + + let (_, block_hash) = clients + .iter() + .map(|client| { + let head = client.chain.head().unwrap(); + (head.height, head.last_block_hash) + }) + .min_by_key(|&(height, _)| height) + .unwrap(); + block_hash +} + +/// Returns the test data of for the node with the given account id. +pub fn get_node_data<'a>(node_datas: &'a [TestData], account_id: &AccountId) -> &'a TestData { + for node_data in node_datas { + if &node_data.account_id == account_id { + return node_data; + } + } + panic!("RPC client not found"); +} diff --git a/integration-tests/src/tests/client/benchmarks.rs b/integration-tests/src/tests/client/benchmarks.rs index 0f9cde0c7ec..1c60100abed 100644 --- a/integration-tests/src/tests/client/benchmarks.rs +++ b/integration-tests/src/tests/client/benchmarks.rs @@ -10,7 +10,7 @@ use near_client::{ProcessTxResponse, ProduceChunkResult}; use near_crypto::{InMemorySigner, KeyType}; use near_primitives::checked_feature; use near_primitives::transaction::{Action, DeployContractAction, SignedTransaction}; -use near_primitives::version::PROTOCOL_VERSION; +use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use nearcore::test_utils::TestEnvNightshadeSetupExt; /// How long does it take to produce a large chunk? @@ -35,9 +35,9 @@ fn benchmark_large_chunk_production_time() { let genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); - let account_id = env.get_client_id(0).clone(); + let account_id = env.get_client_id(0); let signer = - InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()); + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()).into(); let last_block_hash = env.clients[0].chain.head().unwrap().last_block_hash; for i in 0..n_txes { let tx = SignedTransaction::from_actions( @@ -56,14 +56,20 @@ fn benchmark_large_chunk_production_time() { let ProduceChunkResult { chunk, .. } = create_chunk_on_height(&mut env.clients[0], 0); let time = t.elapsed(); + let decoded_chunk = chunk.decode_chunk(0).unwrap(); + let size = borsh::object_length(&chunk).unwrap(); eprintln!("chunk size: {}kb", size / 1024); eprintln!("time to produce: {:0.2?}", time); // Check that we limit the size of the chunk and not include all `n_txes` // transactions in the chunk. - if checked_feature!("stable", WitnessTransactionLimits, PROTOCOL_VERSION) { + if ProtocolFeature::BiggerCombinedTransactionLimit.enabled(PROTOCOL_VERSION) { + assert!(6 * mb < size && size < 8 * mb, "{size}"); + assert_eq!(decoded_chunk.transactions().len(), 7); // 4MiB limit allows for 7 x 0.5MiB transactions + } else if ProtocolFeature::WitnessTransactionLimits.enabled(PROTOCOL_VERSION) { assert!(2 * mb < size && size < 4 * mb, "{size}"); + assert_eq!(decoded_chunk.transactions().len(), 3); // 2MiB limit allows for 3 x 0.5MiB transactions } else { assert!(30 * mb < size && size < 40 * mb, "{size}"); } diff --git a/integration-tests/src/tests/client/block_corruption.rs b/integration-tests/src/tests/client/block_corruption.rs index 6ab17c3c33d..b0ab6e9b546 100644 --- a/integration-tests/src/tests/client/block_corruption.rs +++ b/integration-tests/src/tests/client/block_corruption.rs @@ -21,7 +21,7 @@ fn create_tx_load(height: BlockHeight, last_block: &Block) -> Vec1, 5->6 and 10->11 - let prev_epoch_id = height_to_epoch.get(&(h - 1)).unwrap().clone(); + let prev_epoch_id = *height_to_epoch.get(&(h - 1)).unwrap(); assert_eq!(EpochId(block.header.epoch_id) == prev_epoch_id, h % 5 != 1); // Make sure that the blocks leading to the epoch switch have twice as @@ -345,6 +336,7 @@ fn chunks_produced_and_distributed_all_in_all_shards() { } #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_2_vals_per_shard() { Test { validator_groups: 2, @@ -357,6 +349,7 @@ fn chunks_produced_and_distributed_2_vals_per_shard() { } #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_one_val_per_shard() { Test { validator_groups: 4, @@ -372,6 +365,7 @@ fn chunks_produced_and_distributed_one_val_per_shard() { // because we always fallback on the p2p mechanism. This test runs with a config // where `enabled: false`. #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_chunk_distribution_network_disabled() { let config = ChunkDistributionNetworkConfig { enabled: false, @@ -391,6 +385,7 @@ fn chunks_produced_and_distributed_chunk_distribution_network_disabled() { // because we always fallback on the p2p mechanism. This test runs with a config // where the URIs are not real endpoints. #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_chunk_distribution_network_wrong_urls() { let config = ChunkDistributionNetworkConfig { enabled: false, @@ -414,6 +409,7 @@ fn chunks_produced_and_distributed_chunk_distribution_network_wrong_urls() { // where the `get` URI points at a random http server (therefore it does not // return valid chunks). #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_chunk_distribution_network_incorrect_get_return() { let config = ChunkDistributionNetworkConfig { enabled: false, @@ -443,6 +439,7 @@ fn chunks_produced_and_distributed_all_in_all_shards_should_succeed_even_without } #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_2_vals_per_shard_should_succeed_even_without_forwarding() { Test { validator_groups: 2, @@ -455,6 +452,7 @@ fn chunks_produced_and_distributed_2_vals_per_shard_should_succeed_even_without_ } #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_one_val_per_shard_should_succeed_even_without_forwarding() { Test { validator_groups: 4, @@ -526,6 +524,7 @@ fn chunks_recovered_from_full() { /// Happy case -- each shard is handled by one cop and one block producers. #[test] +#[cfg_attr(not(feature = "expensive_tests"), ignore)] fn chunks_produced_and_distributed_one_val_shard_cop() { Test { validator_groups: 4, diff --git a/integration-tests/src/tests/client/cold_storage.rs b/integration-tests/src/tests/client/cold_storage.rs index 3e625de9af9..96573f3dcd5 100644 --- a/integration-tests/src/tests/client/cold_storage.rs +++ b/integration-tests/src/tests/client/cold_storage.rs @@ -1,9 +1,9 @@ use borsh::BorshDeserialize; use near_chain::Provenance; -use near_chain_configs::Genesis; +use near_chain_configs::{Genesis, MutableConfigValue}; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; -use near_crypto::{InMemorySigner, KeyType}; +use near_crypto::{InMemorySigner, KeyType, Signer}; use near_epoch_manager::EpochManager; use near_o11y::testonly::init_test_logger; use near_primitives::block::Tip; @@ -71,17 +71,13 @@ fn test1() -> AccountId { "test1".parse().unwrap() } -fn create_tx_send_money( - nonce: u64, - signer: &InMemorySigner, - block_hash: CryptoHash, -) -> SignedTransaction { +fn create_tx_send_money(nonce: u64, signer: &Signer, block_hash: CryptoHash) -> SignedTransaction { SignedTransaction::send_money(nonce, test0(), test1(), signer, 1, block_hash) } fn create_tx_deploy_contract( height: u64, - signer: &InMemorySigner, + signer: &Signer, block_hash: CryptoHash, ) -> SignedTransaction { let code = near_test_contracts::rs_contract().to_vec(); @@ -92,7 +88,7 @@ fn create_tx_deploy_contract( fn create_tx_function_call( nonce: u64, - signer: &InMemorySigner, + signer: &Signer, block_hash: CryptoHash, ) -> SignedTransaction { let action = Action::FunctionCall(Box::new(FunctionCallAction { @@ -131,7 +127,7 @@ fn test_storage_after_commit_of_cold_update() { let mut last_hash = *env.clients[0].chain.genesis().hash(); for height in 1..max_height { - let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0"); + let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0").into(); if height == 1 { let tx = create_tx_deploy_contract(height, &signer, last_hash); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -262,7 +258,9 @@ fn test_cold_db_copy_with_height_skips() { let mut genesis = Genesis::test(vec![test0(), test1()], 1); genesis.config.epoch_length = epoch_length; genesis.config.min_gas_price = 0; - let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); + let mut env = TestEnv::builder(&genesis.config) + .nightshade_runtimes_congestion_control_disabled(&genesis) + .build(); let (storage, ..) = create_test_node_storage_with_cold(DB_VERSION, DbKind::Hot); let cold_db = storage.cold_db().unwrap(); @@ -271,7 +269,7 @@ fn test_cold_db_copy_with_height_skips() { let mut last_hash = *env.clients[0].chain.genesis().hash(); for height in 1..max_height { - let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0"); + let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0").into(); // It is still painful to filter out transactions in last two blocks. // So, as block 19 is skipped, blocks 17 and 18 shouldn't contain any transactions. // So, we shouldn't send any transactions between block 17 and the previous block. @@ -362,7 +360,7 @@ fn test_initial_copy_to_cold(batch_size: usize) { let mut last_hash = *env.clients[0].chain.genesis().hash(); for height in 1..max_height { - let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0"); + let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0").into(); for i in 0..5 { let tx = create_tx_send_money(height * 10 + i, &signer, last_hash); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -446,7 +444,7 @@ fn test_cold_loop_on_gc_boundary() { let mut last_hash = *env.clients[0].chain.genesis().hash(); for height in 1..height_delta { - let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0"); + let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0").into(); for i in 0..5 { let tx = create_tx_send_money(height * 10 + i, &signer, last_hash); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -465,7 +463,7 @@ fn test_cold_loop_on_gc_boundary() { update_cold_head(cold_db, &hot_store, &(height_delta - 1)).unwrap(); for height in height_delta..height_delta * 2 { - let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0"); + let signer = InMemorySigner::from_seed(test0(), KeyType::ED25519, "test0").into(); for i in 0..5 { let tx = create_tx_send_money(height * 10 + i, &signer, last_hash); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -490,7 +488,7 @@ fn test_cold_loop_on_gc_boundary() { public_key: signer.public_key, secret_key: signer.secret_key, }, - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); near_config.client_config = env.clients[0].config.clone(); diff --git a/integration-tests/src/tests/client/epoch_sync.rs b/integration-tests/src/tests/client/epoch_sync.rs index 1b5c1f18d2c..0d8a8027c42 100644 --- a/integration-tests/src/tests/client/epoch_sync.rs +++ b/integration-tests/src/tests/client/epoch_sync.rs @@ -1,5 +1,5 @@ -use crate::nearcore_utils::{add_blocks, setup_configs_with_epoch_length}; -use crate::test_helpers::heavy_test; +use crate::tests::nearcore_utils::{add_blocks, setup_configs_with_epoch_length}; +use crate::tests::test_helpers::heavy_test; use actix::Actor; use actix_rt::System; use futures::{future, FutureExt}; @@ -37,7 +37,8 @@ use std::sync::{Arc, RwLock}; fn generate_transactions(last_hash: &CryptoHash, h: BlockHeight) -> Vec { let mut txs = vec![]; - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); if h == 1 { txs.push(SignedTransaction::from_actions( h, @@ -115,14 +116,14 @@ fn test_continuous_epoch_sync_info_population() { env.clients[0].chain.chain_store().get_block_header(last_final_hash).unwrap(); if *last_final_header.epoch_id() != last_epoch_id { - let epoch_id = last_epoch_id.clone(); + let epoch_id = last_epoch_id; tracing::debug!("Checking epoch: {:?}", &epoch_id); assert!(env.clients[0].chain.chain_store().get_epoch_sync_info(&epoch_id).is_ok()); tracing::debug!("OK"); } - last_epoch_id = last_final_header.epoch_id().clone(); + last_epoch_id = *last_final_header.epoch_id(); } } @@ -171,7 +172,7 @@ fn test_continuous_epoch_sync_info_population_on_header_sync() { // Save all finished epoch_ids let mut epoch_ids = epoch_ids.write().unwrap(); for block in blocks[0..200].iter() { - epoch_ids.insert(block.header().epoch_id().clone()); + epoch_ids.insert(*block.header().epoch_id()); } // Start second node @@ -273,7 +274,7 @@ fn test_epoch_sync_data_hash_from_epoch_sync_info() { env.clients[0].chain.chain_store().get_block_header(last_final_hash).unwrap(); if *last_final_header.epoch_id() != last_epoch_id { - let epoch_id = last_epoch_id.clone(); + let epoch_id = last_epoch_id; let epoch_sync_info = env.clients[0].chain.chain_store().get_epoch_sync_info(&epoch_id).unwrap(); @@ -302,7 +303,7 @@ fn test_epoch_sync_data_hash_from_epoch_sync_info() { tracing::debug!("OK"); } - last_epoch_id = last_final_header.epoch_id().clone(); + last_epoch_id = *last_final_header.epoch_id(); } } diff --git a/integration-tests/src/tests/client/features.rs b/integration-tests/src/tests/client/features.rs index d964fccdf00..25c77caf1ff 100644 --- a/integration-tests/src/tests/client/features.rs +++ b/integration-tests/src/tests/client/features.rs @@ -16,14 +16,12 @@ mod increase_deployment_cost; mod increase_storage_compute_cost; mod limit_contract_functions_number; mod lower_storage_key_limit; -mod multinode_test_loop_example; mod nearvm; #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] mod nonrefundable_transfer; mod orphan_chunk_state_witness; mod restore_receipts_after_fix_apply_chunks; mod restrict_tla; -mod simple_test_loop_example; mod stateless_validation; mod storage_proof_size_limit; mod wallet_contract; diff --git a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs index 412ea881ebc..d2f9e851f31 100644 --- a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs +++ b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs @@ -7,7 +7,7 @@ use near_chain_configs::{Genesis, NEAR_BASE}; use near_chunks::metrics::PARTIAL_ENCODED_CHUNK_FORWARD_CACHED_WITHOUT_HEADER; use near_client::test_utils::{create_chunk_with_transactions, TestEnv}; use near_client::{ProcessTxResponse, ProduceChunkResult}; -use near_crypto::{InMemorySigner, KeyType, SecretKey, Signer}; +use near_crypto::{InMemorySigner, KeyType, SecretKey}; use near_network::shards_manager::ShardsManagerRequestFromNetwork; use near_network::types::{NetworkRequests, PeerManagerMessageRequest}; use near_o11y::testonly::init_test_logger; @@ -19,7 +19,7 @@ use near_primitives::sharding::ChunkHash; use near_primitives::transaction::SignedTransaction; use near_primitives::types::{AccountId, BlockHeight}; use near_primitives::utils::derive_near_implicit_account_id; -use near_primitives::version::{ProtocolFeature, ProtocolVersion}; +use near_primitives::version::{ProtocolFeature, ProtocolVersion, PROTOCOL_VERSION}; use near_primitives::views::FinalExecutionStatus; use nearcore::test_utils::TestEnvNightshadeSetupExt; use rand::seq::SliceRandom; @@ -53,7 +53,8 @@ fn test_transaction_hash_collision() { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let signer0 = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); - let signer1 = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer1 = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let send_money_tx = SignedTransaction::send_money( 1, "test1".parse().unwrap(), @@ -90,7 +91,7 @@ fn test_transaction_hash_collision() { "test1".parse().unwrap(), NEAR_BASE, signer1.public_key(), - &signer0, + &signer0.into(), *genesis_block.hash(), ); assert_eq!( @@ -124,8 +125,10 @@ fn get_status_of_tx_hash_collision_for_near_implicit_account( let deposit_for_account_creation = 10u128.pow(23); let mut height = 1; let blocks_number = 5; - let signer1 = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer1 = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let near_implicit_account_id = near_implicit_account_signer.account_id.clone(); + let near_implicit_account_signer = near_implicit_account_signer.into(); // Send money to NEAR-implicit account, invoking its creation. let send_money_tx = SignedTransaction::send_money( @@ -241,7 +244,7 @@ fn test_chunk_transaction_validity() { 1, "test1".parse().unwrap(), "test0".parse().unwrap(), - &signer, + &signer.into(), 100, *genesis_block.hash(), ); @@ -252,7 +255,7 @@ fn test_chunk_transaction_validity() { ProduceChunkResult { chunk, encoded_chunk_parts_paths: merkle_paths, receipts, .. }, block, ) = create_chunk_with_transactions(&mut env.clients[0], vec![tx]); - let validator_id = env.clients[0].validator_signer.as_ref().unwrap().validator_id().clone(); + let validator_id = env.clients[0].validator_signer.get().unwrap().validator_id().clone(); env.clients[0] .persist_and_distribute_encoded_chunk(chunk, merkle_paths, receipts, validator_id) .unwrap(); @@ -273,7 +276,7 @@ fn test_transaction_nonce_too_large() { large_nonce, "test1".parse().unwrap(), "test0".parse().unwrap(), - &signer, + &signer.into(), 100, *genesis_block.hash(), ); @@ -497,10 +500,12 @@ fn test_processing_chunks_sanity() { let mut next_blocks: Vec<_> = (3 * i..3 * i + 3).collect(); next_blocks.shuffle(&mut rng); for ind in next_blocks { + let signer = env.clients[1].validator_signer.get(); let _ = env.clients[1].start_process_block( blocks[ind].clone().into(), Provenance::NONE, None, + &signer, ); if rng.gen_bool(0.5) { env.process_shards_manager_responses_and_finish_processing_blocks(1); @@ -721,10 +726,12 @@ fn test_chunk_forwarding_optimization() { // The block producer of course has the complete block so we can process that. for i in 0..test.num_validators { debug!(target: "test", "Processing block {} as validator #{}", block.header().height(), i); + let signer = test.env.clients[i].validator_signer.get(); let _ = test.env.clients[i].start_process_block( block.clone().into(), if i == 0 { Provenance::PRODUCED } else { Provenance::NONE }, None, + &signer, ); let mut accepted_blocks = test.env.clients[i].finish_block_in_processing(block.header().hash()); @@ -757,7 +764,9 @@ fn test_chunk_forwarding_optimization() { // Note: For nightly, which includes SingleShardTracking, this check is disabled because // we're so efficient with part forwarding now that we don't seem to be forwarding more // than it is necessary. - if !cfg!(feature = "nightly") && !cfg!(feature = "statelessnet_protocol") { + // TODO - Since the stabilization of Stateless Validation which includes the + // SingleShardTracking this test doesn't make sense anymore. We should remove it. + if !ProtocolFeature::SingleShardTracking.enabled(PROTOCOL_VERSION) { assert!(PARTIAL_ENCODED_CHUNK_FORWARD_CACHED_WITHOUT_HEADER.get() > 0.0); } debug!(target: "test", @@ -810,8 +819,13 @@ fn test_processing_blocks_async() { let mut rng = thread_rng(); blocks.shuffle(&mut rng); for ind in 0..blocks.len() { - let _ = - env.clients[1].start_process_block(blocks[ind].clone().into(), Provenance::NONE, None); + let signer = env.clients[1].validator_signer.get(); + let _ = env.clients[1].start_process_block( + blocks[ind].clone().into(), + Provenance::NONE, + None, + &signer, + ); } env.process_shards_manager_responses_and_finish_processing_blocks(1); diff --git a/integration-tests/src/tests/client/features/account_id_in_function_call_permission.rs b/integration-tests/src/tests/client/features/account_id_in_function_call_permission.rs index 205c841286f..538bbf5c327 100644 --- a/integration-tests/src/tests/client/features/account_id_in_function_call_permission.rs +++ b/integration-tests/src/tests/client/features/account_id_in_function_call_permission.rs @@ -35,7 +35,8 @@ fn test_account_id_in_function_call_permission_upgrade() { .build() }; - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer: Signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx = TransactionV0 { signer_id: "test0".parse().unwrap(), receiver_id: "test0".parse().unwrap(), @@ -122,7 +123,7 @@ fn test_very_long_account_id() { nonce: 0, block_hash: tip.last_block_hash, }) - .sign(&signer); + .sign(&signer.into()); assert_eq!( env.clients[0].process_tx(tx, false, false), diff --git a/integration-tests/src/tests/client/features/adversarial_behaviors.rs b/integration-tests/src/tests/client/features/adversarial_behaviors.rs index e7d7200a3f6..733485c7a6b 100644 --- a/integration-tests/src/tests/client/features/adversarial_behaviors.rs +++ b/integration-tests/src/tests/client/features/adversarial_behaviors.rs @@ -8,7 +8,7 @@ use near_chain::Provenance; use near_chain_configs::Genesis; use near_chunks::{ shards_manager_actor::CHUNK_REQUEST_SWITCH_TO_FULL_FETCH, - test_loop::ShardsManagerResendChunkRequests, + test_utils::ShardsManagerResendChunkRequests, }; use near_client::test_utils::TestEnv; use near_network::{ @@ -59,6 +59,7 @@ impl AdversarialBehaviorTestData { // Configure kickout threshold at 50%. config.block_producer_kickout_threshold = 50; config.chunk_producer_kickout_threshold = 50; + config.chunk_validator_only_kickout_threshold = 50; } let env = TestEnv::builder(&genesis.config) .clock(clock.clock()) @@ -170,10 +171,12 @@ fn test_non_adversarial_case() { for i in 0..test.num_validators { debug!(target: "test", "Processing block {} as validator #{}", height, i); + let signer = test.env.clients[i].validator_signer.get(); let _ = test.env.clients[i].start_process_block( block.clone().into(), if i == 0 { Provenance::PRODUCED } else { Provenance::NONE }, None, + &signer, ); let mut accepted_blocks = test.env.clients[i].finish_block_in_processing(block.header().hash()); @@ -221,7 +224,7 @@ fn test_banning_chunk_producer_when_seeing_invalid_chunk_base( checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION); let epoch_manager = test.env.clients[0].epoch_manager.clone(); let bad_chunk_producer = - test.env.clients[7].validator_signer.as_ref().unwrap().validator_id().clone(); + test.env.clients[7].validator_signer.get().unwrap().validator_id().clone(); let mut epochs_seen_invalid_chunk: HashSet = HashSet::new(); let mut last_block_skipped = false; for height in 1..=EPOCH_LENGTH * 4 + 5 { @@ -256,7 +259,7 @@ fn test_banning_chunk_producer_when_seeing_invalid_chunk_base( if &chunk_producer == &bad_chunk_producer { invalid_chunks_in_this_block.insert(shard_id); if !epochs_seen_invalid_chunk.contains(&epoch_id) { - epochs_seen_invalid_chunk.insert(epoch_id.clone()); + epochs_seen_invalid_chunk.insert(epoch_id); // This is the first block with invalid chunks in the current epoch. // In pre-stateless validation protocol the first block with invalid chunks @@ -304,10 +307,12 @@ fn test_banning_chunk_producer_when_seeing_invalid_chunk_base( // The block producer of course has the complete block so we can process that. for i in 0..test.num_validators { debug!(target: "test", "Processing block {} as validator #{}", height, i); + let signer = test.env.clients[i].validator_signer.get(); let _ = test.env.clients[i].start_process_block( block.clone().into(), if i == 0 { Provenance::PRODUCED } else { Provenance::NONE }, None, + &signer, ); let mut accepted_blocks = test.env.clients[i].finish_block_in_processing(block.header().hash()); diff --git a/integration-tests/src/tests/client/features/chunk_nodes_cache.rs b/integration-tests/src/tests/client/features/chunk_nodes_cache.rs index 274e3e06a72..dc42a9c0444 100644 --- a/integration-tests/src/tests/client/features/chunk_nodes_cache.rs +++ b/integration-tests/src/tests/client/features/chunk_nodes_cache.rs @@ -19,7 +19,7 @@ use nearcore::test_utils::TestEnvNightshadeSetupExt; fn process_transaction( env: &mut TestEnv, - signer: &dyn Signer, + signer: &Signer, num_blocks: BlockHeightDelta, protocol_version: ProtocolVersion, ) -> CryptoHash { @@ -105,7 +105,8 @@ fn compare_node_counts() { 1, ); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx_node_counts: Vec = (0..4) .map(|i| { let touching_trie_node_cost: Gas = 16_101_955_926; diff --git a/integration-tests/src/tests/client/features/congestion_control.rs b/integration-tests/src/tests/client/features/congestion_control.rs index 65a29fa3670..ee50918717f 100644 --- a/integration-tests/src/tests/client/features/congestion_control.rs +++ b/integration-tests/src/tests/client/features/congestion_control.rs @@ -1,3 +1,5 @@ +use assert_matches::assert_matches; +use near_chain::Provenance; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; @@ -6,7 +8,9 @@ use near_o11y::testonly::init_test_logger; use near_parameters::{RuntimeConfig, RuntimeConfigStore}; use near_primitives::account::id::AccountId; use near_primitives::congestion_info::{CongestionControl, CongestionInfo}; -use near_primitives::errors::{ActionErrorKind, FunctionCallError, TxExecutionError}; +use near_primitives::errors::{ + ActionErrorKind, FunctionCallError, InvalidTxError, TxExecutionError, +}; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardLayout; use near_primitives::sharding::{ShardChunk, ShardChunkHeader}; @@ -16,20 +20,22 @@ use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use near_primitives::views::FinalExecutionStatus; use near_vm_runner::logic::ProtocolVersion; use nearcore::test_utils::TestEnvNightshadeSetupExt; +use node_runtime::bootstrap_congestion_info; use std::sync::Arc; const CONTRACT_ID: &str = "contract.test0"; fn setup_runtime(sender_id: AccountId, protocol_version: ProtocolVersion) -> TestEnv { let mut genesis = Genesis::test_sharded_new_version(vec![sender_id], 1, vec![1, 1, 1, 1]); - genesis.config.epoch_length = 5; + genesis.config.epoch_length = 10; genesis.config.protocol_version = protocol_version; // Chain must be sharded to test cross-shard congestion control. genesis.config.shard_layout = ShardLayout::v1_test(); let mut config = RuntimeConfig::test(); // Make 1 wasm op cost ~4 GGas, to let "loop_forever" finish more quickly. - config.wasm_config.regular_op_cost = u32::MAX; + let wasm_config = Arc::make_mut(&mut config.wasm_config); + wasm_config.regular_op_cost = u32::MAX; let runtime_configs = vec![RuntimeConfigStore::with_one_config(config)]; TestEnv::builder(&genesis.config) @@ -52,12 +58,12 @@ fn setup_contract(env: &mut TestEnv) { let mut nonce = 1; let create_contract_tx = SignedTransaction::create_contract( nonce, - signer_id.clone(), + signer_id, CONTRACT_ID.parse().unwrap(), contract.to_vec(), 10 * 10u128.pow(24), PublicKey::from_seed(KeyType::ED25519, CONTRACT_ID), - &signer, + &signer.clone().into(), *block.hash(), ); // this adds the tx to the pool and then produces blocks until the tx result is available @@ -81,10 +87,77 @@ fn setup_contract(env: &mut TestEnv) { ); } +/// Check that the congestion info is correctly bootstrapped, updated and +/// propagated from chunk extra to chunk header. If the +/// `check_congested_protocol_upgrade` flag is set check that the chain is under +/// congestion during the protocol upgrade. +fn check_congestion_info(env: &TestEnv, check_congested_protocol_upgrade: bool) { + let client = &env.clients[0]; + let genesis_height = client.chain.genesis().height(); + let head_height = client.chain.head().unwrap().height; + + let mut check_congested_protocol_upgrade_done = false; + + for height in genesis_height..head_height + 1 { + let block = client.chain.get_block_by_height(height); + let Ok(block) = block else { + continue; + }; + + let prev_hash = block.header().prev_hash(); + let epoch_id = client.epoch_manager.get_epoch_id(block.hash()).unwrap(); + let protocol_config = client.runtime_adapter.get_protocol_config(&epoch_id).unwrap(); + let runtime_config = protocol_config.runtime_config; + + for chunk in block.chunks().iter() { + let shard_id = chunk.shard_id(); + let prev_state_root = chunk.prev_state_root(); + + let trie = client + .chain + .runtime_adapter + .get_trie_for_shard(shard_id, prev_hash, prev_state_root, false) + .unwrap(); + let mut computed_congestion_info = + bootstrap_congestion_info(&trie, &runtime_config, shard_id).unwrap(); + + tracing::info!(target: "test", ?epoch_id, ?height, ?shard_id, ?computed_congestion_info, "checking congestion info"); + + let header_congestion_info = chunk.congestion_info(); + let Some(header_congestion_info) = header_congestion_info else { + continue; + }; + + // Do not check the allowed shard as it's set separately from the + // bootstrapping logic. + computed_congestion_info.set_allowed_shard(header_congestion_info.allowed_shard()); + + assert_eq!( + header_congestion_info, computed_congestion_info, + "congestion info mismatch at height {} for shard {}", + height, shard_id + ); + + if shard_id == 1 + && check_congested_protocol_upgrade + && !check_congested_protocol_upgrade_done + { + let congestion_level = header_congestion_info + .localized_congestion_level(&runtime_config.congestion_control_config); + assert!(congestion_level > 0.0, "the congestion level should be non-zero for the congested shard during protocol upgrade"); + + check_congested_protocol_upgrade_done = true; + } + } + } +} + /// Simplest possible upgrade to new protocol with congestion control enabled, /// no traffic at all. #[test] fn test_protocol_upgrade_simple() { + init_test_logger(); + // The following only makes sense to test if the feature is enabled in the current build. if !ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { return; @@ -121,15 +194,18 @@ fn test_protocol_upgrade_simple() { .expect("chunk header must have congestion info after upgrade"); let congestion_control = CongestionControl::new(config, congestion_info, 0); assert_eq!(congestion_control.congestion_level(), 0.0); - assert!(congestion_control.shard_accepts_transactions()); + assert!(congestion_control.shard_accepts_transactions().is_yes()); } + + let check_congested_protocol_upgrade = false; + check_congestion_info(&env, check_congested_protocol_upgrade); } fn head_congestion_control_config( env: &TestEnv, ) -> near_parameters::config::CongestionControlConfig { let block = env.clients[0].chain.get_head_block().unwrap(); - let runtime_config = env.get_runtime_config(0, block.header().epoch_id().clone()); + let runtime_config = env.get_runtime_config(0, *block.header().epoch_id()); runtime_config.congestion_control_config } @@ -209,11 +285,23 @@ fn test_protocol_upgrade_under_congestion() { ); // Also check that the congested shard is still making progress. - env.produce_block(0, tip.height + 1); - let next_congestion_info = head_congestion_info(&mut env, contract_shard_id); + let block = env.clients[0].produce_block(tip.height + 1).unwrap().unwrap(); + assert_eq!( + block.header().chunk_mask()[contract_shard_id as usize], + true, + "chunk isn't missing" + ); + let gas_used = block.chunks().get(contract_shard_id as usize).unwrap().prev_gas_used(); + tracing::debug!(target: "test", "prev_gas_used: {}", gas_used); + + // The chunk should process at least 500TGas worth of receipts + assert!(gas_used > 500_000_000_000_000); + + env.process_block(0, block, Provenance::PRODUCED); - assert!(congestion_info.delayed_receipts_gas() > next_congestion_info.delayed_receipts_gas()); - assert!(congestion_info.receipt_bytes() > next_congestion_info.receipt_bytes()); + let check_congested_protocol_upgrade = true; + check_congestion_info(&env, check_congested_protocol_upgrade); + env.print_summary(); } /// Check we are still in the old version and no congestion info is shared. @@ -249,7 +337,7 @@ fn new_fn_call_100tgas( nonce, signer.account_id.clone(), CONTRACT_ID.parse().unwrap(), - signer, + &signer.clone().into(), deposit, // easy way to burn all attached gas "loop_forever".to_owned(), @@ -275,7 +363,7 @@ fn new_cheap_fn_call( nonce, signer.account_id.clone(), receiver, - signer, + &signer.clone().into(), deposit, "foo_does_not_exists".to_owned(), vec![], @@ -292,7 +380,11 @@ fn submit_n_100tgas_fns(env: &mut TestEnv, n: u32, nonce: &mut u64, signer: &InM let fn_tx = new_fn_call_100tgas(nonce, signer, *block.hash()); // this only adds the tx to the pool, no chain progress is made let response = env.clients[0].process_tx(fn_tx, false, false); - assert_eq!(response, ProcessTxResponse::ValidTx); + match response { + ProcessTxResponse::ValidTx + | ProcessTxResponse::InvalidTx(InvalidTxError::ShardCongested { .. }) => (), + other => panic!("unexpected result from submitting tx: {other:?}"), + } } } @@ -565,3 +657,53 @@ fn measure_tx_limit( local_tx_included_with_congestion, ) } + +/// Test that RPC clients stop accepting transactions when the receiver is +/// congested. +#[test] +fn test_rpc_client_rejection() { + let sender_id: AccountId = "test0".parse().unwrap(); + let mut env = setup_runtime(sender_id.clone(), PROTOCOL_VERSION); + + // prepare a contract to call + setup_contract(&mut env); + + let signer = InMemorySigner::from_seed(sender_id.clone(), KeyType::ED25519, sender_id.as_str()); + let mut nonce = 10; + + // Check we can send transactions at the start. + let fn_tx = new_fn_call_100tgas( + &mut nonce, + &signer, + *env.clients[0].chain.head_header().unwrap().hash(), + ); + let response = env.clients[0].process_tx(fn_tx, false, false); + assert_eq!(response, ProcessTxResponse::ValidTx); + + // Congest the network with a burst of 100 PGas. + submit_n_100tgas_fns(&mut env, 1_000, &mut nonce, &signer); + + // Allow transactions to enter the chain and enough receipts to arrive at + // the receiver shard for it to become congested. + let tip = env.clients[0].chain.head().unwrap(); + for i in 1..10 { + env.produce_block(0, tip.height + i); + } + + // Check that congestion control rejects new transactions. + let fn_tx = new_fn_call_100tgas( + &mut nonce, + &signer, + *env.clients[0].chain.head_header().unwrap().hash(), + ); + let response = env.clients[0].process_tx(fn_tx, false, false); + + if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { + assert_matches!( + response, + ProcessTxResponse::InvalidTx(InvalidTxError::ShardCongested { .. }) + ); + } else { + assert_eq!(response, ProcessTxResponse::ValidTx); + } +} diff --git a/integration-tests/src/tests/client/features/delegate_action.rs b/integration-tests/src/tests/client/features/delegate_action.rs index 356a41a1619..9078ad7c36e 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -7,7 +7,7 @@ use crate::node::{Node, RuntimeNode}; use crate::tests::standard_cases::fee_helper; use near_chain_configs::{Genesis, NEAR_BASE}; use near_client::test_utils::TestEnv; -use near_crypto::{KeyType, PublicKey, Signer}; +use near_crypto::{KeyType, PublicKey}; use near_parameters::ActionCosts; use near_primitives::account::{ id::AccountType, AccessKey, AccessKeyPermission, FunctionCallPermission, @@ -826,7 +826,12 @@ fn meta_tx_create_eth_implicit_account_fails() { fn meta_tx_create_and_use_implicit_account(new_account: AccountId) { let relayer = bob_account(); let sender = alice_account(); - let node = RuntimeNode::new(&relayer); + + let node = RuntimeNode::new_with_modified_config(&relayer, |runtime_config| { + // Increase the outgoing receipts limit to allow the large receipt to be processed immediately. + // Without this change the receipt would be processed somewhere in the next few blocks. + runtime_config.congestion_control_config.outgoing_receipts_usual_size_limit = 200_000; + }); // Check the account doesn't exist, yet. We will attempt creating it. node.view_account(&new_account).expect_err("account already exists"); diff --git a/integration-tests/src/tests/client/features/flat_storage.rs b/integration-tests/src/tests/client/features/flat_storage.rs index 27cdd55c029..488cd07310e 100644 --- a/integration-tests/src/tests/client/features/flat_storage.rs +++ b/integration-tests/src/tests/client/features/flat_storage.rs @@ -51,7 +51,8 @@ fn test_flat_storage_upgrade() { old_protocol_version, ); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer: Signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let gas = 20_000_000_000_000; let tx = TransactionV0 { signer_id: "test0".parse().unwrap(), diff --git a/integration-tests/src/tests/client/features/in_memory_tries.rs b/integration-tests/src/tests/client/features/in_memory_tries.rs index 13af8b8c72a..c49d815e7aa 100644 --- a/integration-tests/src/tests/client/features/in_memory_tries.rs +++ b/integration-tests/src/tests/client/features/in_memory_tries.rs @@ -4,7 +4,7 @@ use near_chain::{Block, Provenance}; use near_chain_configs::test_genesis::TestGenesisBuilder; use near_chunks::shards_manager_actor::CHUNK_REQUEST_SWITCH_TO_FULL_FETCH; -use near_chunks::test_loop::ShardsManagerResendChunkRequests; +use near_chunks::test_utils::ShardsManagerResendChunkRequests; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; use near_o11y::testonly::init_test_logger; @@ -277,7 +277,7 @@ fn run_chain_for_some_blocks_while_sending_money_around( *nonce, sender.clone(), receiver.clone(), - &create_user_test_signer(&sender), + &create_user_test_signer(&sender).into(), ONE_NEAR, tip.last_block_hash, ); diff --git a/integration-tests/src/tests/client/features/increase_storage_compute_cost.rs b/integration-tests/src/tests/client/features/increase_storage_compute_cost.rs index b319e4ab3fe..d7d7c9dbd1f 100644 --- a/integration-tests/src/tests/client/features/increase_storage_compute_cost.rs +++ b/integration-tests/src/tests/client/features/increase_storage_compute_cost.rs @@ -13,7 +13,7 @@ use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; -use near_crypto::{InMemorySigner, KeyType}; +use near_crypto::{InMemorySigner, KeyType, Signer}; use near_parameters::RuntimeConfigStore; use near_parameters::{ActionCosts, RuntimeConfig}; use near_primitives::sharding::ShardChunk; @@ -301,14 +301,15 @@ fn produce_saturated_chunk( gas, deposit: 0, }))]; - let signer = - InMemorySigner::from_seed(user_account.clone(), KeyType::ED25519, user_account.as_ref()); + let signer: Signer = + InMemorySigner::from_seed(user_account.clone(), KeyType::ED25519, user_account.as_ref()) + .into(); let tip = env.clients[0].chain.head().unwrap(); let mut tx_factory = || { let tx = SignedTransaction::from_actions( *nonce, - signer.account_id.clone(), + user_account.clone(), contract_account.clone(), &signer, actions.clone(), diff --git a/integration-tests/src/tests/client/features/limit_contract_functions_number.rs b/integration-tests/src/tests/client/features/limit_contract_functions_number.rs index a1d5ec92638..c8d40a165df 100644 --- a/integration-tests/src/tests/client/features/limit_contract_functions_number.rs +++ b/integration-tests/src/tests/client/features/limit_contract_functions_number.rs @@ -70,8 +70,8 @@ fn verify_contract_limits_upgrade( // Check that we can't call a contract exceeding functions number limit after upgrade. // Disabled in nightly due to https://github.com/near/nearcore/issues/8590 -#[cfg(all(not(feature = "nightly"), not(feature = "statelessnet_protocol")))] #[test] +#[ignore] fn test_function_limit_change() { verify_contract_limits_upgrade( ProtocolFeature::LimitContractFunctionsNumber, diff --git a/integration-tests/src/tests/client/features/lower_storage_key_limit.rs b/integration-tests/src/tests/client/features/lower_storage_key_limit.rs index 9e065624cb5..aa136534bf2 100644 --- a/integration-tests/src/tests/client/features/lower_storage_key_limit.rs +++ b/integration-tests/src/tests/client/features/lower_storage_key_limit.rs @@ -60,7 +60,8 @@ fn protocol_upgrade() { env }; - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer: Signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx = TransactionV0 { signer_id: "test0".parse().unwrap(), receiver_id: "test0".parse().unwrap(), diff --git a/integration-tests/src/tests/client/features/multinode_test_loop_example.rs b/integration-tests/src/tests/client/features/multinode_test_loop_example.rs deleted file mode 100644 index df9b049b6ae..00000000000 --- a/integration-tests/src/tests/client/features/multinode_test_loop_example.rs +++ /dev/null @@ -1,578 +0,0 @@ -use derive_enum_from_into::{EnumFrom, EnumTryInto}; -use near_async::futures::FutureSpawner; -use near_async::messaging::{noop, IntoMultiSender, IntoSender, MessageWithCallback, SendAsync}; -use near_async::test_loop::adhoc::{handle_adhoc_events, AdhocEvent, AdhocEventSender}; -use near_async::test_loop::event_handler::ignore_events; -use near_async::test_loop::futures::{ - drive_async_computations, drive_futures, TestLoopAsyncComputationEvent, - TestLoopDelayedActionEvent, TestLoopTask, -}; -use near_async::test_loop::TestLoopBuilder; -use near_async::time::Duration; -use near_chain::chunks_store::ReadOnlyChunksStore; -use near_chain::state_snapshot_actor::{ - get_delete_snapshot_callback, get_make_snapshot_callback, SnapshotCallbacks, - StateSnapshotActor, StateSnapshotSenderForClient, StateSnapshotSenderForClientMessage, - StateSnapshotSenderForStateSnapshot, StateSnapshotSenderForStateSnapshotMessage, -}; -use near_chain::test_utils::test_loop::{ - forward_state_snapshot_messages_from_client, - forward_state_snapshot_messages_from_state_snapshot, -}; -use near_chain::types::RuntimeAdapter; -use near_chain::ChainGenesis; -use near_chain_configs::test_genesis::TestGenesisBuilder; -use near_chain_configs::{ - ClientConfig, DumpConfig, ExternalStorageConfig, ExternalStorageLocation, StateSyncConfig, - SyncConfig, -}; -use near_chunks::adapter::ShardsManagerRequestFromClient; -use near_chunks::client::ShardsManagerResponse; -use near_chunks::shards_manager_actor::ShardsManagerActor; -use near_chunks::test_loop::{ - forward_client_request_to_shards_manager, forward_network_request_to_shards_manager, - route_shards_manager_network_messages, -}; -use near_client::client_actor::{ - ClientActorInner, ClientSenderForClientMessage, ClientSenderForPartialWitnessMessage, - SyncJobsSenderForClientMessage, -}; -use near_client::sync::sync_actor::SyncActor; -use near_client::sync_jobs_actor::{ClientSenderForSyncJobsMessage, SyncJobsActor}; -use near_client::test_utils::test_loop::client_actor::{ - forward_client_messages_from_client_to_client_actor, - forward_client_messages_from_network_to_client_actor, - forward_client_messages_from_shards_manager, forward_client_messages_from_sync_adapter, - forward_client_messages_from_sync_jobs_to_client_actor, -}; -use near_client::test_utils::test_loop::partial_witness_actor::{ - forward_messages_from_client_to_partial_witness_actor, - forward_messages_from_network_to_partial_witness_actor, -}; -use near_client::test_utils::test_loop::sync_actor::{ - forward_sync_actor_messages_from_client, forward_sync_actor_messages_from_network, - test_loop_sync_actor_maker, TestSyncActors, -}; -use near_client::test_utils::test_loop::sync_jobs_actor::forward_messages_from_client_to_sync_jobs_actor; -use near_client::test_utils::test_loop::{ - forward_messages_from_partial_witness_actor_to_client, - print_basic_client_info_before_each_event, -}; -use near_client::test_utils::test_loop::{route_network_messages_to_client, ClientQueries}; -use near_client::{ - Client, PartialWitnessActor, PartialWitnessSenderForClientMessage, SyncAdapter, SyncMessage, -}; -use near_epoch_manager::shard_tracker::{ShardTracker, TrackedConfig}; -use near_epoch_manager::EpochManager; -use near_network::client::{ - ClientSenderForNetwork, ClientSenderForNetworkMessage, ProcessTxRequest, -}; -use near_network::shards_manager::ShardsManagerRequestFromNetwork; -use near_network::state_sync::StateSyncResponse; -use near_network::state_witness::PartialWitnessSenderForNetworkMessage; -use near_network::types::{PeerManagerMessageRequest, PeerManagerMessageResponse, SetChainInfo}; -use near_primitives::network::PeerId; -use near_primitives::shard_layout::ShardUId; -use near_primitives::test_utils::{create_test_signer, create_user_test_signer}; -use near_primitives::transaction::SignedTransaction; -use near_primitives::types::AccountId; -use near_store::config::StateSnapshotType; -use near_store::genesis::initialize_genesis_state; -use near_store::{NodeStorage, StoreConfig, TrieConfig}; -use near_vm_runner::ContractRuntimeCache; -use near_vm_runner::FilesystemContractRuntimeCache; -use nearcore::state_sync::StateSyncDumper; -use nearcore::NightshadeRuntime; -use std::collections::HashMap; -use std::sync::{Arc, Mutex, RwLock}; - -#[derive(derive_more::AsMut, derive_more::AsRef)] -struct TestData { - pub dummy: (), - pub account: AccountId, - pub client: ClientActorInner, - pub sync_jobs: SyncJobsActor, - pub shards_manager: ShardsManagerActor, - pub partial_witness: PartialWitnessActor, - pub sync_actors: TestSyncActors, - pub state_sync_dumper: StateSyncDumper, - pub state_snapshot: StateSnapshotActor, -} - -impl AsMut for TestData { - fn as_mut(&mut self) -> &mut Self { - self - } -} - -impl AsRef for TestData { - fn as_ref(&self) -> &Client { - &self.client.client - } -} - -#[derive(EnumTryInto, Debug, EnumFrom)] -#[allow(clippy::large_enum_variant)] -enum TestEvent { - /// Allows futures to be spawn and executed. - Task(Arc), - /// Allows adhoc events to be used for the test (only used inside this file). - Adhoc(AdhocEvent), - /// Allows asynchronous computation (chunk application, stateless validation, etc.). - AsyncComputation(TestLoopAsyncComputationEvent), - - /// Allows delayed actions to be posted, as if ClientActor scheduled them, e.g. timers. - ClientDelayedActions(TestLoopDelayedActionEvent), - /// Allows delayed actions to be posted, as if ShardsManagerActor scheduled them, e.g. timers. - ShardsManagerDelayedActions(TestLoopDelayedActionEvent), - /// Allows delayed actions to be posted, as if SyncJobsActor scheduled them, e.g. timers. - SyncJobsDelayedActions(TestLoopDelayedActionEvent), - - /// Message that the network layer sends to the client. - ClientEventFromNetwork(ClientSenderForNetworkMessage), - /// Message that the client sends to the client itself. - ClientEventFromClient(ClientSenderForClientMessage), - /// Message that the SyncJobs component sends to the client. - ClientEventFromSyncJobs(ClientSenderForSyncJobsMessage), - /// Message that the ShardsManager component sends to the client. - ClientEventFromShardsManager(ShardsManagerResponse), - /// Message that the state sync adapter sends to the client. - ClientEventFromStateSyncAdapter(SyncMessage), - - /// Message that the client sends to the SyncJobs component. - SyncJobsEventFromClient(SyncJobsSenderForClientMessage), - - /// Message that the client sends to the SyncActor component. - SyncActorEventFromClient((ShardUId, SyncMessage)), - /// Message that the network sends to the SyncActor component. - SyncActorEventFromNetwork((ShardUId, StateSyncResponse)), - - /// Message that the client sends to the ShardsManager component. - ShardsManagerRequestFromClient(ShardsManagerRequestFromClient), - /// Message that the network layer sends to the ShardsManager component. - ShardsManagerRequestFromNetwork(ShardsManagerRequestFromNetwork), - - /// Message that the client sends to StateSnapshotActor. - StateSnapshotRequestFromClient(StateSnapshotSenderForClientMessage), - /// Message that the StateSnapshotActor sends to itself. - StateSnapshotRequestFromStateSnapshot(StateSnapshotSenderForStateSnapshotMessage), - - /// Outgoing network message that is sent by any of the components of this node. - OutgoingNetworkMessage(PeerManagerMessageRequest), - /// Same as OutgoingNetworkMessage, but of the variant that requests a response. - OutgoingNetworkMessageForResult( - MessageWithCallback, - ), - /// Calls to the network component to set chain info. - SetChainInfo(SetChainInfo), - /// Message from Client to PartialWitnessActor. - PartialWitnessSenderForClient(PartialWitnessSenderForClientMessage), - /// Message from Network to PartialWitnessActor. - PartialWitnessSenderForNetwork(PartialWitnessSenderForNetworkMessage), - /// Message from PartialWitnessActor to Client. - ClientSenderForPartialWitness(ClientSenderForPartialWitnessMessage), -} - -const ONE_NEAR: u128 = 1_000_000_000_000_000_000_000_000; - -#[test] -fn test_client_with_multi_test_loop() { - const NUM_CLIENTS: usize = 4; - const NETWORK_DELAY: Duration = Duration::milliseconds(10); - let builder = TestLoopBuilder::<(usize, TestEvent)>::new(); - - let initial_balance = 10000 * ONE_NEAR; - let accounts = - (0..100).map(|i| format!("account{}", i).parse().unwrap()).collect::>(); - - let mut genesis_builder = TestGenesisBuilder::new(); - genesis_builder - .genesis_time_from_clock(&builder.clock()) - .protocol_version_latest() - .genesis_height(10000) - .gas_prices_free() - .gas_limit_one_petagas() - .shard_layout_simple_v1(&["account3", "account5", "account7"]) - .transaction_validity_period(1000) - .epoch_length(10) - .validators_desired_roles( - &(0..NUM_CLIENTS).map(|idx| accounts[idx].as_str()).collect::>(), - &[], - ) - .shuffle_shard_assignment_for_chunk_producers(true); - for account in &accounts { - genesis_builder.add_user_account_simple(account.clone(), initial_balance); - } - let genesis = genesis_builder.build(); - - let tempdir = tempfile::tempdir().unwrap(); - let mut datas = Vec::new(); - for idx in 0..NUM_CLIENTS { - let mut client_config = ClientConfig::test(true, 600, 2000, 4, false, true, false, false); - client_config.max_block_wait_delay = Duration::seconds(6); - client_config.state_sync_enabled = true; - client_config.state_sync_timeout = Duration::milliseconds(100); - let external_storage_location = - ExternalStorageLocation::Filesystem { root_dir: tempdir.path().join("state_sync") }; - client_config.state_sync = StateSyncConfig { - dump: Some(DumpConfig { - iteration_delay: Some(Duration::seconds(1)), - location: external_storage_location.clone(), - credentials_file: None, - restart_dump_for_shards: None, - }), - sync: SyncConfig::ExternalStorage(ExternalStorageConfig { - location: external_storage_location, - num_concurrent_requests: 1, - num_concurrent_requests_during_catchup: 1, - }), - }; - client_config.tracked_shards = Vec::new(); - - let homedir = tempdir.path().join(format!("{}", idx)); - std::fs::create_dir_all(&homedir).expect("Unable to create homedir"); - - let store_config = StoreConfig { - path: Some(homedir.clone()), - load_mem_tries_for_tracked_shards: true, - max_open_files: 1000, - ..Default::default() - }; - let opener = NodeStorage::opener(&homedir, false, &store_config, None); - let store = opener.open().unwrap().get_hot_store(); - initialize_genesis_state(store.clone(), &genesis, None); - - let sync_jobs_actor = SyncJobsActor::new( - builder - .sender() - .for_index(idx) - .into_wrapped_multi_sender::(), - ); - let chain_genesis = ChainGenesis::new(&genesis.config); - let epoch_manager = EpochManager::new_arc_handle(store.clone(), &genesis.config); - let shard_tracker = - ShardTracker::new(TrackedConfig::from_config(&client_config), epoch_manager.clone()); - - let sync_actors = Arc::new(Mutex::new(HashMap::::new())); - let state_sync_adapter = Arc::new(RwLock::new(SyncAdapter::new( - builder.sender().for_index(idx).into_sender(), - builder.sender().for_index(idx).into_sender(), - test_loop_sync_actor_maker(builder.sender().for_index(idx), sync_actors.clone()), - ))); - let contract_cache = FilesystemContractRuntimeCache::new(&homedir, None::<&str>) - .expect("filesystem contract cache") - .handle(); - let runtime_adapter = NightshadeRuntime::test_with_trie_config( - &homedir, - store.clone(), - contract_cache, - &genesis.config, - epoch_manager.clone(), - TrieConfig::from_store_config(&store_config), - StateSnapshotType::EveryEpoch, - ); - - let state_snapshot = StateSnapshotActor::new( - runtime_adapter.get_flat_storage_manager(), - builder.sender().for_index(idx).into_multi_sender(), - runtime_adapter.get_tries(), - builder.sender().for_index(idx).into_wrapped_multi_sender::(), - ); - - let delete_snapshot_callback = get_delete_snapshot_callback( - builder.sender().for_index(idx).into_wrapped_multi_sender::(), - ); - let make_snapshot_callback = get_make_snapshot_callback( - builder.sender().for_index(idx).into_wrapped_multi_sender::(), - runtime_adapter.get_flat_storage_manager(), - ); - let snapshot_callbacks = - SnapshotCallbacks { make_snapshot_callback, delete_snapshot_callback }; - - let validator_signer = Arc::new(create_test_signer(accounts[idx].as_str())); - let client = Client::new( - builder.clock(), - client_config.clone(), - chain_genesis.clone(), - epoch_manager.clone(), - shard_tracker.clone(), - state_sync_adapter, - runtime_adapter.clone(), - builder.sender().for_index(idx).into_multi_sender(), - builder.sender().for_index(idx).into_sender(), - Some(validator_signer.clone()), - true, - [0; 32], - Some(snapshot_callbacks), - Arc::new( - builder - .sender() - .for_index(idx) - .into_async_computation_spawner(|_| Duration::milliseconds(80)), - ), - builder - .sender() - .for_index(idx) - .into_wrapped_multi_sender::(), - ) - .unwrap(); - - let shards_manager = ShardsManagerActor::new( - builder.clock(), - Some(accounts[idx].clone()), - epoch_manager.clone(), - shard_tracker.clone(), - builder.sender().for_index(idx).into_sender(), - builder.sender().for_index(idx).into_sender(), - ReadOnlyChunksStore::new(store.clone()), - client.chain.head().unwrap(), - client.chain.header_head().unwrap(), - Duration::milliseconds(100), - ); - - let client_actor = ClientActorInner::new( - builder.clock(), - client, - builder - .sender() - .for_index(idx) - .into_wrapped_multi_sender::(), - client_config.clone(), - PeerId::random(), - builder.sender().for_index(idx).into_multi_sender(), - None, - noop().into_sender(), - None, - Default::default(), - None, - builder - .sender() - .for_index(idx) - .into_wrapped_multi_sender::(), - Box::new(builder.sender().for_index(idx).into_future_spawner()), - ) - .unwrap(); - - let partial_witness_actions = PartialWitnessActor::new( - builder.clock(), - builder.sender().for_index(idx).into_multi_sender(), - builder - .sender() - .for_index(idx) - .into_wrapped_multi_sender::(), - validator_signer, - epoch_manager.clone(), - store, - ); - - let future_spawner = builder.sender().for_index(idx).into_future_spawner(); - let state_sync_dumper = StateSyncDumper { - clock: builder.clock(), - client_config, - chain_genesis, - epoch_manager, - shard_tracker, - runtime: runtime_adapter, - account_id: Some(accounts[idx].clone()), - dump_future_runner: Box::new(move |future| { - future_spawner.spawn_boxed("state_sync_dumper", future); - Box::new(|| {}) - }), - handle: None, - }; - - let data = TestData { - dummy: (), - account: accounts[idx].clone(), - client: client_actor, - sync_jobs: sync_jobs_actor, - shards_manager, - partial_witness: partial_witness_actions, - sync_actors, - state_sync_dumper, - state_snapshot, - }; - datas.push(data); - } - - let mut test = builder.build(datas); - for idx in 0..NUM_CLIENTS { - // Handlers that do nothing but print some information. - test.register_handler(print_basic_client_info_before_each_event(Some(idx)).for_index(idx)); - - // Futures, adhoc events, async computations. - test.register_handler(drive_futures().widen().for_index(idx)); - test.register_handler(handle_adhoc_events::().widen().for_index(idx)); - test.register_handler(drive_async_computations().widen().for_index(idx)); - - // Delayed actions. - test.register_delayed_action_handler_for_index::(idx); - test.register_delayed_action_handler_for_index::(idx); - - // Messages to the client. - test.register_handler( - forward_client_messages_from_network_to_client_actor().widen().for_index(idx), - ); - test.register_handler( - forward_client_messages_from_client_to_client_actor().widen().for_index(idx), - ); - test.register_handler( - forward_client_messages_from_sync_jobs_to_client_actor().widen().for_index(idx), - ); - test.register_handler(forward_client_messages_from_shards_manager().widen().for_index(idx)); - test.register_handler( - forward_messages_from_partial_witness_actor_to_client().widen().for_index(idx), - ); - test.register_handler(forward_client_messages_from_sync_adapter().widen().for_index(idx)); - - // Messages to the SyncJobs component. - test.register_handler( - forward_messages_from_client_to_sync_jobs_actor( - test.sender().for_index(idx).into_delayed_action_runner(test.shutting_down()), - ) - .widen() - .for_index(idx), - ); - - // Messages to the SyncActor component. - test.register_handler(forward_sync_actor_messages_from_client().widen().for_index(idx)); - test.register_handler(forward_sync_actor_messages_from_network().widen().for_index(idx)); - - // Messages to the ShardsManager component. - test.register_handler(forward_client_request_to_shards_manager().widen().for_index(idx)); - test.register_handler(forward_network_request_to_shards_manager().widen().for_index(idx)); - - // Messages to the StateSnapshotActor component. - test.register_handler( - forward_state_snapshot_messages_from_state_snapshot().widen().for_index(idx), - ); - test.register_handler(forward_state_snapshot_messages_from_client().widen().for_index(idx)); - - // Messages to the network layer; multi-node messages are handled below. - test.register_handler(ignore_events::().widen().for_index(idx)); - - // Messages to PartialWitnessActor. - test.register_handler( - forward_messages_from_client_to_partial_witness_actor().widen().for_index(idx), - ); - test.register_handler( - forward_messages_from_network_to_partial_witness_actor().widen().for_index(idx), - ); - } - // Handles network routing. Outgoing messages are handled by emitting incoming messages to the - // appropriate component of the appropriate node index. - test.register_handler(route_network_messages_to_client(test.sender(), NETWORK_DELAY)); - test.register_handler(route_shards_manager_network_messages( - test.sender(), - test.clock(), - NETWORK_DELAY, - )); - - // Bootstrap the test by starting the components. - // We use adhoc events for these, just so that the visualizer can see these as events rather - // than happening outside of the TestLoop framework. Other than that, we could also just remove - // the send_adhoc_event part and the test would still work. - for idx in 0..NUM_CLIENTS { - let sender = test.sender().for_index(idx); - let shutting_down = test.shutting_down(); - test.sender().for_index(idx).send_adhoc_event("start_client", move |data| { - data.client.start(&mut sender.into_delayed_action_runner(shutting_down)); - }); - - let sender = test.sender().for_index(idx); - let shutting_down = test.shutting_down(); - test.sender().for_index(idx).send_adhoc_event("start_shards_manager", move |data| { - data.shards_manager.periodically_resend_chunk_requests( - &mut sender.into_delayed_action_runner(shutting_down), - ); - }); - - test.sender().for_index(idx).send_adhoc_event("start_state_sync_dumper", move |data| { - data.state_sync_dumper.start().unwrap(); - }); - } - - // Give it some condition to stop running at. Here we run the test until the first client - // reaches height 10003, with a timeout of 5sec (failing if it doesn't reach 10003 in time). - test.run_until( - |data| data[0].client.client.chain.head().unwrap().height == 10003, - Duration::seconds(5), - ); - for idx in 0..NUM_CLIENTS { - test.sender().for_index(idx).send_adhoc_event("assertions", |data| { - let chain = &data.client.client.chain; - let block = chain.get_block_by_height(10002).unwrap(); - assert_eq!( - block.header().chunk_mask(), - &(0..NUM_CLIENTS).map(|_| true).collect::>() - ); - }) - } - test.run_instant(); - - let first_epoch_tracked_shards = test.data.tracked_shards_for_each_client(); - tracing::info!("First epoch tracked shards: {:?}", first_epoch_tracked_shards); - - let mut balances = accounts - .iter() - .cloned() - .map(|account| (account, initial_balance)) - .collect::>(); - - let anchor_hash = *test.data[0].client.client.chain.get_block_by_height(10002).unwrap().hash(); - for i in 0..accounts.len() { - let amount = ONE_NEAR * (i as u128 + 1); - let tx = SignedTransaction::send_money( - 1, - accounts[i].clone(), - accounts[(i + 1) % accounts.len()].clone(), - &create_user_test_signer(&accounts[i]), - amount, - anchor_hash, - ); - *balances.get_mut(&accounts[i]).unwrap() -= amount; - *balances.get_mut(&accounts[(i + 1) % accounts.len()]).unwrap() += amount; - drop( - test.sender() - .for_index(i % NUM_CLIENTS) - .with_additional_delay(Duration::milliseconds(300 * i as i64)) - .into_wrapped_multi_sender::( - ) - .send_async(ProcessTxRequest { - transaction: tx, - is_forwarded: false, - check_only: false, - }), - ); - } - - // Give plenty of time for these transactions to complete. - test.run_for(Duration::seconds(40)); - - // Make sure the chain progresses for several epochs. - test.run_until( - |data| data[0].client.client.chain.head().unwrap().height > 10050, - Duration::seconds(10), - ); - - for account in &accounts { - assert_eq!( - test.data.query_balance(account), - *balances.get(account).unwrap(), - "Account balance mismatch for account {}", - account - ); - } - - let later_epoch_tracked_shards = test.data.tracked_shards_for_each_client(); - tracing::info!("Later epoch tracked shards: {:?}", later_epoch_tracked_shards); - assert_ne!(first_epoch_tracked_shards, later_epoch_tracked_shards); - - for idx in 0..NUM_CLIENTS { - test.data[idx].state_sync_dumper.stop(); - } - - // Give the test a chance to finish off remaining events in the event loop, which can - // be important for properly shutting down the nodes. - test.shutdown_and_drain_remaining_events(Duration::seconds(20)); -} diff --git a/integration-tests/src/tests/client/features/nearvm.rs b/integration-tests/src/tests/client/features/nearvm.rs index f1b56bbb77a..93e2f4ae74c 100644 --- a/integration-tests/src/tests/client/features/nearvm.rs +++ b/integration-tests/src/tests/client/features/nearvm.rs @@ -42,7 +42,8 @@ fn test_nearvm_upgrade() { env }; - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer: Signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx = TransactionV0 { signer_id: "test0".parse().unwrap(), receiver_id: "test0".parse().unwrap(), diff --git a/integration-tests/src/tests/client/features/nonrefundable_transfer.rs b/integration-tests/src/tests/client/features/nonrefundable_transfer.rs index b0e8104a235..8b844d4cbb5 100644 --- a/integration-tests/src/tests/client/features/nonrefundable_transfer.rs +++ b/integration-tests/src/tests/client/features/nonrefundable_transfer.rs @@ -133,7 +133,7 @@ fn execute_transaction_from_actions( nonce + 1, signer.account_id.clone(), receiver, - signer, + &signer.clone().into(), actions, tip.last_block_hash, 0, diff --git a/integration-tests/src/tests/client/features/orphan_chunk_state_witness.rs b/integration-tests/src/tests/client/features/orphan_chunk_state_witness.rs index b2b523c2862..19772b777e6 100644 --- a/integration-tests/src/tests/client/features/orphan_chunk_state_witness.rs +++ b/integration-tests/src/tests/client/features/orphan_chunk_state_witness.rs @@ -1,18 +1,21 @@ use std::collections::HashSet; +use near_chain::stateless_validation::processing_tracker::{ + ProcessingDoneTracker, ProcessingDoneWaiter, +}; use near_chain::{Block, Provenance}; use near_chain_configs::default_orphan_state_witness_max_size; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_client::DistributeStateWitnessRequest; use near_client::HandleOrphanWitnessOutcome; -use near_client::{ProcessingDoneTracker, ProcessingDoneWaiter}; use near_o11y::testonly::init_integration_logger; use near_primitives::sharding::ShardChunkHeaderV3; use near_primitives::sharding::{ ChunkHash, ReceiptProof, ShardChunkHeader, ShardChunkHeaderInner, ShardProof, }; -use near_primitives::stateless_validation::EncodedChunkStateWitness; +use near_primitives::stateless_validation::ChunkStateWitness; +use near_primitives::stateless_validation::ChunkStateWitnessSize; use near_primitives::types::AccountId; use near_primitives_core::checked_feature; use near_primitives_core::version::PROTOCOL_VERSION; @@ -22,7 +25,7 @@ struct OrphanWitnessTestEnv { env: TestEnv, block1: Block, block2: Block, - encoded_witness: EncodedChunkStateWitness, + witness: ChunkStateWitness, excluded_validator: AccountId, excluded_validator_idx: usize, } @@ -127,12 +130,12 @@ fn setup_orphan_witness_test() -> OrphanWitnessTestEnv { // and process it on all validators except for `excluded_validator`. // The witness isn't processed on `excluded_validator` to give users of // `setup_orphan_witness_test()` full control over the events. - let mut encoded_witness_opt = None; + let mut witness_opt = None; let partial_witness_adapter = env.partial_witness_adapters[env.get_client_index(&block2_chunk_producer)].clone(); while let Some(request) = partial_witness_adapter.pop_distribution_request() { let DistributeStateWitnessRequest { epoch_id, chunk_header, state_witness } = request; - let (encoded_witness, _) = EncodedChunkStateWitness::encode(&state_witness).unwrap(); + let raw_witness_size = borsh_size(&state_witness); let chunk_validators = env .client(&block2_chunk_producer) .epoch_manager @@ -148,14 +151,20 @@ fn setup_orphan_witness_test() -> OrphanWitnessTestEnv { for account_id in chunk_validators.into_iter().filter(|acc| *acc != excluded_validator) { let processing_done_tracker = ProcessingDoneTracker::new(); witness_processing_done_waiters.push(processing_done_tracker.make_waiter()); - env.client(&account_id) - .process_chunk_state_witness(encoded_witness.clone(), Some(processing_done_tracker)) + let client = env.client(&account_id); + client + .process_chunk_state_witness( + state_witness.clone(), + raw_witness_size, + Some(processing_done_tracker), + client.validator_signer.get(), + ) .unwrap(); } for waiter in witness_processing_done_waiters { waiter.wait(); } - encoded_witness_opt = Some(encoded_witness); + witness_opt = Some(state_witness); } env.propagate_chunk_endorsements(false); @@ -167,11 +176,8 @@ fn setup_orphan_witness_test() -> OrphanWitnessTestEnv { block2.header().height(), "There should be no missing chunks." ); - let encoded_witness = encoded_witness_opt.unwrap(); - assert_eq!( - encoded_witness.decode().unwrap().0.chunk_header.chunk_hash(), - block2.chunks()[0].chunk_hash() - ); + let witness = witness_opt.unwrap(); + assert_eq!(witness.chunk_header.chunk_hash(), block2.chunks()[0].chunk_hash()); for client_idx in clients_without_excluded { let blocks_processed = env.clients[client_idx] @@ -189,7 +195,7 @@ fn setup_orphan_witness_test() -> OrphanWitnessTestEnv { env, block1, block2, - encoded_witness, + witness, excluded_validator, excluded_validator_idx, } @@ -209,7 +215,7 @@ fn test_orphan_witness_valid() { mut env, block1, block2, - encoded_witness, + witness, excluded_validator, excluded_validator_idx, .. @@ -217,7 +223,11 @@ fn test_orphan_witness_valid() { // `excluded_validator` receives witness for chunk belonging to `block2`, but it doesn't have `block1`. // The witness should become an orphaned witness and it should be saved to the orphan pool. - env.client(&excluded_validator).process_chunk_state_witness(encoded_witness, None).unwrap(); + let witness_size = borsh_size(&witness); + let client = env.client(&excluded_validator); + client + .process_chunk_state_witness(witness, witness_size, None, client.validator_signer.get()) + .unwrap(); let block_processed = env .client(&excluded_validator) @@ -240,10 +250,9 @@ fn test_orphan_witness_too_large() { return; } - let OrphanWitnessTestEnv { mut env, encoded_witness, excluded_validator, .. } = + let OrphanWitnessTestEnv { mut env, witness, excluded_validator, .. } = setup_orphan_witness_test(); - let witness = encoded_witness.decode().unwrap().0; // The witness should not be saved too the pool, as it's too big let outcome = env .client(&excluded_validator) @@ -265,17 +274,16 @@ fn test_orphan_witness_far_from_head() { return; } - let OrphanWitnessTestEnv { mut env, mut encoded_witness, block1, excluded_validator, .. } = + let OrphanWitnessTestEnv { mut env, mut witness, block1, excluded_validator, .. } = setup_orphan_witness_test(); let bad_height = 10000; - modify_witness_header_inner(&mut encoded_witness, |header| match &mut header.inner { + modify_witness_header_inner(&mut witness, |header| match &mut header.inner { ShardChunkHeaderInner::V1(inner) => inner.height_created = bad_height, ShardChunkHeaderInner::V2(inner) => inner.height_created = bad_height, ShardChunkHeaderInner::V3(inner) => inner.height_created = bad_height, }); - let witness = encoded_witness.decode().unwrap().0; let outcome = env.client(&excluded_validator).handle_orphan_state_witness(witness, 2000).unwrap(); assert_eq!( @@ -299,10 +307,9 @@ fn test_orphan_witness_not_fully_validated() { return; } - let OrphanWitnessTestEnv { mut env, mut encoded_witness, excluded_validator, .. } = + let OrphanWitnessTestEnv { mut env, mut witness, excluded_validator, .. } = setup_orphan_witness_test(); - let mut witness = encoded_witness.decode().unwrap().0; // Make the witness invalid in a way that won't be detected during orphan witness validation witness.source_receipt_proofs.insert( ChunkHash::default(), @@ -311,25 +318,29 @@ fn test_orphan_witness_not_fully_validated() { ShardProof { from_shard_id: 100230230, to_shard_id: 383939, proof: vec![] }, ), ); - encoded_witness = EncodedChunkStateWitness::encode(&witness).unwrap().0; // The witness should be accepted and saved into the pool, even though it's invalid. // There is no way to fully validate an orphan witness, so this is the correct behavior. // The witness will later be fully validated when the required block arrives. - env.client(&excluded_validator).process_chunk_state_witness(encoded_witness, None).unwrap(); + let witness_size = borsh_size(&witness); + let client = env.client(&excluded_validator); + client + .process_chunk_state_witness(witness, witness_size, None, client.validator_signer.get()) + .unwrap(); } fn modify_witness_header_inner( - encoded_witness: &mut EncodedChunkStateWitness, + witness: &mut ChunkStateWitness, f: impl FnOnce(&mut ShardChunkHeaderV3), ) { - let mut witness = encoded_witness.decode().unwrap().0; - match &mut witness.chunk_header { ShardChunkHeader::V3(header) => { f(header); } - _ => panic!(), + _ => unreachable!(), }; - *encoded_witness = EncodedChunkStateWitness::encode(&witness).unwrap().0; +} + +fn borsh_size(witness: &ChunkStateWitness) -> ChunkStateWitnessSize { + borsh::to_vec(&witness).unwrap().len() } diff --git a/integration-tests/src/tests/client/features/simple_test_loop_example.rs b/integration-tests/src/tests/client/features/simple_test_loop_example.rs deleted file mode 100644 index 853a767a224..00000000000 --- a/integration-tests/src/tests/client/features/simple_test_loop_example.rs +++ /dev/null @@ -1,208 +0,0 @@ -use derive_enum_from_into::{EnumFrom, EnumTryInto}; -use near_async::messaging::{noop, IntoMultiSender, IntoSender}; -use near_async::test_loop::adhoc::{handle_adhoc_events, AdhocEvent, AdhocEventSender}; -use near_async::test_loop::futures::{ - drive_async_computations, drive_futures, TestLoopAsyncComputationEvent, - TestLoopDelayedActionEvent, TestLoopTask, -}; -use near_async::test_loop::TestLoopBuilder; -use near_async::time::Duration; -use near_chain::chunks_store::ReadOnlyChunksStore; -use near_chain::ChainGenesis; -use near_chain_configs::test_genesis::TestGenesisBuilder; -use near_chain_configs::ClientConfig; -use near_chunks::adapter::ShardsManagerRequestFromClient; -use near_chunks::client::ShardsManagerResponse; -use near_chunks::shards_manager_actor::ShardsManagerActor; -use near_chunks::test_loop::forward_client_request_to_shards_manager; -use near_client::client_actor::{ - ClientActorInner, ClientSenderForClientMessage, SyncJobsSenderForClientMessage, -}; -use near_client::sync_jobs_actor::{ClientSenderForSyncJobsMessage, SyncJobsActor}; -use near_client::test_utils::test_loop::client_actor::{ - forward_client_messages_from_client_to_client_actor, - forward_client_messages_from_shards_manager, - forward_client_messages_from_sync_jobs_to_client_actor, -}; -use near_client::test_utils::test_loop::sync_jobs_actor::forward_messages_from_client_to_sync_jobs_actor; -use near_client::test_utils::{MAX_BLOCK_PROD_TIME, MIN_BLOCK_PROD_TIME}; -use near_client::{Client, SyncAdapter, SyncMessage}; -use near_epoch_manager::shard_tracker::{ShardTracker, TrackedConfig}; -use near_epoch_manager::EpochManager; -use near_network::client::ClientSenderForNetworkMessage; -use near_primitives::network::PeerId; - -use near_primitives::test_utils::create_test_signer; -use near_primitives::types::AccountId; - -use near_store::genesis::initialize_genesis_state; -use near_store::test_utils::create_test_store; -use nearcore::NightshadeRuntime; -use std::path::Path; -use std::sync::{Arc, RwLock}; - -#[derive(derive_more::AsMut, derive_more::AsRef)] -struct TestData { - pub dummy: (), - pub client: ClientActorInner, - pub sync_jobs: SyncJobsActor, - pub shards_manager: ShardsManagerActor, -} - -impl AsMut for TestData { - fn as_mut(&mut self) -> &mut Self { - self - } -} - -#[derive(EnumTryInto, Debug, EnumFrom)] -#[allow(clippy::large_enum_variant)] -enum TestEvent { - Task(Arc), - Adhoc(AdhocEvent), - AsyncComputation(TestLoopAsyncComputationEvent), - ClientDelayedActions(TestLoopDelayedActionEvent), - SyncJobsDelayedActions(TestLoopDelayedActionEvent), - ClientEventFromNetwork(ClientSenderForNetworkMessage), - ClientEventFromClient(ClientSenderForClientMessage), - ClientEventFromSyncJobs(ClientSenderForSyncJobsMessage), - ClientEventFromShardsManager(ShardsManagerResponse), - SyncJobsEventFromClient(SyncJobsSenderForClientMessage), - ShardsManagerRequestFromClient(ShardsManagerRequestFromClient), - ClientEventFromStateSyncAdapter(SyncMessage), -} - -const ONE_NEAR: u128 = 1_000_000_000_000_000_000_000_000; - -#[test] -fn test_client_with_simple_test_loop() { - let builder = TestLoopBuilder::::new(); - let sync_jobs_actor = SyncJobsActor::new( - builder.sender().into_wrapped_multi_sender::(), - ); - let client_config = ClientConfig::test( - true, - MIN_BLOCK_PROD_TIME.whole_milliseconds() as u64, - MAX_BLOCK_PROD_TIME.whole_milliseconds() as u64, - 4, - false, - true, - false, - false, - ); - let initial_balance = 10000 * ONE_NEAR; - let accounts = - (0..100).map(|i| format!("account{}", i).parse().unwrap()).collect::>(); - - let mut genesis_builder = TestGenesisBuilder::new(); - genesis_builder - .genesis_time_from_clock(&builder.clock()) - .protocol_version_latest() - .genesis_height(10000) - .gas_prices_free() - .gas_limit_one_petagas() - .shard_layout_simple_v1(&["account3", "account5", "account7"]) - .transaction_validity_period(1000) - .epoch_length(10) - .validators_desired_roles(&["account0"], &[]) - .shuffle_shard_assignment_for_chunk_producers(true); - for account in &accounts { - genesis_builder.add_user_account_simple(account.clone(), initial_balance); - } - let genesis = genesis_builder.build(); - - let store = create_test_store(); - initialize_genesis_state(store.clone(), &genesis, None); - - let chain_genesis = ChainGenesis::new(&genesis.config); - let epoch_manager = EpochManager::new_arc_handle(store.clone(), &genesis.config); - let shard_tracker = ShardTracker::new(TrackedConfig::AllShards, epoch_manager.clone()); - let state_sync_adapter = Arc::new(RwLock::new(SyncAdapter::new( - builder.sender().into_sender(), - noop().into_sender(), - SyncAdapter::actix_actor_maker(), - ))); - let runtime_adapter = NightshadeRuntime::test( - Path::new("."), - store.clone(), - &genesis.config, - epoch_manager.clone(), - ); - - let client = Client::new( - builder.clock(), - client_config.clone(), - chain_genesis, - epoch_manager.clone(), - shard_tracker.clone(), - state_sync_adapter, - runtime_adapter, - noop().into_multi_sender(), - builder.sender().into_sender(), - Some(Arc::new(create_test_signer(accounts[0].as_str()))), - true, - [0; 32], - None, - Arc::new(builder.sender().into_async_computation_spawner(|_| Duration::milliseconds(80))), - noop().into_multi_sender(), - ) - .unwrap(); - - let shards_manager = ShardsManagerActor::new( - builder.clock(), - Some(accounts[0].clone()), - epoch_manager, - shard_tracker, - noop().into_sender(), - builder.sender().into_sender(), - ReadOnlyChunksStore::new(store), - client.chain.head().unwrap(), - client.chain.header_head().unwrap(), - Duration::milliseconds(100), - ); - - let client_actor = ClientActorInner::new( - builder.clock(), - client, - builder.sender().into_wrapped_multi_sender::(), - client_config, - PeerId::random(), - noop().into_multi_sender(), - None, - noop().into_sender(), - None, - Default::default(), - None, - builder.sender().into_wrapped_multi_sender::(), - Box::new(builder.sender().into_future_spawner()), - ) - .unwrap(); - - let data = - TestData { dummy: (), client: client_actor, sync_jobs: sync_jobs_actor, shards_manager }; - - let mut test = builder.build(data); - test.register_handler(forward_client_messages_from_client_to_client_actor().widen()); - test.register_handler(forward_client_messages_from_sync_jobs_to_client_actor().widen()); - test.register_handler(forward_client_messages_from_shards_manager().widen()); - test.register_handler( - forward_messages_from_client_to_sync_jobs_actor( - test.sender().into_delayed_action_runner(test.shutting_down()), - ) - .widen(), - ); - test.register_handler(drive_futures().widen()); - test.register_handler(handle_adhoc_events::().widen()); - test.register_handler(drive_async_computations().widen()); - test.register_delayed_action_handler::(); - test.register_handler(forward_client_request_to_shards_manager().widen()); - // TODO: handle additional events. - - let mut delayed_runner = - test.sender().into_delayed_action_runner::(test.shutting_down()); - test.sender().send_adhoc_event("start_client", move |data| { - data.client.start(&mut delayed_runner); - }); - test.run_for(Duration::seconds(10)); - test.shutdown_and_drain_remaining_events(Duration::seconds(1)); -} diff --git a/integration-tests/src/tests/client/features/stateless_validation.rs b/integration-tests/src/tests/client/features/stateless_validation.rs index 3d48ea60433..11fc2d5b08e 100644 --- a/integration-tests/src/tests/client/features/stateless_validation.rs +++ b/integration-tests/src/tests/client/features/stateless_validation.rs @@ -1,33 +1,47 @@ +use crate::tests::client::features::wallet_contract::{ + create_rlp_execute_tx, view_balance, NearSigner, +}; +use near_client::{ProcessTxResponse, ProduceChunkResult}; use near_epoch_manager::{EpochManager, EpochManagerAdapter}; -use near_primitives::stateless_validation::{ChunkStateWitness, EncodedChunkStateWitness}; +use near_primitives::account::id::AccountIdRef; +use near_primitives::account::{AccessKeyPermission, FunctionCallPermission}; +use near_primitives::action::{Action, AddKeyAction, TransferAction}; +use near_primitives::stateless_validation::ChunkStateWitness; +use near_primitives::version::ProtocolFeature; use near_store::test_utils::create_test_store; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use std::collections::HashSet; -use near_chain::Provenance; +use near_chain::{Chain, Provenance}; use near_chain_configs::{Genesis, GenesisConfig, GenesisRecords}; -use near_client::test_utils::TestEnv; -use near_crypto::{InMemorySigner, KeyType}; +use near_client::test_utils::{create_chunk_with_transactions, TestEnv}; +use near_crypto::{InMemorySigner, KeyType, SecretKey}; use near_o11y::testonly::init_integration_logger; use near_primitives::epoch_manager::AllEpochConfigTestOverrides; use near_primitives::num_rational::Rational32; use near_primitives::shard_layout::ShardLayout; use near_primitives::state_record::StateRecord; -use near_primitives::test_utils::create_test_signer; +use near_primitives::test_utils::{create_test_signer, create_user_test_signer}; use near_primitives::transaction::SignedTransaction; use near_primitives::types::{AccountInfo, EpochId}; +use near_primitives::utils::derive_eth_implicit_account_id; +use near_primitives::version::{ProtocolVersion, PROTOCOL_VERSION}; use near_primitives::views::FinalExecutionStatus; use near_primitives_core::account::{AccessKey, Account}; use near_primitives_core::checked_feature; use near_primitives_core::hash::CryptoHash; use near_primitives_core::types::{AccountId, NumSeats}; -use near_primitives_core::version::PROTOCOL_VERSION; use nearcore::test_utils::TestEnvNightshadeSetupExt; const ONE_NEAR: u128 = 1_000_000_000_000_000_000_000_000; -fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { +fn run_chunk_validation_test( + seed: u64, + prob_missing_chunk: f64, + prob_missing_block: f64, + genesis_protocol_version: ProtocolVersion, +) { init_integration_logger(); if !checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) { @@ -37,7 +51,7 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { let initial_balance = 100 * ONE_NEAR; let validator_stake = 1000000 * ONE_NEAR; - let blocks_to_produce = 50; + let blocks_to_produce = if prob_missing_block > 0.0 { 200 } else { 50 }; let num_accounts = 9; let accounts = (0..num_accounts) .map(|i| format!("account{}", i).parse().unwrap()) @@ -54,7 +68,7 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { let mut genesis_config = GenesisConfig { // Use the latest protocol version. Otherwise, the version may be too // old that e.g. blocks don't even store previous heights. - protocol_version: PROTOCOL_VERSION, + protocol_version: genesis_protocol_version, // Some arbitrary starting height. Doesn't matter. genesis_height: 10000, shard_layout, @@ -85,6 +99,7 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { // missing chunks functionality. block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, // Needed to distribute full non-trivial reward to each validator if at // least some block/chunk was produced. // This itself is needed to make state transition on epoch boundary @@ -114,7 +129,7 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { 0, CryptoHash::default(), 0, - PROTOCOL_VERSION, + genesis_protocol_version, ), }); records.push(StateRecord::AccessKey { @@ -137,7 +152,17 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { let mut rng: StdRng = SeedableRng::seed_from_u64(seed); let mut found_differing_post_state_root_due_to_state_transitions = false; + + let tip = env.clients[0].chain.head().unwrap(); + let mut height = tip.height; + for round in 0..blocks_to_produce { + height += 1; + if rng.gen_bool(prob_missing_block) { + // Skip producing a block. + continue; + } + let heads = env .clients .iter() @@ -158,7 +183,7 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { round as u64, sender_account, receiver_account, - &signer, + &signer.into(), ONE_NEAR, tip.last_block_hash, ); @@ -166,12 +191,13 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { let _ = env.clients[0].process_tx(tx, false, false); } - let block_producer = env.get_block_producer_at_offset(&tip, 1); + let height_offset = height - tip.height; + let block_producer = env.get_block_producer_at_offset(&tip, height_offset); tracing::debug!( target: "client", - "Producing block at height {} by {}", tip.height + 1, block_producer + "Producing block at height {} by {}", height, block_producer ); - let block = env.client(&block_producer).produce_block(tip.height + 1).unwrap().unwrap(); + let block = env.client(&block_producer).produce_block(height).unwrap().unwrap(); // Apply the block. for i in 0..env.clients.len() { @@ -223,25 +249,69 @@ fn run_chunk_validation_test(seed: u64, prob_missing_chunk: f64) { if prob_missing_chunk >= 0.8 { assert!(found_differing_post_state_root_due_to_state_transitions); } + + let client = &env.clients[0]; + + let genesis = client.chain.genesis(); + let genesis_epoch_id = client.epoch_manager.get_epoch_id(genesis.hash()).unwrap(); + assert_eq!( + genesis_protocol_version, + client.epoch_manager.get_epoch_protocol_version(&genesis_epoch_id).unwrap() + ); + + let head = client.chain.head().unwrap(); + let head_epoch_id = client.epoch_manager.get_epoch_id(&head.last_block_hash).unwrap(); + assert_eq!( + PROTOCOL_VERSION, + client.epoch_manager.get_epoch_protocol_version(&head_epoch_id).unwrap() + ); + + env.print_summary(); } #[test] fn test_chunk_validation_no_missing_chunks() { - run_chunk_validation_test(42, 0.0); + run_chunk_validation_test(42, 0.0, 0.0, PROTOCOL_VERSION); } #[test] fn test_chunk_validation_low_missing_chunks() { - run_chunk_validation_test(43, 0.3); + run_chunk_validation_test(43, 0.3, 0.0, PROTOCOL_VERSION); } -// This test fails because transactions are rejected when there are too many -// missing chunks in a row. -// TODO(congestion_control) - make congestion control configurable, -// disable it here and re-enable this test #[test] fn test_chunk_validation_high_missing_chunks() { - run_chunk_validation_test(44, 0.81); + run_chunk_validation_test(44, 0.81, 0.0, PROTOCOL_VERSION); +} + +#[test] +fn test_chunk_validation_protocol_upgrade_no_missing() { + run_chunk_validation_test( + 42, + 0.0, + 0.0, + ProtocolFeature::StatelessValidationV0.protocol_version() - 1, + ); +} + +#[test] +fn test_chunk_validation_protocol_upgrade_low_missing_prob() { + run_chunk_validation_test( + 42, + 0.2, + 0.1, + ProtocolFeature::StatelessValidationV0.protocol_version() - 1, + ); +} + +#[test] +fn test_chunk_validation_protocol_upgrade_mid_missing_prob() { + run_chunk_validation_test( + 42, + 0.6, + 0.3, + ProtocolFeature::StatelessValidationV0.protocol_version() - 1, + ); } #[test] @@ -337,13 +407,311 @@ fn test_chunk_state_witness_bad_shard_id() { let previous_block = env.clients[0].chain.head().unwrap().prev_block_hash; let invalid_shard_id = 1000000000; let witness = ChunkStateWitness::new_dummy(upper_height, invalid_shard_id, previous_block); - let encoded_witness = EncodedChunkStateWitness::encode(&witness).unwrap().0; + let witness_size = borsh::to_vec(&witness).unwrap().len(); // Client should reject this ChunkStateWitness and the error message should mention "shard" tracing::info!(target: "test", "Processing invalid ChunkStateWitness"); - let res = env.clients[0].process_chunk_state_witness(encoded_witness, None); + let signer = env.clients[0].validator_signer.get(); + let res = env.clients[0].process_chunk_state_witness(witness, witness_size, None, signer); let error = res.unwrap_err(); let error_message = format!("{}", error).to_lowercase(); tracing::info!(target: "test", "error message: {}", error_message); assert!(error_message.contains("shard")); } + +/// Test that processing chunks with invalid transactions does not lead to panics +#[test] +fn test_invalid_transactions() { + let accounts = + vec!["test0".parse().unwrap(), "test1".parse().unwrap(), "test2".parse().unwrap()]; + let signers: Vec<_> = accounts + .iter() + .map(|account_id: &AccountId| { + create_user_test_signer(AccountIdRef::new(account_id.as_str()).unwrap()) + }) + .collect(); + let genesis = Genesis::test(accounts.clone(), 2); + let mut env = TestEnv::builder(&genesis.config) + .validators(accounts.clone()) + .clients(accounts.clone()) + .nightshade_runtimes(&genesis) + .build(); + let new_signer = create_user_test_signer(AccountIdRef::new("test3").unwrap()); + + let tip = env.clients[0].chain.head().unwrap(); + let sender_account = accounts[0].clone(); + let receiver_account = accounts[1].clone(); + let invalid_transactions = vec![ + // transaction with invalid balance + SignedTransaction::send_money( + 1, + sender_account.clone(), + receiver_account.clone(), + &signers[0].clone().into(), + u128::MAX, + tip.last_block_hash, + ), + // transaction with invalid nonce + SignedTransaction::send_money( + 0, + sender_account.clone(), + receiver_account.clone(), + &signers[0].clone().into(), + ONE_NEAR, + tip.last_block_hash, + ), + // transaction with invalid sender account + SignedTransaction::send_money( + 2, + "test3".parse().unwrap(), + receiver_account.clone(), + &new_signer.into(), + ONE_NEAR, + tip.last_block_hash, + ), + ]; + // Need to create a valid transaction with the same accounts touched in order to have some state witness generated + let valid_tx = SignedTransaction::send_money( + 1, + sender_account, + receiver_account, + &signers[0].clone().into(), + ONE_NEAR, + tip.last_block_hash, + ); + let mut start_height = 1; + for tx in invalid_transactions { + for height in start_height..start_height + 3 { + let tip = env.clients[0].chain.head().unwrap(); + let chunk_producer = env.get_chunk_producer_at_offset(&tip, 1, 0); + let block_producer = env.get_block_producer_at_offset(&tip, 1); + + let client = env.client(&chunk_producer); + let transactions = if height == start_height { vec![tx.clone()] } else { vec![] }; + if height == start_height { + let res = client.process_tx(valid_tx.clone(), false, false); + assert!(matches!(res, ProcessTxResponse::ValidTx)) + } + + let ( + ProduceChunkResult { + chunk, + encoded_chunk_parts_paths, + receipts, + transactions_storage_proof, + }, + _, + ) = create_chunk_with_transactions(client, transactions); + + let shard_chunk = client + .persist_and_distribute_encoded_chunk( + chunk, + encoded_chunk_parts_paths, + receipts, + client.validator_signer.get().unwrap().validator_id().clone(), + ) + .unwrap(); + let prev_block = client.chain.get_block(shard_chunk.prev_block()).unwrap(); + let prev_chunk_header = Chain::get_prev_chunk_header( + client.epoch_manager.as_ref(), + &prev_block, + shard_chunk.shard_id(), + ) + .unwrap(); + client + .send_chunk_state_witness_to_chunk_validators( + &client + .epoch_manager + .get_epoch_id_from_prev_block(shard_chunk.prev_block()) + .unwrap(), + prev_block.header(), + &prev_chunk_header, + &shard_chunk, + transactions_storage_proof, + &client.validator_signer.get(), + ) + .unwrap(); + + env.process_partial_encoded_chunks(); + for i in 0..env.clients.len() { + env.process_shards_manager_responses(i); + } + env.propagate_chunk_state_witnesses_and_endorsements(true); + let block = env.client(&block_producer).produce_block(height).unwrap().unwrap(); + for client in env.clients.iter_mut() { + client + .process_block_test_no_produce_chunk_allow_errors( + block.clone().into(), + Provenance::NONE, + ) + .unwrap(); + } + } + start_height += 3; + } +} + +/// Tests that eth-implicit accounts still work with stateless validation. +#[test] +fn test_eth_implicit_accounts() { + if !(checked_feature!("stable", StatelessValidationV0, PROTOCOL_VERSION) + && checked_feature!("stable", EthImplicitAccounts, PROTOCOL_VERSION)) + { + println!("Test not applicable without both StatelessValidation and eth-implicit accounts enabled"); + return; + } + + let accounts = + vec!["test0".parse().unwrap(), "test1".parse().unwrap(), "test2".parse().unwrap()]; + let genesis = Genesis::test(accounts.clone(), 2); + let mut env = TestEnv::builder(&genesis.config) + .validators(accounts.clone()) + .clients(accounts) + .nightshade_runtimes(&genesis) + .build(); + let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); + let signer = create_user_test_signer(AccountIdRef::new("test2").unwrap()); + + // 1. Create two eth-implicit accounts + let secret_key = SecretKey::from_seed(KeyType::SECP256K1, "test"); + let public_key = secret_key.public_key(); + let alice_eth_account = derive_eth_implicit_account_id(public_key.unwrap_as_secp256k1()); + let bob_eth_account: AccountId = "0x0000000000000000000000000000000000000b0b".parse().unwrap(); + + let alice_init_balance = 3 * ONE_NEAR; + let create_alice_tx = SignedTransaction::send_money( + 1, + signer.account_id.clone(), + alice_eth_account.clone(), + &signer.clone().into(), + alice_init_balance, + *genesis_block.hash(), + ); + + let bob_init_balance = 0; + let create_bob_tx = SignedTransaction::send_money( + 2, + signer.account_id.clone(), + bob_eth_account.clone(), + &signer.clone().into(), + bob_init_balance, + *genesis_block.hash(), + ); + + assert_eq!( + env.clients[0].process_tx(create_alice_tx, false, false), + ProcessTxResponse::ValidTx + ); + assert_eq!(env.clients[0].process_tx(create_bob_tx, false, false), ProcessTxResponse::ValidTx); + + // Process some blocks to ensure the transactions are complete. + for _ in 0..10 { + produce_block(&mut env); + } + + assert_eq!(view_balance(&env, &alice_eth_account), alice_init_balance); + assert_eq!(view_balance(&env, &bob_eth_account), bob_init_balance); + + // 2. Add function call access key to one eth-implicit account + let relayer_account_id = signer.account_id.clone(); + let mut relayer_signer = NearSigner { account_id: &relayer_account_id, signer }; + let relayer_pk = relayer_signer.signer.public_key.clone(); + let action = Action::AddKey(Box::new(AddKeyAction { + public_key: relayer_pk, + access_key: AccessKey { + nonce: 0, + permission: AccessKeyPermission::FunctionCall(FunctionCallPermission { + allowance: None, + receiver_id: alice_eth_account.to_string(), + method_names: vec!["rlp_execute".into()], + }), + }, + })); + let signed_transaction = create_rlp_execute_tx( + &alice_eth_account, + action, + 0, + &alice_eth_account, + &secret_key, + &mut relayer_signer, + &env, + ); + + assert_eq!( + env.clients[0].process_tx(signed_transaction, false, false), + ProcessTxResponse::ValidTx + ); + + for _ in 0..10 { + produce_block(&mut env); + } + + // Now the relayer can sign transactions on behalf of the implicit account + relayer_signer.account_id = &alice_eth_account; + + // 3. Use one implicit account to make a transfer to the other. + let transfer_amount = ONE_NEAR; + let action = Action::Transfer(TransferAction { deposit: transfer_amount }); + let signed_transaction = create_rlp_execute_tx( + &bob_eth_account, + action, + 1, + &alice_eth_account, + &secret_key, + &mut relayer_signer, + &env, + ); + + assert_eq!( + env.clients[0].process_tx(signed_transaction, false, false), + ProcessTxResponse::ValidTx + ); + + for _ in 0..10 { + produce_block(&mut env); + } + + let alice_final_balance = view_balance(&env, &alice_eth_account); + let bob_final_balance = view_balance(&env, &bob_eth_account); + + // Bob receives the transfer + assert_eq!(bob_final_balance, bob_init_balance + transfer_amount); + + // The only tokens lost in the transaction are due to gas + let gas_cost = + (alice_init_balance + bob_init_balance) - (alice_final_balance + bob_final_balance); + assert_eq!(alice_final_balance, alice_init_balance - transfer_amount - gas_cost); + assert!(gas_cost < ONE_NEAR / 500); +} + +/// Produce a block, apply it and propagate it through the network (including state witnesses). +fn produce_block(env: &mut TestEnv) { + let heads = env + .clients + .iter() + .map(|client| client.chain.head().unwrap().last_block_hash) + .collect::>(); + assert_eq!(heads.len(), 1, "All clients should have the same head"); + let tip = env.clients[0].chain.head().unwrap(); + let block_producer = env.get_block_producer_at_offset(&tip, 1); + let block = env.client(&block_producer).produce_block(tip.height + 1).unwrap().unwrap(); + + for i in 0..env.clients.len() { + let validator_id = env.get_client_id(i); + tracing::debug!( + target: "client", + "Applying block at height {} at {}", block.header().height(), validator_id + ); + let blocks_processed = + env.clients[i].process_block_test(block.clone().into(), Provenance::NONE).unwrap(); + assert_eq!(blocks_processed, vec![*block.hash()]); + } + + env.process_partial_encoded_chunks(); + for j in 0..env.clients.len() { + env.process_shards_manager_responses_and_finish_processing_blocks(j); + } + + env.propagate_chunk_state_witnesses(false); + env.propagate_chunk_endorsements(false); +} diff --git a/integration-tests/src/tests/client/features/storage_proof_size_limit.rs b/integration-tests/src/tests/client/features/storage_proof_size_limit.rs index 294e8ab9e5c..5f192be4f13 100644 --- a/integration-tests/src/tests/client/features/storage_proof_size_limit.rs +++ b/integration-tests/src/tests/client/features/storage_proof_size_limit.rs @@ -3,7 +3,7 @@ use near_chain::Provenance; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; -use near_crypto::{InMemorySigner, KeyType}; +use near_crypto::{InMemorySigner, KeyType, Signer}; use near_parameters::RuntimeConfigStore; use near_primitives::action::{Action, DeployContractAction, FunctionCallAction}; use near_primitives::checked_feature; @@ -54,8 +54,9 @@ fn test_storage_proof_size_limit() { // query the access key of the user. It's easier to keep a shared counter // that starts at 1 and increases monotonically. let mut nonce = 1; - let signer = - InMemorySigner::from_seed(user_account.clone(), KeyType::ED25519, user_account.as_ref()); + let signer: Signer = + InMemorySigner::from_seed(user_account.clone(), KeyType::ED25519, user_account.as_ref()) + .into(); // Write 1MB values under keys 0, 1, 2, 3, ..., 23. // 24MB of data in total @@ -69,7 +70,7 @@ fn test_storage_proof_size_limit() { let tx = SignedTransaction::from_actions( nonce, - signer.account_id.clone(), + user_account.clone(), contract_account.clone(), &signer, vec![action], @@ -92,7 +93,7 @@ fn test_storage_proof_size_limit() { })); let tx = SignedTransaction::from_actions( nonce, - signer.account_id.clone(), + user_account.clone(), contract_account.clone(), &signer, vec![action], diff --git a/integration-tests/src/tests/client/features/wallet_contract.rs b/integration-tests/src/tests/client/features/wallet_contract.rs index da5f7a605a6..ff65eccf148 100644 --- a/integration-tests/src/tests/client/features/wallet_contract.rs +++ b/integration-tests/src/tests/client/features/wallet_contract.rs @@ -65,7 +65,7 @@ fn view_request(env: &TestEnv, request: QueryRequest) -> QueryResponse { .unwrap() } -fn view_balance(env: &TestEnv, account: &AccountIdRef) -> u128 { +pub fn view_balance(env: &TestEnv, account: &AccountIdRef) -> u128 { let request = QueryRequest::ViewAccount { account_id: account.into() }; match view_request(&env, request).kind { QueryResponseKind::ViewAccount(view) => view.amount, @@ -100,7 +100,7 @@ fn test_eth_implicit_account_creation() { 1, signer.account_id.clone(), eth_implicit_account_id.clone(), - &signer, + &signer.into(), 0, *genesis_block.hash(), ); @@ -154,14 +154,14 @@ fn test_transaction_from_eth_implicit_account_fail() { let public_key = secret_key.public_key(); let eth_implicit_account_id = derive_eth_implicit_account_id(public_key.unwrap_as_secp256k1()); let eth_implicit_account_signer = - InMemorySigner::from_secret_key(eth_implicit_account_id.clone(), secret_key); + InMemorySigner::from_secret_key(eth_implicit_account_id.clone(), secret_key).into(); // Send money to ETH-implicit account, invoking its creation. let send_money_tx = SignedTransaction::send_money( 1, "test1".parse().unwrap(), eth_implicit_account_id.clone(), - &signer1, + &signer1.into(), deposit_for_account_creation, *genesis_block.hash(), ); @@ -272,7 +272,7 @@ fn test_wallet_contract_interaction() { nonce, relayer.clone(), eth_implicit_account.clone(), - &relayer_signer.signer, + &relayer_signer.signer.clone().into(), actions, block_hash, 0, @@ -333,7 +333,7 @@ fn test_wallet_contract_interaction() { assert!(wallet_balance_diff - transfer_amount < NEAR_BASE / 500); } -fn create_rlp_execute_tx( +pub fn create_rlp_execute_tx( target: &AccountIdRef, mut action: Action, nonce: u64, @@ -395,16 +395,16 @@ fn create_rlp_execute_tx( nonce, near_signer.account_id.into(), eth_implicit_account.into(), - &near_signer.signer, + &near_signer.signer.clone().into(), actions, block_hash, 0, ) } -struct NearSigner<'a> { - account_id: &'a AccountIdRef, - signer: InMemorySigner, +pub struct NearSigner<'a> { + pub account_id: &'a AccountIdRef, + pub signer: InMemorySigner, } fn abi_encode(target: String, action: Action) -> Vec { diff --git a/integration-tests/src/tests/client/features/yield_timeouts.rs b/integration-tests/src/tests/client/features/yield_timeouts.rs index 476edb51c36..a73a2bc0142 100644 --- a/integration-tests/src/tests/client/features/yield_timeouts.rs +++ b/integration-tests/src/tests/client/features/yield_timeouts.rs @@ -28,7 +28,7 @@ const YIELD_TIMEOUT_HEIGHT: u64 = YIELD_CREATE_HEIGHT + TEST_CONFIG_YIELD_TIMEOU /// Returns yield data ids for all PromiseYield and PromiseResume receipts. fn find_yield_data_ids_from_latest_block(env: &TestEnv) -> Vec { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - let epoch_id = genesis_block.header().epoch_id().clone(); + let epoch_id = *genesis_block.header().epoch_id(); let shard_layout = env.clients[0].epoch_manager.get_shard_layout(&epoch_id).unwrap(); let shard_id = account_id_to_shard_id(&"test0".parse::().unwrap(), &shard_layout); let last_block_hash = env.clients[0].chain.head().unwrap().last_block_hash; @@ -65,7 +65,8 @@ fn prepare_env_with_yield( } let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); // Submit transaction deploying contract to test0 let tx = SignedTransaction::from_actions( @@ -74,7 +75,7 @@ fn prepare_env_with_yield( "test0".parse().unwrap(), &signer, vec![Action::DeployContract(DeployContractAction { - code: near_test_contracts::nightly_rs_contract().to_vec(), + code: near_test_contracts::rs_contract().to_vec(), })], *genesis_block.hash(), 0, @@ -139,7 +140,7 @@ fn invoke_yield_resume( 200, "test0".parse().unwrap(), "test0".parse().unwrap(), - &signer, + &signer.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_yield_resume".to_string(), args: yield_payload.into_iter().chain(data_id.as_bytes().iter().cloned()).collect(), @@ -162,7 +163,8 @@ fn invoke_yield_resume( /// Note that these transactions start to be processed in the *second* block produced after they are /// inserted to client 0's mempool. fn create_congestion(env: &mut TestEnv) { - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let mut tx_hashes = vec![]; diff --git a/integration-tests/src/tests/client/features/zero_balance_account.rs b/integration-tests/src/tests/client/features/zero_balance_account.rs index 102fd353baa..3d1854cb42e 100644 --- a/integration-tests/src/tests/client/features/zero_balance_account.rs +++ b/integration-tests/src/tests/client/features/zero_balance_account.rs @@ -1,8 +1,7 @@ use assert_matches::assert_matches; - use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; -use near_crypto::{InMemorySigner, KeyType, PublicKey}; +use near_crypto::{InMemorySigner, KeyType, PublicKey, Signer}; use near_network::client::ProcessTxResponse; use near_parameters::{ExtCostsConfig, RuntimeConfig, RuntimeConfigStore, StorageUsageConfig}; use near_primitives::account::id::AccountId; @@ -15,6 +14,7 @@ use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use near_primitives::views::{FinalExecutionStatus, QueryRequest, QueryResponseKind}; use nearcore::test_utils::TestEnvNightshadeSetupExt; use node_runtime::ZERO_BALANCE_ACCOUNT_STORAGE_LIMIT; +use std::sync::Arc; /// Assert that an account exists and has zero balance fn assert_zero_balance_account(env: &TestEnv, account_id: &AccountId) { @@ -54,14 +54,16 @@ fn test_zero_balance_account_creation() { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let new_account_id: AccountId = "hello.test0".parse().unwrap(); - let signer0 = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer0_account_id: AccountId = "test0".parse().unwrap(); + let signer0 = + InMemorySigner::from_seed(signer0_account_id.clone(), KeyType::ED25519, "test0").into(); let new_signer = InMemorySigner::from_seed(new_account_id.clone(), KeyType::ED25519, "hello.test0"); // create a valid zero balance account. Transaction should succeed let create_account_tx = SignedTransaction::create_account( 1, - signer0.account_id.clone(), + signer0_account_id.clone(), new_account_id.clone(), 0, new_signer.public_key.clone(), @@ -83,7 +85,7 @@ fn test_zero_balance_account_creation() { let contract = near_test_contracts::sized_contract(ZERO_BALANCE_ACCOUNT_STORAGE_LIMIT as usize); let create_account_tx = SignedTransaction::create_contract( 2, - signer0.account_id.clone(), + signer0_account_id, new_account_id, contract.to_vec(), 0, @@ -121,12 +123,14 @@ fn test_zero_balance_account_add_key() { // create free runtime config for transaction costs to make it easier to assert // the exact amount of tokens on accounts let mut runtime_config = RuntimeConfig::free(); - runtime_config.fees.storage_usage_config = StorageUsageConfig { + let fees = Arc::make_mut(&mut runtime_config.fees); + fees.storage_usage_config = StorageUsageConfig { storage_amount_per_byte: 10u128.pow(19), num_bytes_account: 100, num_extra_bytes_record: 40, }; - runtime_config.wasm_config.ext_costs = ExtCostsConfig::test(); + let wasm_config = Arc::make_mut(&mut runtime_config.wasm_config); + wasm_config.ext_costs = ExtCostsConfig::test(); let runtime_config_store = RuntimeConfigStore::with_one_config(runtime_config); let mut env = TestEnv::builder(&genesis.config) .nightshade_runtimes_with_runtime_config_store(&genesis, vec![runtime_config_store]) @@ -134,17 +138,19 @@ fn test_zero_balance_account_add_key() { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let new_account_id: AccountId = "hello.test0".parse().unwrap(); - let signer0 = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); - let new_signer = - InMemorySigner::from_seed(new_account_id.clone(), KeyType::ED25519, "hello.test0"); + let signer0_account_id: AccountId = "test0".parse().unwrap(); + let signer0 = + InMemorySigner::from_seed(signer0_account_id.clone(), KeyType::ED25519, "test0").into(); + let new_signer: Signer = + InMemorySigner::from_seed(new_account_id.clone(), KeyType::ED25519, "hello.test0").into(); let amount = 10u128.pow(24); let create_account_tx = SignedTransaction::create_account( 1, - signer0.account_id.clone(), + signer0_account_id.clone(), new_account_id.clone(), amount, - new_signer.public_key.clone(), + new_signer.public_key(), &signer0, *genesis_block.hash(), ); @@ -204,7 +210,7 @@ fn test_zero_balance_account_add_key() { let send_money_tx = SignedTransaction::send_money( nonce + 10, new_account_id.clone(), - signer0.account_id, + signer0_account_id, &new_signer, amount, *genesis_block.hash(), @@ -252,14 +258,16 @@ fn test_zero_balance_account_upgrade() { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let new_account_id: AccountId = "hello.test0".parse().unwrap(); - let signer0 = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer0_account_id: AccountId = "test0".parse().unwrap(); + let signer0: Signer = + InMemorySigner::from_seed(signer0_account_id.clone(), KeyType::ED25519, "test0").into(); let new_signer = InMemorySigner::from_seed(new_account_id.clone(), KeyType::ED25519, "hello.test0"); // before protocol upgrade, should not be possible to create a zero balance account let first_create_account_tx = SignedTransaction::create_account( 1, - signer0.account_id.clone(), + signer0_account_id.clone(), new_account_id.clone(), 0, new_signer.public_key.clone(), @@ -285,7 +293,7 @@ fn test_zero_balance_account_upgrade() { let second_create_account_tx = SignedTransaction::create_account( 2, - signer0.account_id.clone(), + signer0_account_id, new_account_id, 0, new_signer.public_key, diff --git a/integration-tests/src/tests/client/flat_storage.rs b/integration-tests/src/tests/client/flat_storage.rs index fa5f7208b17..fd3d30e114a 100644 --- a/integration-tests/src/tests/client/flat_storage.rs +++ b/integration-tests/src/tests/client/flat_storage.rs @@ -127,7 +127,8 @@ fn test_flat_storage_creation_sanity() { // Process some blocks with flat storage. Then remove flat storage data from disk. { let mut env = setup_env(&genesis, store.clone()); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let genesis_hash = *env.clients[0].chain.genesis().hash(); for height in 1..START_HEIGHT { env.produce_block(0, height); @@ -250,7 +251,8 @@ fn test_flat_storage_creation_two_shards() { // Process some blocks with flat storages for two shards. Then remove flat storage data from disk for shard 0. { let mut env = setup_env(&genesis, store.clone()); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let genesis_hash = *env.clients[0].chain.genesis().hash(); for height in 1..START_HEIGHT { env.produce_block(0, height); @@ -511,7 +513,8 @@ fn test_not_supported_block() { let store = create_test_store(); let mut env = setup_env(&genesis, store); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let genesis_hash = *env.clients[0].chain.genesis().hash(); // Produce blocks up to `START_HEIGHT`. diff --git a/integration-tests/src/tests/client/process_blocks.rs b/integration-tests/src/tests/client/process_blocks.rs index 4d2223b6242..288cf2cf4b7 100644 --- a/integration-tests/src/tests/client/process_blocks.rs +++ b/integration-tests/src/tests/client/process_blocks.rs @@ -25,7 +25,7 @@ use near_client::{ BlockApproval, BlockResponse, GetBlockWithMerkleTree, ProcessTxResponse, ProduceChunkResult, SetNetworkInfo, }; -use near_crypto::{InMemorySigner, KeyType, PublicKey, Signature, Signer}; +use near_crypto::{InMemorySigner, KeyType, PublicKey, Signature}; use near_network::test_utils::{wait_or_panic, MockPeerManagerAdapter}; use near_network::types::{ BlockInfo, ConnectedPeerInfo, HighestHeightPeerInfo, NetworkInfo, PeerChainInfo, @@ -58,7 +58,6 @@ use near_primitives::transaction::{ use near_primitives::trie_key::TrieKey; use near_primitives::types::validator_stake::ValidatorStake; use near_primitives::types::{AccountId, BlockHeight, EpochId, NumBlocks, ProtocolVersion}; -use near_primitives::validator_signer::ValidatorSigner; use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use near_primitives::views::{ BlockHeaderView, FinalExecutionStatus, QueryRequest, QueryResponseKind, @@ -135,7 +134,7 @@ pub(crate) fn create_account( new_account_id, 10u128.pow(24), signer.public_key(), - &signer, + &signer.into(), *block.hash(), ); let tx_hash = tx.get_hash(); @@ -160,7 +159,7 @@ pub(crate) fn deploy_test_contract_with_protocol_version( height, account_id.clone(), account_id, - &signer, + &signer.into(), vec![Action::DeployContract(DeployContractAction { code: wasm_code.to_vec() })], *block.hash(), 0, @@ -203,7 +202,8 @@ pub(crate) fn prepare_env_with_congestion( } let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); // Deploy contract to test0. let tx = SignedTransaction::from_actions( @@ -349,7 +349,8 @@ fn receive_network_block() { &signer, last_block.header.next_bp_hash, block_merkle_tree.root(), - Clock::real().now_utc(), + Clock::real(), + None, ); actor_handles.client_actor.do_send( BlockResponse { block, peer_id: PeerInfo::random().id, was_requested: false } @@ -435,7 +436,8 @@ fn produce_block_with_approvals() { &signer1, last_block.header.next_bp_hash, block_merkle_tree.root(), - Clock::real().now_utc(), + Clock::real(), + None, ); actor_handles.client_actor.do_send( BlockResponse { @@ -651,7 +653,8 @@ fn invalid_blocks_common(is_requested: bool) { &signer, last_block.header.next_bp_hash, block_merkle_tree.root(), - Clock::real().now_utc(), + Clock::real(), + None, ); // Send block with invalid chunk mask let mut block = valid_block.clone(); @@ -1067,7 +1070,7 @@ fn test_time_attack() { init_test_logger(); let mut env = TestEnv::default_builder().clients_count(1).mock_epoch_managers().build(); let client = &mut env.clients[0]; - let signer = client.validator_signer.as_ref().unwrap(); + let signer = client.validator_signer.get().unwrap(); let genesis = client.chain.get_block_by_height(0).unwrap(); let mut b1 = TestBlockBuilder::new(Clock::real(), &genesis, signer.clone()).build(); b1.mut_header().get_mut().inner_lite.timestamp = @@ -1095,7 +1098,7 @@ fn test_invalid_gas_price() { genesis_config.min_gas_price = 100; let mut env = TestEnv::builder(&genesis_config).clients_count(1).mock_epoch_managers().build(); let client = &mut env.clients[0]; - let signer = client.validator_signer.as_ref().unwrap(); + let signer = client.validator_signer.get().unwrap(); let genesis = client.chain.get_block_by_height(0).unwrap(); let mut b1 = TestBlockBuilder::new(Clock::real(), &genesis, signer.clone()).build(); @@ -1139,7 +1142,7 @@ fn test_bad_orphan() { env.produce_block(0, i); } let block = env.clients[0].produce_block(5).unwrap().unwrap(); - let signer = env.clients[0].validator_signer.as_ref().unwrap().clone(); + let signer = env.clients[0].validator_signer.get().unwrap(); { // Orphan block with unknown epoch let mut block = env.clients[0].produce_block(6).unwrap().unwrap(); @@ -1283,7 +1286,7 @@ fn test_bad_chunk_mask() { } block .mut_header() - .resign(&*env.client(&block_producer).validator_signer.as_ref().unwrap().clone()); + .resign(&*env.client(&block_producer).validator_signer.get().unwrap().clone()); for client in env.clients.iter_mut() { let res = client @@ -1600,7 +1603,8 @@ fn test_gc_execution_outcome() { genesis.config.epoch_length = epoch_length; let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx = SignedTransaction::send_money( 1, "test0".parse().unwrap(), @@ -1693,7 +1697,7 @@ fn test_process_block_after_state_sync() { assert!(env.clients[0].chain.epoch_manager.get_epoch_start_height(&block_hash).is_ok()); } env.clients[0].chain.reset_data_pre_state_sync(sync_hash).unwrap(); - let epoch_id = env.clients[0].chain.get_block_header(&sync_hash).unwrap().epoch_id().clone(); + let epoch_id = *env.clients[0].chain.get_block_header(&sync_hash).unwrap().epoch_id(); env.clients[0] .runtime_adapter .apply_state_part(0, &state_root, PartId::new(0, 1), &state_part, &epoch_id) @@ -1789,13 +1793,14 @@ fn test_tx_forward_around_epoch_boundary() { .nightshade_runtimes(&genesis) .build(); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -1870,7 +1875,8 @@ fn test_gc_tail_update() { blocks.push(block); } let headers = blocks.iter().map(|b| b.header().clone()).collect::>(); - env.clients[1].sync_block_headers(headers).unwrap(); + let signer = env.clients[1].validator_signer.get(); + env.clients[1].sync_block_headers(headers, &signer).unwrap(); // simulate save sync hash block let prev_sync_block = blocks[blocks.len() - 3].clone(); let prev_sync_hash = *prev_sync_block.hash(); @@ -1918,7 +1924,8 @@ fn test_gas_price_change() { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let genesis_hash = *genesis_block.hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::send_money( 1, "test1".parse().unwrap(), @@ -1962,7 +1969,8 @@ fn test_gas_price_overflow() { let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let genesis_hash = *genesis_block.hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); for i in 1..100 { let tx = SignedTransaction::send_money( i, @@ -2093,7 +2101,7 @@ fn test_data_reset_before_state_sync() { "test_account".parse().unwrap(), NEAR_BASE, signer.public_key(), - &signer, + &signer.into(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -2186,7 +2194,8 @@ fn test_validate_chunk_extra() { let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let genesis_height = genesis_block.header().height(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx = SignedTransaction::from_actions( 1, "test0".parse().unwrap(), @@ -2285,12 +2294,13 @@ fn test_validate_chunk_extra() { let mut chain_store = ChainStore::new(env.clients[0].chain.chain_store().store().clone(), genesis_height, true); let chunk_header = encoded_chunk.cloned_header(); - let validator_id = env.clients[0].validator_signer.as_ref().unwrap().validator_id().clone(); + let signer = env.clients[0].validator_signer.get(); + let validator_id = signer.as_ref().unwrap().validator_id().clone(); env.clients[0] .persist_and_distribute_encoded_chunk(encoded_chunk, merkle_paths, receipts, validator_id) .unwrap(); env.clients[0].chain.blocks_with_missing_chunks.accept_chunk(&chunk_header.chunk_hash()); - env.clients[0].process_blocks_with_missing_chunks(None); + env.clients[0].process_blocks_with_missing_chunks(None, &signer); let accepted_blocks = env.clients[0].finish_block_in_processing(block1.hash()); assert_eq!(accepted_blocks.len(), 1); env.resume_block_processing(block2.hash()); @@ -2355,7 +2365,8 @@ fn test_catchup_gas_price_change() { env.process_block(0, block.clone(), Provenance::PRODUCED); env.process_block(1, block, Provenance::NONE); } - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); for i in 0..3 { let tx = SignedTransaction::send_money( i + 1, @@ -2447,7 +2458,8 @@ fn test_block_execution_outcomes() { genesis.config.gas_limit = 1000000000000; let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let mut tx_hashes = vec![]; for i in 0..3 { // send transaction to the same account to generate local receipts @@ -2535,7 +2547,8 @@ fn test_refund_receipts_processing() { genesis.config.gas_limit = 100_000_000; let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let mut tx_hashes = vec![]; // Send transactions to a non-existing account to generate refunds. for i in 0..3 { @@ -2615,7 +2628,8 @@ fn test_delayed_receipt_count_limit() { let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); // Send enough transactions to saturate delayed receipts capacity. let total_tx_count = 200usize; for i in 0..total_tx_count { @@ -2828,14 +2842,8 @@ fn test_epoch_multi_protocol_version_change() { let mut seen_v2 = false; let mut height = 1; - while chrono::Utc::now() < end_time { - let head = env.clients[0].chain.head().unwrap(); - let epoch_id = env.clients[0] - .epoch_manager - .get_epoch_id_from_prev_block(&head.last_block_hash) - .unwrap(); - let protocol_version = - env.clients[0].epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap(); + while chrono::Utc::now() < end_time && (!seen_v0 || !seen_v1 || !seen_v2) { + let (epoch_id, protocol_version) = get_epoch_id_and_protocol_version(&env); produce_chunks(&mut env, &epoch_id, height); @@ -2862,7 +2870,96 @@ fn test_epoch_multi_protocol_version_change() { assert!(seen_v2); } -// helper for test_epoch_multi_protocol_version_change +#[test] +fn test_epoch_multi_protocol_version_change_epoch_overlap() { + init_test_logger(); + + let v0 = PROTOCOL_VERSION - 2; + let v1 = PROTOCOL_VERSION - 1; + let v2 = PROTOCOL_VERSION; + + // produce blocks roughly every 500ms + // one epoch is roughly 2500ms + // at least two epochs are needed for one protocol upgrade + // schedule the first protocol upgrade voting at now +1s + // arrange the second protocol upgrade voting so that it falls + // on the same epoch as the first (now +1s) + // assert two protocol upgrades should never happen on the same epoch + + let start_time = chrono::Utc::now(); + let end_time = start_time + chrono::Duration::seconds(25); + + let v1_upgrade_time = start_time + chrono::Duration::seconds(1); + let v2_upgrade_time = start_time + chrono::Duration::seconds(2); + + let v1_upgrade_time = v1_upgrade_time.format("%Y-%m-%d %H:%M:%S").to_string(); + let v2_upgrade_time = v2_upgrade_time.format("%Y-%m-%d %H:%M:%S").to_string(); + + let protocol_version_override = + format!("{}={},{}={}", v1_upgrade_time, v1, v2_upgrade_time, v2); + + tracing::debug!(target: "test", ?protocol_version_override, "setting the protocol_version_override"); + std::env::set_var("NEAR_TESTS_PROTOCOL_UPGRADE_OVERRIDE", protocol_version_override); + + let epoch_length = 5; + let mut genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 2); + genesis.config.epoch_length = epoch_length; + genesis.config.protocol_version = v0; + let mut env = TestEnv::builder(&genesis.config) + .clients_count(2) + .validator_seats(2) + .nightshade_runtimes(&genesis) + .build(); + + let mut seen_v0 = false; + let mut seen_v1 = false; + let mut seen_v2 = false; + + let mut height = 1; + let (mut current_epoch_id, mut current_protocol_version) = + get_epoch_id_and_protocol_version(&env); + while chrono::Utc::now() < end_time && (!seen_v0 || !seen_v1 || !seen_v2) { + let (epoch_id, protocol_version) = get_epoch_id_and_protocol_version(&env); + + produce_chunks(&mut env, &epoch_id, height); + + produce_block(&mut env, &epoch_id, height); + + if protocol_version == v0 { + seen_v0 = true; + } + if protocol_version == v1 { + seen_v1 = true; + } + if protocol_version == v2 { + seen_v2 = true; + } + + assert!( + protocol_version - current_protocol_version <= 1, + "protocol version should never increase twice in one iteration ({current_protocol_version} -> {protocol_version})" + ); + if epoch_id == current_epoch_id { + assert_eq!( + current_protocol_version, protocol_version, + "protocol version shouldn't change during the same epoch" + ); + } + + tracing::debug!(target: "test", ?height, ?protocol_version, "loop iter finished"); + + height += 1; + current_epoch_id = epoch_id; + current_protocol_version = protocol_version; + std::thread::sleep(std::time::Duration::from_millis(500)); + } + + assert!(seen_v0); + assert!(seen_v1); + assert!(seen_v2); +} + +// helper for test_epoch_multi_protocol_version_change* class of tests fn produce_block(env: &mut TestEnv, epoch_id: &EpochId, height: u64) { let block_producer = env.clients[0].epoch_manager.get_block_producer(epoch_id, height).unwrap(); let index = if block_producer == "test0" { 0 } else { 1 }; @@ -2872,7 +2969,7 @@ fn produce_block(env: &mut TestEnv, epoch_id: &EpochId, height: u64) { } } -// helper for test_epoch_multi_protocol_version_change +// helper for test_epoch_multi_protocol_version_change* class of tests fn produce_chunks(env: &mut TestEnv, epoch_id: &EpochId, height: u64) { let shard_layout = env.clients[0].epoch_manager.get_shard_layout(epoch_id).unwrap(); @@ -2885,7 +2982,7 @@ fn produce_chunks(env: &mut TestEnv, epoch_id: &EpochId, height: u64) { produce_chunk_result; for client in &mut env.clients { - let validator_id = client.validator_signer.as_ref().unwrap().validator_id().clone(); + let validator_id = client.validator_signer.get().unwrap().validator_id().clone(); client .persist_and_distribute_encoded_chunk( chunk.clone(), @@ -2898,6 +2995,16 @@ fn produce_chunks(env: &mut TestEnv, epoch_id: &EpochId, height: u64) { } } +// helper for test_epoch_multi_protocol_version_change* class of tests +fn get_epoch_id_and_protocol_version(env: &TestEnv) -> (EpochId, u32) { + let head = env.clients[0].chain.head().unwrap(); + let epoch_id = + env.clients[0].epoch_manager.get_epoch_id_from_prev_block(&head.last_block_hash).unwrap(); + let protocol_version = + env.clients[0].epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap(); + (epoch_id, protocol_version) +} + #[test] fn test_discard_non_finalizable_block() { let genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); @@ -2967,7 +3074,7 @@ fn test_query_final_state() { 1, "test0".parse().unwrap(), "test1".parse().unwrap(), - &signer, + &signer.into(), 100, *genesis_block.hash(), ); @@ -3165,7 +3272,7 @@ fn prepare_env_with_transaction() -> (TestEnv, CryptoHash) { 1, "test0".parse().unwrap(), "test1".parse().unwrap(), - &signer, + &signer.into(), 100, *genesis_block.hash(), ); @@ -3262,7 +3369,11 @@ fn test_node_shutdown_with_old_protocol_version() { #[test] fn test_block_ordinal() { - let mut env = TestEnv::default_builder().mock_epoch_managers().build(); + // Ensure that GC will not happen. + let epoch_length = 200; + let mut genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); + genesis.config.epoch_length = epoch_length; + let mut env = TestEnv::builder(&genesis.config).mock_epoch_managers().build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); assert_eq!(genesis_block.header().block_ordinal(), 1); let mut ordinal = 1; @@ -3390,7 +3501,7 @@ fn test_validator_stake_host_function() { 10, "test0".parse().unwrap(), "test0".parse().unwrap(), - &signer, + &signer.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "ext_validator_stake".to_string(), args: b"test0".to_vec(), @@ -3485,12 +3596,12 @@ fn test_long_chain_with_restart_from_snapshot() { mod contract_precompilation_tests { use super::*; use near_primitives::test_utils::MockEpochInfoProvider; - use near_primitives::views::ViewApplyState; use near_store::TrieUpdate; use near_vm_runner::{ get_contract_cache_key, ContractCode, ContractRuntimeCache, FilesystemContractRuntimeCache, }; use node_runtime::state_viewer::TrieViewer; + use node_runtime::state_viewer::ViewApplyState; const EPOCH_LENGTH: u64 = 25; @@ -3500,8 +3611,7 @@ mod contract_precompilation_tests { let state_sync_header = env.clients[0].chain.get_state_response_header(0, sync_hash).unwrap(); let state_root = state_sync_header.chunk_prev_state_root(); - let epoch_id = - env.clients[0].chain.get_block_header(&sync_hash).unwrap().epoch_id().clone(); + let epoch_id = *env.clients[0].chain.get_block_header(&sync_hash).unwrap().epoch_id(); let sync_prev_header = env.clients[0].chain.get_previous_header(sync_block.header()).unwrap(); let sync_prev_prev_hash = sync_prev_header.prev_hash(); @@ -3550,13 +3660,8 @@ mod contract_precompilation_tests { // Check existence of contract in both caches. let contract_code = ContractCode::new(wasm_code.clone(), None); - let epoch_id = env.clients[0] - .chain - .get_block_by_height(height - 1) - .unwrap() - .header() - .epoch_id() - .clone(); + let epoch_id = + *env.clients[0].chain.get_block_by_height(height - 1).unwrap().header().epoch_id(); let runtime_config = env.get_runtime_config(0, epoch_id); let key = get_contract_cache_key(*contract_code.hash(), &runtime_config.wasm_config); for i in 0..num_clients { @@ -3590,7 +3695,7 @@ mod contract_precompilation_tests { prev_block_hash: *block.header().prev_hash(), block_hash: *block.hash(), shard_id: ShardUId::single_shard().shard_id(), - epoch_id: block.header().epoch_id().clone(), + epoch_id: *block.header().epoch_id(), epoch_height: 1, block_timestamp: block.header().raw_timestamp(), current_protocol_version: PROTOCOL_VERSION, @@ -3655,13 +3760,8 @@ mod contract_precompilation_tests { // Perform state sync for the second client on the last produced height. state_sync_on_height(&mut env, height - 1); - let epoch_id = env.clients[0] - .chain - .get_block_by_height(height - 1) - .unwrap() - .header() - .epoch_id() - .clone(); + let epoch_id = + *env.clients[0].chain.get_block_by_height(height - 1).unwrap().header().epoch_id(); let runtime_config = env.get_runtime_config(0, epoch_id); let tiny_contract_key = get_contract_cache_key( *ContractCode::new(tiny_wasm_code.clone(), None).hash(), @@ -3721,7 +3821,7 @@ mod contract_precompilation_tests { "test2".parse().unwrap(), "test2".parse().unwrap(), "test0".parse().unwrap(), - &signer, + &signer.into(), *block.hash(), ); assert_eq!( @@ -3733,13 +3833,8 @@ mod contract_precompilation_tests { // Perform state sync for the second client. state_sync_on_height(&mut env, height - 1); - let epoch_id = env.clients[0] - .chain - .get_block_by_height(height - 1) - .unwrap() - .header() - .epoch_id() - .clone(); + let epoch_id = + *env.clients[0].chain.get_block_by_height(height - 1).unwrap().header().epoch_id(); let runtime_config = env.get_runtime_config(0, epoch_id); let contract_key = get_contract_cache_key( *ContractCode::new(wasm_code.clone(), None).hash(), diff --git a/integration-tests/src/tests/client/resharding.rs b/integration-tests/src/tests/client/resharding.rs index 9bdf874c8ca..49bb65457da 100644 --- a/integration-tests/src/tests/client/resharding.rs +++ b/integration-tests/src/tests/client/resharding.rs @@ -6,7 +6,7 @@ use near_chain::{ChainStoreAccess, Provenance}; use near_chain_configs::{Genesis, NEAR_BASE}; use near_client::test_utils::{run_catchup, TestEnv}; use near_client::{Client, ProcessTxResponse}; -use near_crypto::{InMemorySigner, KeyType, Signer}; +use near_crypto::{InMemorySigner, KeyType}; use near_o11y::testonly::init_test_logger; use near_primitives::account::id::AccountId; use near_primitives::block::{Block, Tip}; @@ -239,7 +239,7 @@ impl TestReshardingEnv { .validator_seats(num_validators) .real_stores() .epoch_managers_with_test_overrides(epoch_config_test_overrides) - .nightshade_runtimes(&genesis) + .nightshade_runtimes_congestion_control_disabled(&genesis) .track_all_shards() .build(); assert_eq!(env.validators.len(), num_validators); @@ -341,7 +341,7 @@ impl TestReshardingEnv { let _span = tracing::debug_span!(target: "test", "process block", client=j).entered(); let shard_ids = chunk_producer_to_shard_id - .get(client.validator_signer.as_ref().unwrap().validator_id()) + .get(client.validator_signer.get().unwrap().validator_id()) .cloned() .unwrap_or_default(); let should_produce_chunk = @@ -352,12 +352,14 @@ impl TestReshardingEnv { // because we want to call run_catchup before finish processing this block. This simulates // that catchup and block processing run in parallel. let block = MaybeValidated::from(block.clone()); - client.start_process_block(block, Provenance::NONE, None).unwrap(); + let signer = client.validator_signer.get(); + client.start_process_block(block, Provenance::NONE, None, &signer).unwrap(); if should_catchup { run_catchup(client, &[])?; } while wait_for_all_blocks_in_processing(&mut client.chain) { - let (_, errors) = client.postprocess_ready_blocks(None, should_produce_chunk); + let (_, errors) = + client.postprocess_ready_blocks(None, should_produce_chunk, &signer); assert!(errors.is_empty(), "unexpected errors: {:?}", errors); } // manually invoke gc @@ -590,53 +592,6 @@ impl TestReshardingEnv { successful_txs } - // Check the receipt_id_to_shard_id mappings are correct for all outgoing receipts in the - // latest block - fn check_receipt_id_to_shard_id(&mut self) { - let env = &mut self.env; - let head = env.clients[0].chain.head().unwrap(); - let shard_layout = env.clients[0] - .epoch_manager - .get_shard_layout_from_prev_block(&head.last_block_hash) - .unwrap(); - let block = env.clients[0].chain.get_block(&head.last_block_hash).unwrap(); - - for (shard_id, chunk_header) in block.chunks().iter().enumerate() { - if chunk_header.height_included() != block.header().height() { - continue; - } - let shard_id = shard_id as ShardId; - - for (i, me) in env.validators.iter().enumerate() { - let client = &mut env.clients[i]; - let care_about_shard = client.shard_tracker.care_about_shard( - Some(me), - &head.prev_block_hash, - shard_id, - true, - ); - if !care_about_shard { - continue; - } - - let outgoing_receipts = client - .chain - .mut_chain_store() - .get_outgoing_receipts(&head.last_block_hash, shard_id) - .unwrap() - .clone(); - for receipt in outgoing_receipts.iter() { - let target_shard_id = - client.chain.get_shard_id_for_receipt_id(receipt.receipt_id()).unwrap(); - assert_eq!( - target_shard_id, - account_id_to_shard_id(receipt.receiver_id(), &shard_layout) - ); - } - } - } - } - /// Check that after resharding is finished, the artifacts stored in storage is removed fn check_resharding_artifacts(&mut self, client_id: usize) { tracing::debug!(target: "test", "checking resharding artifacts"); @@ -913,6 +868,7 @@ fn setup_genesis( // Same needs to be set in the AllEpochConfigTestOverrides. genesis.config.block_producer_kickout_threshold = 0; genesis.config.chunk_producer_kickout_threshold = 0; + genesis.config.chunk_validator_only_kickout_threshold = 0; genesis.config.epoch_length = epoch_length; genesis.config.protocol_version = genesis_protocol_version; genesis.config.use_production_config = true; @@ -973,7 +929,7 @@ fn generate_create_accounts_txs( account_id.clone(), NEAR_BASE, signer.public_key(), - &signer0, + &signer0.into(), genesis_hash, ); if check_accounts { @@ -1039,7 +995,6 @@ fn test_shard_layout_upgrade_simple_impl( let drop_chunk_condition = DropChunkCondition::new(); for _ in 1..4 * epoch_length { test_env.step(&drop_chunk_condition, target_protocol_version); - test_env.check_receipt_id_to_shard_id(); test_env.check_snapshot(state_snapshot_enabled); } @@ -1282,7 +1237,7 @@ fn setup_test_env_with_cross_contract_txs( 1, account_id.clone(), account_id.clone(), - &signer, + &signer.into(), actions, genesis_hash, 0, @@ -1451,7 +1406,7 @@ fn gen_cross_contract_tx_impl( nonce, account0.clone(), account1.clone(), - &signer0, + &signer0.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -1488,7 +1443,6 @@ fn test_shard_layout_upgrade_cross_contract_calls_impl( let drop_chunk_condition = DropChunkCondition::new(); for _ in 1..5 * epoch_length { test_env.step(&drop_chunk_condition, target_protocol_version); - test_env.check_receipt_id_to_shard_id(); } let successful_txs = test_env.check_tx_outcomes(false); @@ -1597,7 +1551,7 @@ fn generate_yield_create_tx( nonce, account_id.clone(), account_id.clone(), - &signer, + &signer.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: callback_method_name, args: vec![], @@ -1631,7 +1585,7 @@ fn setup_test_env_with_promise_yield_txs( 1, account_id.clone(), account_id.clone(), - &signer, + &signer.into(), actions, genesis_hash, 0, @@ -1705,7 +1659,6 @@ fn test_shard_layout_upgrade_promise_yield_impl(resharding_type: ReshardingType, let drop_chunk_condition = DropChunkCondition::new(); for _ in 1..5 * epoch_length { test_env.step(&drop_chunk_condition, target_protocol_version); - test_env.check_receipt_id_to_shard_id(); } let tx_outcomes = test_env.check_tx_outcomes(false); @@ -1762,7 +1715,6 @@ fn test_shard_layout_upgrade_incoming_receipts_impl( let drop_chunk_condition = DropChunkCondition::with_by_height_shard_id(by_height_shard_id); for _ in 1..5 * epoch_length { test_env.step(&drop_chunk_condition, target_protocol_version); - test_env.check_receipt_id_to_shard_id(); } let successful_txs = test_env.check_tx_outcomes(false); @@ -1867,7 +1819,6 @@ fn test_missing_chunks( for height in last_height - 3..=last_height { test_env.check_next_block_with_new_chunk(height); } - test_env.check_receipt_id_to_shard_id(); } // make sure all included transactions finished processing @@ -1878,7 +1829,6 @@ fn test_missing_chunks( for height in last_height - 3..=last_height { test_env.check_next_block_with_new_chunk(height); } - test_env.check_receipt_id_to_shard_id(); } let successful_txs = test_env.check_tx_outcomes(true); diff --git a/integration-tests/src/tests/client/runtimes.rs b/integration-tests/src/tests/client/runtimes.rs index ece190ae587..e7cd36318e6 100644 --- a/integration-tests/src/tests/client/runtimes.rs +++ b/integration-tests/src/tests/client/runtimes.rs @@ -23,7 +23,12 @@ fn test_pending_approvals() { let parent_hash = hash(&[1]); let approval = Approval::new(parent_hash, 0, 1, &signer); let peer_id = PeerId::random(); - env.clients[0].collect_block_approval(&approval, ApprovalType::PeerApproval(peer_id.clone())); + let client_signer = env.clients[0].validator_signer.get(); + env.clients[0].collect_block_approval( + &approval, + ApprovalType::PeerApproval(peer_id.clone()), + &client_signer, + ); let approvals = env.clients[0].pending_approvals.pop(&ApprovalInner::Endorsement(parent_hash)); let expected = vec![("test0".parse().unwrap(), (approval, ApprovalType::PeerApproval(peer_id)))] @@ -45,14 +50,24 @@ fn test_invalid_approvals() { // Approval not from a validator. Should be dropped let approval = Approval::new(parent_hash, 1, 3, &signer); let peer_id = PeerId::random(); - env.clients[0].collect_block_approval(&approval, ApprovalType::PeerApproval(peer_id.clone())); + let client_signer = env.clients[0].validator_signer.get(); + env.clients[0].collect_block_approval( + &approval, + ApprovalType::PeerApproval(peer_id.clone()), + &client_signer, + ); assert_eq!(env.clients[0].pending_approvals.len(), 0); // Approval with invalid signature. Should be dropped let signer = - InMemoryValidatorSigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "random"); + InMemoryValidatorSigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "random") + .into(); let genesis_hash = *env.clients[0].chain.genesis().hash(); let approval = Approval::new(genesis_hash, 0, 1, &signer); - env.clients[0].collect_block_approval(&approval, ApprovalType::PeerApproval(peer_id)); + env.clients[0].collect_block_approval( + &approval, + ApprovalType::PeerApproval(peer_id), + &client_signer, + ); assert_eq!(env.clients[0].pending_approvals.len(), 0); } diff --git a/integration-tests/src/tests/client/sandbox.rs b/integration-tests/src/tests/client/sandbox.rs index 2545e1f3b8b..f21572674b4 100644 --- a/integration-tests/src/tests/client/sandbox.rs +++ b/integration-tests/src/tests/client/sandbox.rs @@ -2,7 +2,7 @@ use near_chain::Provenance; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; -use near_crypto::{InMemorySigner, KeyType}; +use near_crypto::{InMemorySigner, KeyType, Signer}; use near_primitives::account::Account; use near_primitives::sandbox::state_patch::SandboxStatePatch; use near_primitives::state_record::StateRecord; @@ -12,12 +12,13 @@ use near_primitives::transaction::{ use near_primitives::types::{AccountId, BlockHeight, Nonce}; use nearcore::test_utils::TestEnvNightshadeSetupExt; -fn test_setup() -> (TestEnv, InMemorySigner) { +fn test_setup() -> (TestEnv, Signer) { let epoch_length = 5; let mut genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); genesis.config.epoch_length = epoch_length; let mut env = TestEnv::builder(&genesis.config).nightshade_runtimes(&genesis).build(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); assert_eq!( send_tx( &mut env, @@ -65,7 +66,7 @@ fn send_tx( nonce: Nonce, signer_id: AccountId, receiver_id: AccountId, - signer: &InMemorySigner, + signer: &Signer, actions: Vec, ) -> ProcessTxResponse { let hash = env.clients[0].chain.head().unwrap().last_block_hash; diff --git a/integration-tests/src/tests/client/state_dump.rs b/integration-tests/src/tests/client/state_dump.rs index 93f8f7d5000..d64e692de6a 100644 --- a/integration-tests/src/tests/client/state_dump.rs +++ b/integration-tests/src/tests/client/state_dump.rs @@ -4,7 +4,7 @@ use near_async::time::{Clock, Duration}; use near_chain::near_chain_primitives::error::QueryError; use near_chain::{ChainGenesis, ChainStoreAccess, Provenance}; use near_chain_configs::ExternalStorageLocation::Filesystem; -use near_chain_configs::{DumpConfig, Genesis, NEAR_BASE}; +use near_chain_configs::{DumpConfig, Genesis, MutableConfigValue, NEAR_BASE}; use near_client::sync::external::{external_storage_location, StateFileType}; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; @@ -18,6 +18,7 @@ use near_primitives::state_part::PartId; use near_primitives::state_sync::StatePartKey; use near_primitives::transaction::SignedTransaction; use near_primitives::types::BlockHeight; +use near_primitives::validator_signer::{EmptyValidatorSigner, InMemoryValidatorSigner}; use near_primitives::views::{QueryRequest, QueryResponseKind}; use near_store::flat::store_helper; use near_store::DBCol; @@ -57,6 +58,10 @@ fn test_state_dump() { credentials_file: None, }); + let validator = MutableConfigValue::new( + Some(Arc::new(EmptyValidatorSigner::new("test0".parse().unwrap()))), + "validator_signer", + ); let mut state_sync_dumper = StateSyncDumper { clock: Clock::real(), client_config: config.clone(), @@ -64,7 +69,7 @@ fn test_state_dump() { epoch_manager: epoch_manager.clone(), shard_tracker, runtime, - account_id: Some("test0".parse().unwrap()), + validator, dump_future_runner: StateSyncDumper::arbiter_dump_future_runner(), handle: None, }; @@ -145,6 +150,10 @@ fn run_state_sync_with_dumped_parts( .build(); let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let validator = MutableConfigValue::new( + Some(Arc::new(InMemoryValidatorSigner::from_signer(signer.clone()).into())), + "validator_signer", + ); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let genesis_hash = *genesis_block.hash(); @@ -168,7 +177,7 @@ fn run_state_sync_with_dumped_parts( epoch_manager: epoch_manager.clone(), shard_tracker, runtime, - account_id: Some("test0".parse().unwrap()), + validator, dump_future_runner: StateSyncDumper::arbiter_dump_future_runner(), handle: None, }; @@ -182,6 +191,7 @@ fn run_state_sync_with_dumped_parts( account_creation_at_epoch_height * epoch_length + 1 }; + let signer: Signer = signer.into(); for i in 1..=dump_node_head_height { if i == account_creation_at_height { let tx = SignedTransaction::create_account( @@ -236,7 +246,7 @@ fn run_state_sync_with_dumped_parts( assert_ne!(header.epoch_id().clone(), final_block_header.epoch_id().clone()) } - let epoch_id = final_block_header.epoch_id().clone(); + let epoch_id = *final_block_header.epoch_id(); let epoch_info = epoch_manager.get_epoch_info(&epoch_id).unwrap(); let epoch_height = epoch_info.epoch_height(); diff --git a/integration-tests/src/tests/client/state_snapshot.rs b/integration-tests/src/tests/client/state_snapshot.rs index 8f8d78cd9f6..6d1e6b7f85b 100644 --- a/integration-tests/src/tests/client/state_snapshot.rs +++ b/integration-tests/src/tests/client/state_snapshot.rs @@ -194,7 +194,8 @@ fn test_make_state_snapshot() { .nightshade_runtimes(&genesis) .build(); - let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let signer: Signer = + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let genesis_hash = *genesis_block.hash(); diff --git a/integration-tests/src/tests/client/sync_state_nodes.rs b/integration-tests/src/tests/client/sync_state_nodes.rs index 9b60c2098fb..d27e2f5f6db 100644 --- a/integration-tests/src/tests/client/sync_state_nodes.rs +++ b/integration-tests/src/tests/client/sync_state_nodes.rs @@ -1,4 +1,3 @@ -use crate::test_helpers::heavy_test; use actix::{Actor, System}; use futures::{future, FutureExt}; use near_actix_test_utils::run_actix; @@ -30,6 +29,8 @@ use nearcore::{load_test_config, start_with_config}; use std::ops::ControlFlow; use std::sync::{Arc, RwLock}; +use crate::tests::test_helpers::heavy_test; + /// One client is in front, another must sync to it using state (fast) sync. #[test] #[cfg_attr(not(feature = "expensive_tests"), ignore)] @@ -570,13 +571,14 @@ fn test_dump_epoch_missing_chunk_in_last_block() { .clients_count(2) .use_state_snapshots() .real_stores() - .nightshade_runtimes(&genesis) + .nightshade_runtimes_congestion_control_disabled(&genesis) .build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); let mut blocks = vec![genesis_block.clone()]; let signer = - InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0") + .into(); let target_height = epoch_length + 1; for i in 1..=target_height { tracing::info!( diff --git a/integration-tests/src/tests/dependencies.rs b/integration-tests/src/tests/dependencies.rs new file mode 100644 index 00000000000..a1ab72dd7a3 --- /dev/null +++ b/integration-tests/src/tests/dependencies.rs @@ -0,0 +1,106 @@ +/* + * This test is designed to ensure that the public libraries in the project do not exceed a specified number of unique dependencies. + * Please note that this test doesn't care about total compilation units that are displayed during cargo build + * The output of the `cargo build` shows compilation units. Compilation unit is a either a external dependency or a file from the source + * + * The `LIBS_THRESHOLDS` constant defines a list of libraries along with their respective maximum allowed unique dependency counts. + * The `THRESHOLD_IS_TOO_GENEROUS` constant is used to determine if the threshold for any library is too lenient, suggesting it might need to be restricted even futher. + * + * The `get_and_assert_crate_dependencies` function takes a library name and a threshold, runs the `cargo tree` command to get the dependency tree for the library, + * extracts unique dependencies using a regex, and checks if the count of unique dependencies is below the threshold. + * + * The purpose of this test is to maintain a lean dependency graph, promoting better performance, security, and maintainability. + * As well, as limit the total amount of dependencies for public facing libraries + */ + +use std::collections::HashSet; +use std::process::Command; +use std::str; + +const LIBS_THRESHOLDS: [(&str, usize); 9] = [ + ("near-primitives", 115), + ("near-jsonrpc-primitives", 130), + ("near-chain-configs", 130), + ("near-chain-primitives", 130), + ("near-client-primitives", 150), + ("near-parameters", 65), + ("near-crypto", 75), + ("near-primitives-core", 60), + ("near-time", 30), +]; + +const THRESHOLD_IS_TOO_GENEROUS: usize = 30; + +fn get_and_assert_crate_dependencies(name: &str, threshold: usize) -> usize { + let output: std::process::Output = + Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())) + .arg("tree") + .arg("-p") + .arg(name) + .arg("--edges=normal") + .output() + .unwrap_or_else(|_| panic!("Failed to execute cargo tree for {name}")); + + assert!( + output.status.success(), + "Cargo tree failed for {name} with: {}", + str::from_utf8(&output.stderr).unwrap() + ); + + let output_str = str::from_utf8(&output.stdout).expect("Failed to convert output to string"); + + // The `cargo tree` command outputs the dependency tree of a given crate. An example line of the output might look like: + // │ ├── actix_derive v0.6.0 (proc-macro) + // This line indicates that the crate `actix_derive` version `0.6.0` is a dependency. + // + // The regex `([\w-]+) v([\d.]+(?:-\w+)?)` is used to extract the crate name and version from each line of the `cargo tree` output. + // - `([\w-]+)` captures the crate name, which can include alphanumeric characters and hyphens. + // - `v` matches the literal character 'v' that precedes the version number. + // - `([\d.]+(?:-\w+)?)` captures the version number, which can include digits, dots, and optional pre-release identifiers. + let re = regex::Regex::new(r"([\w-]+) v([\d.]+(?:-\w+)?)").unwrap(); + + let mut unique_crates = HashSet::new(); + + for cap in re.captures_iter(output_str) { + let crate_name = &cap[1]; + let crate_version = &cap[2]; + let crate_str = format!("{}-{}", crate_name, crate_version); + unique_crates.insert(crate_str); + } + let crate_count = unique_crates.len(); + + assert!( + crate_count < threshold, + "Dependencies number is too high for {name}: {} > {}", + crate_count, + threshold + ); + crate_count +} + +#[derive(Debug, Clone, PartialEq)] +#[allow(unused)] +struct CrateDeps { + pub crate_name: String, + pub crate_deps: usize, + pub suggested_new_threshold: usize, +} + +#[test] +fn test_public_libs_are_small_enough() { + let results = LIBS_THRESHOLDS + .into_iter() + .map(|(name, limit)| (name, get_and_assert_crate_dependencies(name, limit), limit)); + let mut libs_to_fix = vec![]; + for (name, result, limit) in results { + if limit - result > THRESHOLD_IS_TOO_GENEROUS { + libs_to_fix.push(CrateDeps { + crate_name: name.to_owned(), + crate_deps: result, + suggested_new_threshold: result + 10, + }); + } + } + + assert_eq!(libs_to_fix, vec![], "Good job on reducing dependency count, but it's time to review that thresholds for next components"); +} diff --git a/integration-tests/src/genesis_helpers.rs b/integration-tests/src/tests/genesis_helpers.rs similarity index 91% rename from integration-tests/src/genesis_helpers.rs rename to integration-tests/src/tests/genesis_helpers.rs index 0e451037f04..7f109ad33c2 100644 --- a/integration-tests/src/genesis_helpers.rs +++ b/integration-tests/src/tests/genesis_helpers.rs @@ -2,7 +2,7 @@ use near_async::time::Clock; use near_chain::rayon_spawner::RayonAsyncComputationSpawner; use near_chain::types::ChainConfig; use near_chain::{Chain, ChainGenesis, DoomslugThresholdMode}; -use near_chain_configs::Genesis; +use near_chain_configs::{Genesis, MutableConfigValue}; use near_epoch_manager::shard_tracker::ShardTracker; use near_epoch_manager::EpochManager; use near_primitives::block::{Block, BlockHeader}; @@ -19,7 +19,7 @@ pub fn genesis_hash(genesis: &Genesis) -> CryptoHash { } /// Utility to generate genesis header from config for testing purposes. -pub fn genesis_header(genesis: &Genesis) -> BlockHeader { +fn genesis_header(genesis: &Genesis) -> BlockHeader { let dir = tempdir().unwrap(); let store = create_test_store(); initialize_genesis_state(store.clone(), genesis, None); @@ -38,7 +38,7 @@ pub fn genesis_header(genesis: &Genesis) -> BlockHeader { ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); chain.genesis().clone() @@ -64,7 +64,7 @@ pub fn genesis_block(genesis: &Genesis) -> Block { ChainConfig::test(), None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); chain.get_block(&chain.genesis().hash().clone()).unwrap() diff --git a/integration-tests/src/tests/mod.rs b/integration-tests/src/tests/mod.rs index a59caa24eb9..2ad0910d6a2 100644 --- a/integration-tests/src/tests/mod.rs +++ b/integration-tests/src/tests/mod.rs @@ -1,10 +1,14 @@ mod client; +mod dependencies; +mod genesis_helpers; mod nearcore; +mod nearcore_utils; mod network; mod runtime; mod standard_cases; mod test_catchup; mod test_errors; +mod test_helpers; mod test_overflows; mod test_simple; mod test_tps_regression; diff --git a/integration-tests/src/tests/nearcore/node_cluster.rs b/integration-tests/src/tests/nearcore/node_cluster.rs index 739ddbc813c..05b7802564f 100644 --- a/integration-tests/src/tests/nearcore/node_cluster.rs +++ b/integration-tests/src/tests/nearcore/node_cluster.rs @@ -1,4 +1,3 @@ -use crate::test_helpers::heavy_test; use actix::Addr; use actix_rt::ArbiterHandle; use futures::future; @@ -11,6 +10,8 @@ use near_o11y::testonly::init_integration_logger; use near_primitives::types::{BlockHeight, BlockHeightDelta, NumSeats, NumShards}; use nearcore::{load_test_config, start_with_config}; +use crate::tests::test_helpers::heavy_test; + fn start_nodes( temp_dir: &std::path::Path, num_shards: NumShards, diff --git a/integration-tests/src/tests/nearcore/rpc_error_structs.rs b/integration-tests/src/tests/nearcore/rpc_error_structs.rs index b361cda6700..2e215356157 100644 --- a/integration-tests/src/tests/nearcore/rpc_error_structs.rs +++ b/integration-tests/src/tests/nearcore/rpc_error_structs.rs @@ -4,7 +4,7 @@ use actix::{Actor, System}; use futures::{future, FutureExt, TryFutureExt}; -use crate::genesis_helpers::genesis_block; +use crate::tests::genesis_helpers::genesis_block; use crate::tests::nearcore::node_cluster::NodeCluster; use near_actix_test_utils::spawn_interruptible; use near_client::GetBlock; @@ -369,7 +369,7 @@ fn test_tx_invalid_tx_error() { 1, "near.5".parse().unwrap(), "near.2".parse().unwrap(), - &signer, + &signer.into(), 10000, genesis_hash, ); diff --git a/integration-tests/src/tests/nearcore/rpc_nodes.rs b/integration-tests/src/tests/nearcore/rpc_nodes.rs index 4a9063d055d..885976a9dd7 100644 --- a/integration-tests/src/tests/nearcore/rpc_nodes.rs +++ b/integration-tests/src/tests/nearcore/rpc_nodes.rs @@ -1,4 +1,4 @@ -use crate::genesis_helpers::genesis_block; +use crate::tests::genesis_helpers::genesis_block; use crate::tests::nearcore::node_cluster::NodeCluster; use actix::clock::sleep; use actix::{Actor, System}; @@ -113,7 +113,7 @@ fn test_get_execution_outcome(is_tx_successful: bool) { 1, "near.0".parse().unwrap(), "near.1".parse().unwrap(), - &signer, + &signer.into(), 10000, genesis_hash, ) @@ -124,7 +124,7 @@ fn test_get_execution_outcome(is_tx_successful: bool) { "near.1".parse().unwrap(), 10, signer.public_key.clone(), - &signer, + &signer.into(), genesis_hash, ) }; @@ -372,7 +372,7 @@ fn test_tx_not_enough_balance_must_return_error() { 1, "near.0".parse().unwrap(), "near.1".parse().unwrap(), - &signer, + &signer.into(), 1100000000000000000000000000000000, genesis_hash, ); @@ -436,7 +436,7 @@ fn test_check_unknown_tx_must_return_error() { 1, "near.0".parse().unwrap(), "near.0".parse().unwrap(), - &signer, + &signer.into(), 10000, genesis_hash, ); @@ -500,7 +500,7 @@ fn test_tx_status_on_lightclient_must_return_does_not_track_shard() { 1, "near.1".parse().unwrap(), "near.1".parse().unwrap(), - &signer, + &signer.into(), 10000, genesis_hash, ); diff --git a/integration-tests/src/tests/nearcore/stake_nodes.rs b/integration-tests/src/tests/nearcore/stake_nodes.rs index cecc63e91c5..9b8eb41d4f1 100644 --- a/integration-tests/src/tests/nearcore/stake_nodes.rs +++ b/integration-tests/src/tests/nearcore/stake_nodes.rs @@ -8,8 +8,8 @@ use near_chain_configs::test_utils::{TESTING_INIT_BALANCE, TESTING_INIT_STAKE}; use near_primitives::num_rational::Ratio; use rand::Rng; -use crate::genesis_helpers::genesis_hash; -use crate::test_helpers::heavy_test; +use crate::tests::genesis_helpers::genesis_hash; +use crate::tests::test_helpers::heavy_test; use near_actix_test_utils::run_actix; use near_chain_configs::{Genesis, NEAR_BASE}; use near_client::{ClientActor, GetBlock, ProcessTxRequest, Query, Status, ViewClientActor}; @@ -55,6 +55,7 @@ fn init_test_staking( genesis.config.num_block_producer_seats = num_node_seats; genesis.config.block_producer_kickout_threshold = 20; genesis.config.chunk_producer_kickout_threshold = 20; + genesis.config.chunk_validator_only_kickout_threshold = 20; genesis.config.minimum_stake_divisor = minimum_stake_divisor; if !enable_rewards { genesis.config.max_inflation_rate = Ratio::from_integer(0); @@ -128,9 +129,9 @@ fn test_stake_nodes() { 1, test_nodes[1].account_id.clone(), // &*test_nodes[1].config.block_producer.as_ref().unwrap().signer, - &*test_nodes[1].signer, + &(*test_nodes[1].signer).clone().into(), TESTING_INIT_STAKE, - test_nodes[1].config.validator_signer.as_ref().unwrap().public_key(), + test_nodes[1].config.validator_signer.get().unwrap().public_key(), test_nodes[1].genesis_hash, ); actix::spawn( @@ -213,17 +214,20 @@ fn test_validator_kickout() { let stakes = (0..num_nodes / 2).map(|_| NEAR_BASE + rng.gen_range(1..100)); let stake_transactions = stakes.enumerate().map(|(i, stake)| { let test_node = &test_nodes[i]; - let signer = Arc::new(InMemorySigner::from_seed( - test_node.account_id.clone(), - KeyType::ED25519, - test_node.account_id.as_ref(), - )); + let signer = Arc::new( + InMemorySigner::from_seed( + test_node.account_id.clone(), + KeyType::ED25519, + test_node.account_id.as_ref(), + ) + .into(), + ); SignedTransaction::stake( 1, test_node.account_id.clone(), &*signer, stake, - test_node.config.validator_signer.as_ref().unwrap().public_key(), + test_node.config.validator_signer.get().unwrap().public_key(), test_node.genesis_hash, ) }); @@ -364,31 +368,37 @@ fn test_validator_join() { false, true, ); - let signer = Arc::new(InMemorySigner::from_seed( - test_nodes[1].account_id.clone(), - KeyType::ED25519, - test_nodes[1].account_id.as_ref(), - )); + let signer = Arc::new( + InMemorySigner::from_seed( + test_nodes[1].account_id.clone(), + KeyType::ED25519, + test_nodes[1].account_id.as_ref(), + ) + .into(), + ); let unstake_transaction = SignedTransaction::stake( 1, test_nodes[1].account_id.clone(), &*signer, 0, - test_nodes[1].config.validator_signer.as_ref().unwrap().public_key(), + test_nodes[1].config.validator_signer.get().unwrap().public_key(), test_nodes[1].genesis_hash, ); - let signer = Arc::new(InMemorySigner::from_seed( - test_nodes[2].account_id.clone(), - KeyType::ED25519, - test_nodes[2].account_id.as_ref(), - )); + let signer = Arc::new( + InMemorySigner::from_seed( + test_nodes[2].account_id.clone(), + KeyType::ED25519, + test_nodes[2].account_id.as_ref(), + ) + .into(), + ); let stake_transaction = SignedTransaction::stake( 1, test_nodes[2].account_id.clone(), &*signer, TESTING_INIT_STAKE, - test_nodes[2].config.validator_signer.as_ref().unwrap().public_key(), + test_nodes[2].config.validator_signer.get().unwrap().public_key(), test_nodes[2].genesis_hash, ); @@ -560,8 +570,6 @@ fn test_inflation() { && block.header.height < epoch_length * 2 { tracing::info!(?block.header.total_supply, ?block.header.height, ?initial_total_supply, epoch_length, "Step2: epoch2"); - // It's expected that validator will miss first chunk, hence will only be 95% online, getting 5/9 of their reward. - // +10% of protocol reward = 60% of max inflation are allocated. let base_reward = { let genesis_block_view = view_client .send( @@ -592,9 +600,19 @@ fn test_inflation() { .as_u128() }; // To match rounding, split into protocol reward and validator reward. + // Protocol reward is one tenth of the base reward, while validator reward is the remainder. + // There's only one validator so the second part of the computation is easier. + // The validator rewards depend on its uptime; in other words, the more blocks, chunks and endorsements + // it produces the bigger is the reward. + // In this test the validator produces 10 blocks out 10, 9 chunks out of 10 and 9 endorsements out of 10. + // Then there's a formula to translate 28/30 successes to a 10/27 reward multiplier. + // + // For additional details check: chain/epoch-manager/src/reward_calculator.rs or + // https://nomicon.io/Economics/Economic#validator-rewards-calculation let protocol_reward = base_reward * 1 / 10; + let validator_reward = base_reward - protocol_reward; let inflation = - base_reward * 1 / 10 + (base_reward - protocol_reward) * 5 / 9; + protocol_reward + validator_reward * 10 / 27; tracing::info!(?block.header.total_supply, ?block.header.height, ?initial_total_supply, epoch_length, ?inflation, "Step2: epoch2"); if block.header.total_supply == initial_total_supply + inflation { done2_copy2.store(true, Ordering::SeqCst); diff --git a/integration-tests/src/tests/nearcore/sync_nodes.rs b/integration-tests/src/tests/nearcore/sync_nodes.rs index e8b065ffa0b..40a7c9c3131 100644 --- a/integration-tests/src/tests/nearcore/sync_nodes.rs +++ b/integration-tests/src/tests/nearcore/sync_nodes.rs @@ -1,6 +1,6 @@ -use crate::genesis_helpers::genesis_block; -use crate::nearcore_utils::{add_blocks, setup_configs}; -use crate::test_helpers::heavy_test; +use crate::tests::genesis_helpers::genesis_block; +use crate::tests::nearcore_utils::{add_blocks, setup_configs}; +use crate::tests::test_helpers::heavy_test; use actix::{Actor, System}; use futures::{future, FutureExt}; use near_actix_test_utils::run_actix; @@ -170,17 +170,16 @@ fn sync_state_stake_change() { start_with_config(dir1.path(), near1.clone()).expect("start_with_config"); let genesis_hash = *genesis_block(&genesis).hash(); - let signer = Arc::new(InMemorySigner::from_seed( - "test1".parse().unwrap(), - KeyType::ED25519, - "test1", - )); + let signer = Arc::new( + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1") + .into(), + ); let unstake_transaction = SignedTransaction::stake( 1, "test1".parse().unwrap(), &*signer, TESTING_INIT_STAKE / 2, - near1.validator_signer.as_ref().unwrap().public_key(), + near1.validator_signer.get().unwrap().public_key(), genesis_hash, ); actix::spawn( diff --git a/integration-tests/src/nearcore_utils.rs b/integration-tests/src/tests/nearcore_utils.rs similarity index 97% rename from integration-tests/src/nearcore_utils.rs rename to integration-tests/src/tests/nearcore_utils.rs index 05b094489b4..8c2a0a2ec8d 100644 --- a/integration-tests/src/nearcore_utils.rs +++ b/integration-tests/src/tests/nearcore_utils.rs @@ -1,4 +1,3 @@ -use crate::genesis_helpers::genesis_block; use actix::Addr; use near_async::time::Clock; use near_chain::Block; @@ -19,6 +18,8 @@ use near_primitives::validator_signer::ValidatorSigner; use near_primitives::version::PROTOCOL_VERSION; use nearcore::{load_test_config, NearConfig}; +use super::genesis_helpers::genesis_block; + // This assumes that there is no height skipped. Otherwise epoch hash calculation will be wrong. pub fn add_blocks( clock: Clock, @@ -26,7 +27,7 @@ pub fn add_blocks( client: Addr, num: usize, epoch_length: BlockHeightDelta, - signer: &dyn ValidatorSigner, + signer: &ValidatorSigner, ) -> Vec { let mut prev = &blocks[blocks.len() - 1]; let mut block_merkle_tree = PartialMerkleTree::default(); @@ -83,7 +84,8 @@ pub fn add_blocks( signer, next_bp_hash, block_merkle_tree.root(), - clock.now_utc(), + clock.clone(), + None, ); block_merkle_tree.insert(*block.hash()); let _ = client.do_send( diff --git a/integration-tests/src/tests/network/runner.rs b/integration-tests/src/tests/network/runner.rs index fab4aa79be4..9c7ab737c74 100644 --- a/integration-tests/src/tests/network/runner.rs +++ b/integration-tests/src/tests/network/runner.rs @@ -6,7 +6,7 @@ use near_async::messaging::{noop, IntoMultiSender, IntoSender, LateBoundSender}; use near_async::time::{self, Clock}; use near_chain::types::RuntimeAdapter; use near_chain::{Chain, ChainGenesis}; -use near_chain_configs::{ClientConfig, Genesis, GenesisConfig}; +use near_chain_configs::{ClientConfig, Genesis, GenesisConfig, MutableConfigValue}; use near_chunks::shards_manager_actor::start_shards_manager; use near_client::adapter::client_sender_for_network; use near_client::{start_client, PartialWitnessActor, SyncAdapter, ViewClientActorInner}; @@ -27,7 +27,6 @@ use near_primitives::block::GenesisId; use near_primitives::network::PeerId; use near_primitives::test_utils::create_test_signer; use near_primitives::types::{AccountId, ValidatorId}; -use near_primitives::validator_signer::ValidatorSigner; use near_store::genesis::initialize_genesis_state; use near_telemetry::{TelemetryActor, TelemetryConfig}; use nearcore::NightshadeRuntime; @@ -66,7 +65,10 @@ fn setup_network_node( &genesis.config, epoch_manager.clone(), ); - let signer = Arc::new(create_test_signer(account_id.as_str())); + let validator_signer = MutableConfigValue::new( + Some(Arc::new(create_test_signer(account_id.as_str()))), + "validator_signer", + ); let telemetry_actor = ActixWrapper::new(TelemetryActor::new(TelemetryConfig::default())).start(); @@ -101,7 +103,7 @@ fn setup_network_node( state_sync_adapter, network_adapter.as_multi_sender(), shards_manager_adapter.as_sender(), - Some(signer.clone()), + validator_signer.clone(), telemetry_actor.with_auto_span_context().into_sender(), None, None, @@ -114,7 +116,7 @@ fn setup_network_node( .client_actor; let view_client_addr = ViewClientActorInner::spawn_actix_actor( Clock::real(), - config.validator.as_ref().map(|v| v.account_id()), + validator_signer.clone(), chain_genesis, epoch_manager.clone(), shard_tracker.clone(), @@ -128,7 +130,7 @@ fn setup_network_node( shard_tracker, network_adapter.as_sender(), client_actor.clone().with_auto_span_context().into_sender(), - Some(signer.validator_id().clone()), + validator_signer.clone(), runtime.store().clone(), client_config.chunk_request_retry_period, ); @@ -136,7 +138,7 @@ fn setup_network_node( Clock::real(), network_adapter.as_multi_sender(), client_actor.clone().with_auto_span_context().into_multi_sender(), - signer, + validator_signer, epoch_manager, runtime.store().clone(), )); @@ -218,7 +220,7 @@ impl StateMachine { debug!(target: "test", num_prev_actions, action = ?action_clone, "runner.rs: Action"); let pm = info.get_node(from)?.actix.addr.clone(); let peer_info = info.runner.test_config[to].peer_info(); - match tcp::Stream::connect(&peer_info, tcp::Tier::T2).await { + match tcp::Stream::connect(&peer_info, tcp::Tier::T2, &config::SocketOptions::default()).await { Ok(stream) => { pm.send(PeerManagerMessageRequest::OutboundTcpConnect(stream).with_span_context()).await?; }, Err(err) => tracing::debug!("tcp::Stream::connect({peer_info}): {err}"), } diff --git a/integration-tests/src/tests/runtime/sanity_checks.rs b/integration-tests/src/tests/runtime/sanity_checks.rs index 516517777a1..86c8e8c1054 100644 --- a/integration-tests/src/tests/runtime/sanity_checks.rs +++ b/integration-tests/src/tests/runtime/sanity_checks.rs @@ -152,6 +152,8 @@ fn test_cost_sanity() { insta::assert_debug_snapshot!( if cfg!(feature = "nightly") { "receipts_gas_profile_nightly" + } else if cfg!(feature = "statelessnet_protocol") { + "receipts_gas_profile_statelessnet_protocol" } else { "receipts_gas_profile" }, diff --git a/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile.snap b/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile.snap index 69980566408..57731f93f9c 100644 --- a/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile.snap +++ b/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile.snap @@ -17,7 +17,7 @@ expression: receipts_gas_profile CostGasUsed { cost_category: "ACTION_COST", cost: "ADD_FUNCTION_CALL_KEY_BYTE", - gas_used: 9626655, + gas_used: 238418575, }, CostGasUsed { cost_category: "ACTION_COST", @@ -42,7 +42,7 @@ expression: receipts_gas_profile CostGasUsed { cost_category: "ACTION_COST", cost: "DEPLOY_CONTRACT_BYTE", - gas_used: 231641966, + gas_used: 1621246310, }, CostGasUsed { cost_category: "ACTION_COST", @@ -52,7 +52,7 @@ expression: receipts_gas_profile CostGasUsed { cost_category: "ACTION_COST", cost: "FUNCTION_CALL_BYTE", - gas_used: 207941862, + gas_used: 571524110, }, CostGasUsed { cost_category: "ACTION_COST", diff --git a/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_nightly.snap b/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_nightly.snap index 69980566408..57731f93f9c 100644 --- a/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_nightly.snap +++ b/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_nightly.snap @@ -17,7 +17,7 @@ expression: receipts_gas_profile CostGasUsed { cost_category: "ACTION_COST", cost: "ADD_FUNCTION_CALL_KEY_BYTE", - gas_used: 9626655, + gas_used: 238418575, }, CostGasUsed { cost_category: "ACTION_COST", @@ -42,7 +42,7 @@ expression: receipts_gas_profile CostGasUsed { cost_category: "ACTION_COST", cost: "DEPLOY_CONTRACT_BYTE", - gas_used: 231641966, + gas_used: 1621246310, }, CostGasUsed { cost_category: "ACTION_COST", @@ -52,7 +52,7 @@ expression: receipts_gas_profile CostGasUsed { cost_category: "ACTION_COST", cost: "FUNCTION_CALL_BYTE", - gas_used: 207941862, + gas_used: 571524110, }, CostGasUsed { cost_category: "ACTION_COST", diff --git a/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_statelessnet_protocol.snap b/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_statelessnet_protocol.snap new file mode 100644 index 00000000000..57731f93f9c --- /dev/null +++ b/integration-tests/src/tests/runtime/snapshots/integration_tests__tests__runtime__sanity_checks__receipts_gas_profile_statelessnet_protocol.snap @@ -0,0 +1,500 @@ +--- +source: integration-tests/src/tests/runtime/sanity_checks.rs +expression: receipts_gas_profile +--- +[ + [ + CostGasUsed { + cost_category: "ACTION_COST", + cost: "ADD_FULL_ACCESS_KEY", + gas_used: 101765125000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "ADD_FUNCTION_CALL_KEY_BASE", + gas_used: 102217625000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "ADD_FUNCTION_CALL_KEY_BYTE", + gas_used: 238418575, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "CREATE_ACCOUNT", + gas_used: 7700000000000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "DELETE_ACCOUNT", + gas_used: 147489000000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "DELETE_KEY", + gas_used: 94946625000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "DEPLOY_CONTRACT_BASE", + gas_used: 184765750000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "DEPLOY_CONTRACT_BYTE", + gas_used: 1621246310, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "FUNCTION_CALL_BASE", + gas_used: 1800000000000, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "FUNCTION_CALL_BYTE", + gas_used: 571524110, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "NEW_ACTION_RECEIPT", + gas_used: 1480548358496, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "STAKE", + gas_used: 141715687500, + }, + CostGasUsed { + cost_category: "ACTION_COST", + cost: "TRANSFER", + gas_used: 230246125000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "ALT_BN128_G1_MULTIEXP_BASE", + gas_used: 713000000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "ALT_BN128_G1_MULTIEXP_ELEMENT", + gas_used: 320000000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "ALT_BN128_G1_SUM_BASE", + gas_used: 3000000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "ALT_BN128_G1_SUM_ELEMENT", + gas_used: 5000000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "ALT_BN128_PAIRING_CHECK_BASE", + gas_used: 9686000000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "ALT_BN128_PAIRING_CHECK_ELEMENT", + gas_used: 5102000000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "BASE", + gas_used: 17209927215, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "LOG_BASE", + gas_used: 7086626100, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "LOG_BYTE", + gas_used: 131987910, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "PROMISE_AND_BASE", + gas_used: 1465013400, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "PROMISE_AND_PER_PROMISE", + gas_used: 87234816, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "PROMISE_RETURN", + gas_used: 560152386, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_CACHED_TRIE_NODE", + gas_used: 4560000000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_MEMORY_BASE", + gas_used: 182690424000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_MEMORY_BYTE", + gas_used: 4744063584, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_REGISTER_BASE", + gas_used: 7551495558, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_REGISTER_BYTE", + gas_used: 25034748, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "RIPEMD160_BASE", + gas_used: 853675086, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "RIPEMD160_BLOCK", + gas_used: 680107584, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "SHA256_BASE", + gas_used: 4540970250, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "SHA256_BYTE", + gas_used: 120586755, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_HAS_KEY_BASE", + gas_used: 108079793250, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_HAS_KEY_BYTE", + gas_used: 277117605, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_READ_BASE", + gas_used: 112713691500, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_READ_KEY_BYTE", + gas_used: 278572797, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_READ_VALUE_BYTE", + gas_used: 28055025, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_REMOVE_BASE", + gas_used: 106946061000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_REMOVE_KEY_BYTE", + gas_used: 343983456, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_REMOVE_RET_VALUE_BYTE", + gas_used: 57657780, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_WRITE_BASE", + gas_used: 128393472000, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_WRITE_EVICTED_BYTE", + gas_used: 160586535, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_WRITE_KEY_BYTE", + gas_used: 281931468, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "STORAGE_WRITE_VALUE_BYTE", + gas_used: 310185390, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "TOUCHING_TRIE_NODE", + gas_used: 32203911852, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "UTF16_DECODING_BASE", + gas_used: 3543313050, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "UTF16_DECODING_BYTE", + gas_used: 1635774930, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "UTF8_DECODING_BASE", + gas_used: 46676685915, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "UTF8_DECODING_BYTE", + gas_used: 98262621423, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "VALIDATOR_STAKE_BASE", + gas_used: 911834726400, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "VALIDATOR_TOTAL_STAKE_BASE", + gas_used: 911834726400, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WRITE_MEMORY_BASE", + gas_used: 19626564027, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WRITE_MEMORY_BYTE", + gas_used: 866159496, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WRITE_REGISTER_BASE", + gas_used: 37251792318, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WRITE_REGISTER_BYTE", + gas_used: 1904583564, + }, + ], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "BASE", + gas_used: 264768111, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "BASE", + gas_used: 264768111, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_MEMORY_BASE", + gas_used: 2609863200, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "READ_MEMORY_BYTE", + gas_used: 11403999, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "UTF8_DECODING_BASE", + gas_used: 3111779061, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "UTF8_DECODING_BYTE", + gas_used: 874741437, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [], + [], + [], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 70891926, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [], + [], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + ], + [], + [ + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "BASE", + gas_used: 529536222, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BASE", + gas_used: 35445963, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "CONTRACT_LOADING_BYTES", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WASM_INSTRUCTION", + gas_used: 0, + }, + CostGasUsed { + cost_category: "WASM_HOST_COST", + cost: "WRITE_REGISTER_BASE", + gas_used: 2865522486, + }, + ], + [], + [], +] diff --git a/integration-tests/src/tests/runtime/state_viewer.rs b/integration-tests/src/tests/runtime/state_viewer.rs index df4ba18b088..8c71577aa72 100644 --- a/integration-tests/src/tests/runtime/state_viewer.rs +++ b/integration-tests/src/tests/runtime/state_viewer.rs @@ -5,12 +5,11 @@ use borsh::BorshDeserialize; use crate::runtime_utils::{get_runtime_and_trie, get_test_trie_viewer, TEST_SHARD_UID}; use near_primitives::{ account::Account, - hash::hash as sha256, - hash::CryptoHash, + hash::{hash as sha256, CryptoHash}, serialize::to_base64, trie_key::trie_key_parsers, types::{AccountId, StateRoot}, - views::{StateItem, ViewApplyState}, + views::StateItem, }; use near_primitives::{ test_utils::MockEpochInfoProvider, diff --git a/integration-tests/src/tests/runtime/test_yield_resume.rs b/integration-tests/src/tests/runtime/test_yield_resume.rs index 458c982c443..06d7ba1969f 100644 --- a/integration-tests/src/tests/runtime/test_yield_resume.rs +++ b/integration-tests/src/tests/runtime/test_yield_resume.rs @@ -35,7 +35,7 @@ fn setup_test_contract(wasm_binary: &[u8]) -> RuntimeNode { #[test] fn create_then_resume() { - let node = setup_test_contract(near_test_contracts::nightly_rs_contract()); + let node = setup_test_contract(near_test_contracts::rs_contract()); let yield_payload = vec![6u8; 16]; @@ -117,7 +117,7 @@ fn create_then_resume() { #[test] fn create_and_resume_in_one_call() { - let node = setup_test_contract(near_test_contracts::nightly_rs_contract()); + let node = setup_test_contract(near_test_contracts::rs_contract()); let yield_payload = vec![23u8; 16]; @@ -144,7 +144,7 @@ fn create_and_resume_in_one_call() { #[test] fn resume_without_yield() { - let node = setup_test_contract(near_test_contracts::nightly_rs_contract()); + let node = setup_test_contract(near_test_contracts::rs_contract()); // payload followed by data id let args: Vec = vec![42u8; 12].into_iter().chain(vec![23u8; 32].into_iter()).collect(); diff --git a/integration-tests/src/tests/standard_cases/mod.rs b/integration-tests/src/tests/standard_cases/mod.rs index 4f66a885e89..6c070daf79d 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -4,7 +4,7 @@ mod runtime; use assert_matches::assert_matches; use near_chain_configs::test_utils::{TESTING_INIT_BALANCE, TESTING_INIT_STAKE}; use near_chain_configs::NEAR_BASE; -use near_crypto::{InMemorySigner, KeyType, PublicKey}; +use near_crypto::{InMemorySigner, KeyType, PublicKey, Signer}; use near_jsonrpc_primitives::errors::ServerError; use near_parameters::{ActionCosts, ExtCosts}; use near_primitives::account::{ @@ -47,13 +47,12 @@ fn add_access_key( node: &impl Node, node_user: &dyn User, access_key: &AccessKey, - signer2: &InMemorySigner, + signer2: &Signer, ) -> FinalExecutionOutcomeView { let root = node_user.get_state_root(); let account_id = &node.account_id().unwrap(); - let transaction_result = node_user - .add_key(account_id.clone(), signer2.public_key.clone(), access_key.clone()) - .unwrap(); + let transaction_result = + node_user.add_key(account_id.clone(), signer2.public_key(), access_key.clone()).unwrap(); assert_eq!(transaction_result.status, FinalExecutionStatus::SuccessValue(Vec::new())); assert_eq!(transaction_result.receipts_outcome.len(), 1); let new_root = node_user.get_state_root(); @@ -737,13 +736,13 @@ pub fn test_swap_key(node: impl Node) { pub fn test_add_key(node: impl Node) { let account_id = &node.account_id().unwrap(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); let node_user = node.user(); add_access_key(&node, node_user.as_ref(), &AccessKey::full_access(), &signer2); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_ok()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_ok()); } pub fn test_add_existing_key(node: impl Node) { @@ -775,12 +774,12 @@ pub fn test_add_existing_key(node: impl Node) { pub fn test_delete_key(node: impl Node) { let account_id = &node.account_id().unwrap(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); let node_user = node.user(); add_access_key(&node, node_user.as_ref(), &AccessKey::full_access(), &signer2); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_ok()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_ok()); let root = node_user.get_state_root(); let transaction_result = @@ -791,7 +790,7 @@ pub fn test_delete_key(node: impl Node) { assert_ne!(new_root, root); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_err()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_ok()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_ok()); } pub fn test_delete_key_not_owned(node: impl Node) { @@ -890,12 +889,12 @@ pub fn test_add_access_key_function_call(node: impl Node) { method_names: vec![], }), }; - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); let result = add_access_key(&node, node_user.as_ref(), &access_key, &signer2); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - let view_access_key = node_user.get_access_key(account_id, &signer2.public_key).unwrap(); + let view_access_key = node_user.get_access_key(account_id, &signer2.public_key()).unwrap(); assert_access_key(&access_key, view_access_key, &result, node_user.as_ref()); } @@ -910,22 +909,22 @@ pub fn test_delete_access_key(node: impl Node) { method_names: vec![], }), }; - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); add_access_key(&node, node_user.as_ref(), &access_key, &signer2); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_ok()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_ok()); let root = node_user.get_state_root(); let transaction_result = - node_user.delete_key(account_id.clone(), signer2.public_key.clone()).unwrap(); + node_user.delete_key(account_id.clone(), signer2.public_key()).unwrap(); assert_eq!(transaction_result.status, FinalExecutionStatus::SuccessValue(Vec::new())); assert_eq!(transaction_result.receipts_outcome.len(), 1); let new_root = node_user.get_state_root(); assert_ne!(new_root, root); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_err()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_err()); } pub fn test_add_access_key_with_allowance(node: impl Node) { @@ -939,7 +938,7 @@ pub fn test_add_access_key_with_allowance(node: impl Node) { }), }; let node_user = node.user(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); let account = node_user.view_account(account_id).unwrap(); let initial_balance = account.amount; let fee_helper = fee_helper(&node); @@ -950,7 +949,7 @@ pub fn test_add_access_key_with_allowance(node: impl Node) { assert_eq!(account.amount, initial_balance - add_access_key_cost); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - let view_access_key = node_user.get_access_key(account_id, &signer2.public_key).unwrap(); + let view_access_key = node_user.get_access_key(account_id, &signer2.public_key()).unwrap(); assert_access_key(&access_key, view_access_key, &result, node_user.as_ref()); } @@ -965,7 +964,7 @@ pub fn test_delete_access_key_with_allowance(node: impl Node) { }), }; let node_user = node.user(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); let account = node_user.view_account(account_id).unwrap(); let initial_balance = account.amount; let fee_helper = fee_helper(&node); @@ -973,12 +972,12 @@ pub fn test_delete_access_key_with_allowance(node: impl Node) { add_access_key(&node, node_user.as_ref(), &access_key, &signer2); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_ok()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_ok()); let root = node_user.get_state_root(); let delete_access_key_cost = fee_helper.delete_key_cost(); let transaction_result = - node_user.delete_key(account_id.clone(), signer2.public_key.clone()).unwrap(); + node_user.delete_key(account_id.clone(), signer2.public_key()).unwrap(); assert_eq!(transaction_result.status, FinalExecutionStatus::SuccessValue(Vec::new())); assert_eq!(transaction_result.receipts_outcome.len(), 1); let new_root = node_user.get_state_root(); @@ -988,7 +987,7 @@ pub fn test_delete_access_key_with_allowance(node: impl Node) { assert_eq!(account.amount, initial_balance - add_access_key_cost - delete_access_key_cost); assert!(node_user.get_access_key(account_id, &node.signer().public_key()).is_ok()); - assert!(node_user.get_access_key(account_id, &signer2.public_key).is_err()); + assert!(node_user.get_access_key(account_id, &signer2.public_key()).is_err()); } pub fn test_access_key_smart_contract(node: impl Node) { @@ -1002,7 +1001,8 @@ pub fn test_access_key_smart_contract(node: impl Node) { }; let mut node_user = node.user(); let account_id = &node.account_id().unwrap(); - let signer2 = Arc::new(InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519)); + let signer2 = + Arc::new(InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into()); add_access_key(&node, node_user.as_ref(), &access_key, &signer2); node_user.set_signer(signer2.clone()); @@ -1029,7 +1029,7 @@ pub fn test_access_key_smart_contract(node: impl Node) { let new_root = node_user.get_state_root(); assert_ne!(root, new_root); - let view_access_key = node_user.get_access_key(account_id, &signer2.public_key).unwrap(); + let view_access_key = node_user.get_access_key(account_id, &signer2.public_key()).unwrap(); assert_eq!( view_access_key, AccessKey { @@ -1055,7 +1055,7 @@ pub fn test_access_key_smart_contract_reject_method_name(node: impl Node) { }; let mut node_user = node.user(); let account_id = &node.account_id().unwrap(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); add_access_key(&node, node_user.as_ref(), &access_key, &signer2); node_user.set_signer(Arc::new(signer2)); @@ -1083,7 +1083,7 @@ pub fn test_access_key_smart_contract_reject_contract_id(node: impl Node) { }; let mut node_user = node.user(); let account_id = &node.account_id().unwrap(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); add_access_key(&node, node_user.as_ref(), &access_key, &signer2); node_user.set_signer(Arc::new(signer2)); @@ -1119,7 +1119,7 @@ pub fn test_access_key_reject_non_function_call(node: impl Node) { }), }; let mut node_user = node.user(); - let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519); + let signer2 = InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into(); add_access_key(&node, node_user.as_ref(), &access_key, &signer2); node_user.set_signer(Arc::new(signer2)); @@ -1205,11 +1205,9 @@ pub fn test_unstake_while_not_staked(node: impl Node) { pub fn test_fail_not_enough_balance_for_storage(node: impl Node) { let mut node_user = node.user(); let account_id = bob_account(); - let signer = Arc::new(InMemorySigner::from_seed( - account_id.clone(), - KeyType::ED25519, - account_id.as_ref(), - )); + let signer = Arc::new( + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()).into(), + ); node_user.set_signer(signer); node_user.send_money(account_id, alice_account(), 10).unwrap_err(); } diff --git a/integration-tests/src/tests/standard_cases/rpc.rs b/integration-tests/src/tests/standard_cases/rpc.rs index 07cd524f855..a6b59b84d1c 100644 --- a/integration-tests/src/tests/standard_cases/rpc.rs +++ b/integration-tests/src/tests/standard_cases/rpc.rs @@ -2,8 +2,8 @@ //! The communication is performed through `RPCUser` that uses the standard RPC API to communicate. use crate::node::{create_nodes_from_seeds, Node, NodeConfig, ThreadNode}; -use crate::test_helpers::heavy_test; use crate::tests::standard_cases::*; +use crate::tests::test_helpers::heavy_test; use near_o11y::testonly::init_test_module_logger; use std::thread; use std::time::Duration; diff --git a/integration-tests/src/tests/standard_cases/runtime.rs b/integration-tests/src/tests/standard_cases/runtime.rs index ac2cadf7e3e..79520ba543d 100644 --- a/integration-tests/src/tests/standard_cases/runtime.rs +++ b/integration-tests/src/tests/standard_cases/runtime.rs @@ -5,6 +5,7 @@ use near_crypto::SecretKey; use near_primitives::checked_feature; use near_primitives::state_record::StateRecord; use near_primitives::version::PROTOCOL_VERSION; +use std::sync::Arc; use testlib::runtime_utils::{add_test_contract, alice_account, bob_account}; fn create_runtime_node() -> RuntimeNode { @@ -21,7 +22,8 @@ fn create_runtime_with_expensive_storage() -> RuntimeNode { add_test_contract(&mut genesis, &bob_account()); // Set expensive state requirements and add alice more money. let mut runtime_config = RuntimeConfig::test(); - runtime_config.fees.storage_usage_config.storage_amount_per_byte = TESTING_INIT_BALANCE / 1000; + let fees = Arc::make_mut(&mut runtime_config.fees); + fees.storage_usage_config.storage_amount_per_byte = TESTING_INIT_BALANCE / 1000; let records = genesis.force_read_records().as_mut(); match &mut records[0] { StateRecord::Account { account, .. } => account.set_amount(TESTING_INIT_BALANCE * 10000), diff --git a/integration-tests/src/tests/test_catchup.rs b/integration-tests/src/tests/test_catchup.rs index f2a3f27ccfb..c1c0a8672ef 100644 --- a/integration-tests/src/tests/test_catchup.rs +++ b/integration-tests/src/tests/test_catchup.rs @@ -2,9 +2,10 @@ use std::time::Duration; use crate::node::{create_nodes, Node}; -use crate::test_helpers::{heavy_test, wait}; use std::sync::{Arc, RwLock}; +use super::test_helpers::{heavy_test, wait}; + #[test] #[cfg_attr(not(feature = "expensive_tests"), ignore)] fn test_catchup() { diff --git a/integration-tests/src/tests/test_errors.rs b/integration-tests/src/tests/test_errors.rs index 9fbd1d67f8a..c3dfda51e63 100644 --- a/integration-tests/src/tests/test_errors.rs +++ b/integration-tests/src/tests/test_errors.rs @@ -33,7 +33,7 @@ fn start_node() -> ThreadNode { fn test_check_tx_error_log() { let node = start_node(); let signer = - Arc::new(InMemorySigner::from_seed(alice_account(), KeyType::ED25519, "alice.near")); + Arc::new(InMemorySigner::from_seed(alice_account(), KeyType::ED25519, "alice.near").into()); let block_hash = node.user().get_best_block_hash().unwrap(); let tx = SignedTransaction::from_actions( 1, @@ -44,7 +44,7 @@ fn test_check_tx_error_log() { Action::CreateAccount(CreateAccountAction {}), Action::Transfer(TransferAction { deposit: 1_000 }), Action::AddKey(Box::new(AddKeyAction { - public_key: signer.public_key.clone(), + public_key: signer.public_key(), access_key: AccessKey::full_access(), })), ], @@ -57,7 +57,7 @@ fn test_check_tx_error_log() { tx_result, InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::AccessKeyNotFound { account_id: bob_account(), - public_key: signer.public_key.clone().into() + public_key: Box::new(signer.public_key()), }) .rpc_into() ); @@ -73,19 +73,19 @@ fn test_deliver_tx_error_log() { node.genesis().config.min_gas_price, ); let signer = - Arc::new(InMemorySigner::from_seed(alice_account(), KeyType::ED25519, "alice.near")); + Arc::new(InMemorySigner::from_seed(alice_account(), KeyType::ED25519, "alice.near").into()); let block_hash = node.user().get_best_block_hash().unwrap(); let cost = fee_helper.create_account_transfer_full_key_cost_no_reward(); let tx = SignedTransaction::from_actions( 1, alice_account(), "test.near".parse().unwrap(), - &*signer, + &signer, vec![ Action::CreateAccount(CreateAccountAction {}), Action::Transfer(TransferAction { deposit: TESTING_INIT_BALANCE + 1 }), Action::AddKey(Box::new(AddKeyAction { - public_key: signer.public_key.clone(), + public_key: signer.public_key(), access_key: AccessKey::full_access(), })), ], diff --git a/integration-tests/src/tests/test_helpers.rs b/integration-tests/src/tests/test_helpers.rs new file mode 100644 index 00000000000..3acf8add529 --- /dev/null +++ b/integration-tests/src/tests/test_helpers.rs @@ -0,0 +1,29 @@ +use once_cell::sync::Lazy; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; + +static HEAVY_TESTS_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); + +pub fn heavy_test(f: F) +where + F: FnOnce(), +{ + let _guard = HEAVY_TESTS_LOCK.lock(); + f(); +} + +pub fn wait(mut f: F, check_interval_ms: u64, max_wait_ms: u64) +where + F: FnMut() -> bool, +{ + let mut ms_slept = 0; + while !f() { + thread::sleep(Duration::from_millis(check_interval_ms)); + ms_slept += check_interval_ms; + if ms_slept > max_wait_ms { + println!("BBBB Slept {}; max_wait_ms {}", ms_slept, max_wait_ms); + panic!("Timed out waiting for the condition"); + } + } +} diff --git a/integration-tests/src/tests/test_simple.rs b/integration-tests/src/tests/test_simple.rs index 1626df9df31..bd0157b03da 100644 --- a/integration-tests/src/tests/test_simple.rs +++ b/integration-tests/src/tests/test_simple.rs @@ -1,11 +1,12 @@ //! Simply starts and runs testnet for a while. use crate::node::{create_nodes, sample_two_nodes, Node}; -use crate::test_helpers::{heavy_test, wait}; use near_async::time::Clock; use near_o11y::testonly::init_integration_logger; use near_primitives::transaction::SignedTransaction; use std::time::Duration; +use super::test_helpers::{heavy_test, wait}; + fn run_multiple_nodes(num_nodes: usize, num_trials: usize, test_prefix: &str) { init_integration_logger(); diff --git a/integration-tests/src/tests/test_tps_regression.rs b/integration-tests/src/tests/test_tps_regression.rs index 5ad1bbbb2e5..e673133b806 100644 --- a/integration-tests/src/tests/test_tps_regression.rs +++ b/integration-tests/src/tests/test_tps_regression.rs @@ -3,7 +3,6 @@ //! no choking on transactions). The input tps -- is how fast the nodes can be accepting //! transactions. The output tps -- is how fast the nodes propagate transactions into the blocks. use crate::node::{create_nodes, sample_queryable_node, sample_two_nodes, Node}; -use crate::test_helpers::heavy_test; use near_primitives::transaction::SignedTransaction; use std::io::stdout; use std::io::Write; @@ -11,6 +10,8 @@ use std::sync::{Arc, RwLock}; use std::thread; use std::time::{Duration, Instant}; +use super::test_helpers::heavy_test; + /// Creates and sends a random transaction. /// Args: /// `nodes`: node to submit to; diff --git a/integration-tests/src/user/mod.rs b/integration-tests/src/user/mod.rs index e489aa4887d..e0b65ca549d 100644 --- a/integration-tests/src/user/mod.rs +++ b/integration-tests/src/user/mod.rs @@ -88,9 +88,9 @@ pub trait User { public_key: &PublicKey, ) -> Result; - fn signer(&self) -> Arc; + fn signer(&self) -> Arc; - fn set_signer(&mut self, signer: Arc); + fn set_signer(&mut self, signer: Arc); fn sign_and_commit_actions( &self, diff --git a/integration-tests/src/user/rpc_user.rs b/integration-tests/src/user/rpc_user.rs index 151a29dc650..6b0a445976c 100644 --- a/integration-tests/src/user/rpc_user.rs +++ b/integration-tests/src/user/rpc_user.rs @@ -28,7 +28,7 @@ use crate::user::User; pub struct RpcUser { account_id: AccountId, - signer: Arc, + signer: Arc, addr: String, } @@ -43,7 +43,7 @@ impl RpcUser { .block_on(async move { f(new_client(&format!("http://{}", addr))).await }) } - pub fn new(addr: &str, account_id: AccountId, signer: Arc) -> RpcUser { + pub fn new(addr: &str, account_id: AccountId, signer: Arc) -> RpcUser { RpcUser { account_id, addr: addr.to_owned(), signer } } @@ -222,11 +222,11 @@ impl User for RpcUser { } } - fn signer(&self) -> Arc { + fn signer(&self) -> Arc { self.signer.clone() } - fn set_signer(&mut self, signer: Arc) { + fn set_signer(&mut self, signer: Arc) { self.signer = signer; } } diff --git a/integration-tests/src/user/runtime_user.rs b/integration-tests/src/user/runtime_user.rs index 35a0cc97037..7be65456a57 100644 --- a/integration-tests/src/user/runtime_user.rs +++ b/integration-tests/src/user/runtime_user.rs @@ -6,7 +6,7 @@ use near_chain_configs::MIN_GAS_PRICE; use near_crypto::{PublicKey, Signer}; use near_jsonrpc_primitives::errors::ServerError; use near_parameters::RuntimeConfig; -use near_primitives::congestion_info::ExtendedCongestionInfo; +use near_primitives::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; use near_primitives::errors::{RuntimeError, TxExecutionError}; use near_primitives::hash::CryptoHash; use near_primitives::receipt::Receipt; @@ -18,11 +18,11 @@ use near_primitives::version::{ProtocolFeature, PROTOCOL_VERSION}; use near_primitives::views::{ AccessKeyView, AccountView, BlockView, CallResult, ChunkView, ContractCodeView, ExecutionOutcomeView, ExecutionOutcomeWithIdView, ExecutionStatusView, - FinalExecutionOutcomeView, FinalExecutionStatus, ViewApplyState, ViewStateResult, + FinalExecutionOutcomeView, FinalExecutionStatus, ViewStateResult, }; use near_store::{ShardTries, TrieUpdate}; use node_runtime::state_viewer::TrieViewer; -use node_runtime::{ApplyState, Runtime}; +use node_runtime::{state_viewer::ViewApplyState, ApplyState, Runtime}; use crate::user::{User, POISONED_LOCK_ERR}; use near_primitives::shard_layout::ShardUId; @@ -44,7 +44,7 @@ impl MockClient { pub struct RuntimeUser { pub account_id: AccountId, - pub signer: Arc, + pub signer: Arc, pub trie_viewer: TrieViewer, pub client: Arc>, // Store results of applying transactions/receipts @@ -59,7 +59,7 @@ pub struct RuntimeUser { impl RuntimeUser { pub fn new( account_id: AccountId, - signer: Arc, + signer: Arc, client: Arc>, ) -> Self { let runtime_config = Arc::new(client.read().unwrap().runtime_config.clone()); @@ -117,8 +117,8 @@ impl RuntimeUser { } RuntimeError::BalanceMismatchError(e) => panic!("{}", e), RuntimeError::StorageError(e) => panic!("Storage error {:?}", e), - RuntimeError::UnexpectedIntegerOverflow => { - panic!("UnexpectedIntegerOverflow error") + RuntimeError::UnexpectedIntegerOverflow(reason) => { + panic!("UnexpectedIntegerOverflow error {reason}") } RuntimeError::ReceiptValidationError(e) => panic!("{}", e), RuntimeError::ValidatorError(e) => panic!("{}", e), @@ -159,8 +159,9 @@ impl RuntimeUser { let congestion_info = if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { all_shard_ids.into_iter().map(|id| (id, ExtendedCongestionInfo::default())).collect() } else { - HashMap::new() + Default::default() }; + let congestion_info = BlockCongestionInfo::new(congestion_info); ApplyState { apply_reason: None, @@ -390,11 +391,11 @@ impl User for RuntimeUser { .map_err(|err| err.to_string()) } - fn signer(&self) -> Arc { + fn signer(&self) -> Arc { self.signer.clone() } - fn set_signer(&mut self, signer: Arc) { + fn set_signer(&mut self, signer: Arc) { self.signer = signer; } } diff --git a/nearcore/Cargo.toml b/nearcore/Cargo.toml index 65f04581419..203661709e5 100644 --- a/nearcore/Cargo.toml +++ b/nearcore/Cargo.toml @@ -185,8 +185,9 @@ statelessnet_protocol = [ ] sandbox = [ "near-client/sandbox", - "node-runtime/sandbox", "near-jsonrpc/sandbox", + "near-o11y/sandbox", + "node-runtime/sandbox", ] io_trace = ["near-vm-runner/io_trace"] diff --git a/nearcore/res/example-config-gc.json b/nearcore/res/example-config-gc.json index aac7f1551a2..2da1ccb4b22 100644 --- a/nearcore/res/example-config-gc.json +++ b/nearcore/res/example-config-gc.json @@ -25,7 +25,7 @@ "telemetry": { "endpoints": [ "https://explorer.mainnet.near.org/api/nodes", - "https://telemetry.nearone.org/nodes/mainnet" + "https://telemetry.nearone.org/nodes" ] }, "network": { diff --git a/nearcore/res/example-config-no-gc.json b/nearcore/res/example-config-no-gc.json index 1686affa6ac..23ba9e841ed 100644 --- a/nearcore/res/example-config-no-gc.json +++ b/nearcore/res/example-config-no-gc.json @@ -25,7 +25,7 @@ "telemetry": { "endpoints": [ "https://explorer.mainnet.near.org/api/nodes", - "https://telemetry.nearone.org/nodes/mainnet" + "https://telemetry.nearone.org/nodes" ] }, "network": { diff --git a/nearcore/src/config.rs b/nearcore/src/config.rs index e7b058afae7..e495cbd33e1 100644 --- a/nearcore/src/config.rs +++ b/nearcore/src/config.rs @@ -20,15 +20,16 @@ use near_chain_configs::{ default_tx_routing_height_horizon, default_view_client_threads, default_view_client_throttle_period, get_initial_supply, ChunkDistributionNetworkConfig, ClientConfig, GCConfig, Genesis, GenesisConfig, GenesisValidationMode, LogSummaryStyle, - MutableConfigValue, ReshardingConfig, StateSyncConfig, BLOCK_PRODUCER_KICKOUT_THRESHOLD, - CHUNK_PRODUCER_KICKOUT_THRESHOLD, EXPECTED_EPOCH_LENGTH, FISHERMEN_THRESHOLD, + MutableConfigValue, MutableValidatorSigner, ReshardingConfig, StateSyncConfig, + BLOCK_PRODUCER_KICKOUT_THRESHOLD, CHUNK_PRODUCER_KICKOUT_THRESHOLD, + CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD, EXPECTED_EPOCH_LENGTH, FISHERMEN_THRESHOLD, GAS_PRICE_ADJUSTMENT_RATE, GENESIS_CONFIG_FILENAME, INITIAL_GAS_LIMIT, MAX_INFLATION_RATE, MIN_BLOCK_PRODUCTION_DELAY, MIN_GAS_PRICE, NEAR_BASE, NUM_BLOCKS_PER_YEAR, NUM_BLOCK_PRODUCER_SEATS, PROTOCOL_REWARD_RATE, PROTOCOL_UPGRADE_STAKE_THRESHOLD, TRANSACTION_VALIDITY_PERIOD, }; use near_config_utils::{ValidationError, ValidationErrors}; -use near_crypto::{InMemorySigner, KeyFile, KeyType, PublicKey, Signer}; +use near_crypto::{InMemorySigner, KeyFile, KeyType, PublicKey}; use near_epoch_manager::EpochManagerHandle; #[cfg(feature = "json_rpc")] use near_jsonrpc::RpcConfig; @@ -106,7 +107,7 @@ pub const NODE_KEY_FILE: &str = "node_key.json"; pub const VALIDATOR_KEY_FILE: &str = "validator_key.json"; pub const NETWORK_LEGACY_TELEMETRY_URL: &str = "https://explorer.{}.near.org/api/nodes"; -pub const NETWORK_TELEMETRY_URL: &str = "https://telemetry.nearone.org/nodes/{}"; +pub const NETWORK_TELEMETRY_URL: &str = "https://telemetry.nearone.org/nodes"; fn default_doomslug_step_period() -> Duration { Duration::milliseconds(100) @@ -221,6 +222,7 @@ pub struct Config { pub network: near_network::config_json::Config, pub consensus: Consensus, pub tracked_accounts: Vec, + pub tracked_shadow_validator: Option, pub tracked_shards: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub tracked_shard_schedule: Option>>, @@ -312,7 +314,7 @@ pub struct Config { pub max_loaded_contracts: usize, /// Save observed instances of ChunkStateWitness to the database in DBCol::LatestChunkStateWitnesses. /// Saving the latest witnesses is useful for analysis and debugging. - /// When this option is enabled, the node will save ALL witnesses it oberves, even invalid ones, + /// When this option is enabled, the node will save ALL witnesses it observes, even invalid ones, /// which can cause extra load on the database. This option is not recommended for production use, /// as a large number of incoming witnesses could cause denial of service. pub save_latest_witnesses: bool, @@ -336,6 +338,7 @@ impl Default for Config { network: Default::default(), consensus: Consensus::default(), tracked_accounts: vec![], + tracked_shadow_validator: None, tracked_shards: vec![], tracked_shard_schedule: None, archive: false, @@ -504,7 +507,10 @@ pub struct NearConfig { pub rosetta_rpc_config: Option, pub telemetry_config: TelemetryConfig, pub genesis: Genesis, - pub validator_signer: Option>, + /// Contains validator key for this node. This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + pub validator_signer: MutableValidatorSigner, } impl NearConfig { @@ -512,7 +518,7 @@ impl NearConfig { config: Config, genesis: Genesis, network_key_pair: KeyFile, - validator_signer: Option>, + validator_signer: MutableValidatorSigner, ) -> anyhow::Result { Ok(NearConfig { config: config.clone(), @@ -553,6 +559,7 @@ impl NearConfig { doosmslug_step_period: config.consensus.doomslug_step_period, tracked_accounts: config.tracked_accounts, tracked_shards: config.tracked_shards, + tracked_shadow_validator: config.tracked_shadow_validator, tracked_shard_schedule: config.tracked_shard_schedule.unwrap_or(vec![]), archive: config.archive, save_trie_changes: config.save_trie_changes.unwrap_or(!config.archive), @@ -618,7 +625,7 @@ impl NearConfig { self.config.write_to_file(&dir.join(CONFIG_FILENAME)).expect("Error writing config"); - if let Some(validator_signer) = &self.validator_signer { + if let Some(validator_signer) = &self.validator_signer.get() { validator_signer .write_to_file(&dir.join(&self.config.validator_key_file)) .expect("Error writing validator key file"); @@ -863,7 +870,7 @@ pub fn init_configs( bail!("Test seed is not supported for {chain_id}"); } config.telemetry.endpoints.push(NETWORK_LEGACY_TELEMETRY_URL.replace("{}", &chain_id)); - config.telemetry.endpoints.push(NETWORK_TELEMETRY_URL.replace("{}", &chain_id)); + config.telemetry.endpoints.push(NETWORK_TELEMETRY_URL.to_string()); } _ => { // Create new configuration, key files and genesis for one validator. @@ -982,6 +989,7 @@ pub fn init_configs( gas_price_adjustment_rate: GAS_PRICE_ADJUSTMENT_RATE, block_producer_kickout_threshold: BLOCK_PRODUCER_KICKOUT_THRESHOLD, chunk_producer_kickout_threshold: CHUNK_PRODUCER_KICKOUT_THRESHOLD, + chunk_validator_only_kickout_threshold: CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD, online_max_threshold: Rational32::new(99, 100), online_min_threshold: Rational32::new(BLOCK_PRODUCER_KICKOUT_THRESHOLD as i32, 100), validators: vec![AccountInfo { @@ -1015,7 +1023,7 @@ pub fn create_testnet_configs_from_seeds( local_ports: bool, archive: bool, tracked_shards: Vec, -) -> (Vec, Vec, Vec, Genesis) { +) -> (Vec, Vec, Vec, Genesis) { let num_validator_seats = (seeds.len() - num_non_validator_seats as usize) as NumSeats; let validator_signers = seeds.iter().map(|seed| create_test_signer(seed.as_str())).collect::>(); @@ -1075,8 +1083,7 @@ pub fn create_testnet_configs( local_ports: bool, archive: bool, tracked_shards: Vec, -) -> (Vec, Vec, Vec, Genesis, Vec) -{ +) -> (Vec, Vec, Vec, Genesis, Vec) { let shard_keys = vec![]; let (configs, validator_signers, network_signers, genesis) = create_testnet_configs_from_seeds( (0..(num_validator_seats + num_non_validator_seats)) @@ -1221,6 +1228,20 @@ impl From for KeyFile { } } +pub fn load_validator_key(validator_file: &Path) -> anyhow::Result>> { + if !validator_file.exists() { + return Ok(None); + } + match InMemoryValidatorSigner::from_file(&validator_file) { + Ok(signer) => Ok(Some(Arc::new(signer.into()))), + Err(_) => { + let error_message = + format!("Failed initializing validator signer from {}", validator_file.display()); + Err(anyhow!(error_message)) + } + } +} + pub fn load_config( dir: &Path, genesis_validation: GenesisValidationMode, @@ -1234,21 +1255,13 @@ pub fn load_config( validation_errors.push_errors(e) }; - let validator_file = dir.join(&config.validator_key_file); - let validator_signer = if validator_file.exists() { - match InMemoryValidatorSigner::from_file(&validator_file) { - Ok(signer) => Some(Arc::new(signer) as Arc), - Err(_) => { - let error_message = format!( - "Failed initializing validator signer from {}", - validator_file.display() - ); - validation_errors.push_validator_key_file_error(error_message); - None - } + let validator_file: PathBuf = dir.join(&config.validator_key_file); + let validator_signer = match load_validator_key(&validator_file) { + Ok(validator_signer) => validator_signer, + Err(e) => { + validation_errors.push_validator_key_file_error(e.to_string()); + None } - } else { - None }; let node_key_path = dir.join(&config.node_key_file); @@ -1309,7 +1322,7 @@ pub fn load_config( config, genesis.unwrap(), network_signer.unwrap().into(), - validator_signer, + MutableConfigValue::new(validator_signer, "validator_signer"), )?; Ok(near_config) } @@ -1329,10 +1342,16 @@ pub fn load_test_config(seed: &str, addr: tcp::ListenerAddr, genesis: Genesis) - } else { let signer = Arc::new(InMemorySigner::from_seed(seed.parse().unwrap(), KeyType::ED25519, seed)); - let validator_signer = Arc::new(create_test_signer(seed)) as Arc; + let validator_signer = Arc::new(create_test_signer(seed)) as Arc; (signer, Some(validator_signer)) }; - NearConfig::new(config, genesis, signer.into(), validator_signer).unwrap() + NearConfig::new( + config, + genesis, + signer.into(), + MutableConfigValue::new(validator_signer, "validator_signer"), + ) + .unwrap() } #[cfg(test)] @@ -1510,7 +1529,7 @@ mod tests { assert_eq!( vec![ "https://explorer.mainnet.near.org/api/nodes".to_string(), - "https://telemetry.nearone.org/nodes/mainnet".to_string() + "https://telemetry.nearone.org/nodes".to_string() ], config.telemetry.endpoints ); diff --git a/nearcore/src/config_duration_test.rs b/nearcore/src/config_duration_test.rs index 2509a83a470..3a60a26f082 100644 --- a/nearcore/src/config_duration_test.rs +++ b/nearcore/src/config_duration_test.rs @@ -1,7 +1,10 @@ +use std::str::FromStr; + use crate::config::Config; use near_jsonrpc::RpcConfig; use near_network::config_json::{ExperimentalConfig, NetworkConfigOverrides}; use near_o11y::testonly::init_test_logger; +use near_primitives::types::AccountId; use near_store::StoreConfig; use serde::ser::{ SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, @@ -40,6 +43,7 @@ fn test_config_duration_all_std() { rosetta_rpc: Some(Default::default()), save_trie_changes: Some(Default::default()), split_storage: Some(Default::default()), + tracked_shadow_validator: Some(AccountId::from_str("test").unwrap()), tracked_shard_schedule: Some(Default::default()), transaction_pool_size_limit: Some(Default::default()), state_sync: Some(Default::default()), @@ -58,6 +62,9 @@ fn test_config_duration_all_std() { routed_message_ttl: Some(0), routing_table_update_rate_limit_burst: Some(0), routing_table_update_rate_limit_qps: Some(0.0), + received_messages_rate_limits: Some( + near_network::MessagesLimitsOverrideConfig::default(), + ), }, ..Default::default() }, diff --git a/nearcore/src/download_file.rs b/nearcore/src/download_file.rs index 27e4fcdc4d1..ae2dc794066 100644 --- a/nearcore/src/download_file.rs +++ b/nearcore/src/download_file.rs @@ -1,10 +1,12 @@ -use hyper::body::HttpBody; +use hyper::{body::HttpBody, StatusCode}; use indicatif::{ProgressBar, ProgressStyle}; use std::path::{Path, PathBuf}; use tokio::io::AsyncWriteExt; #[derive(thiserror::Error, Debug)] pub enum FileDownloadError { + #[error("Unsuccessful HTTP connection. Return code: {0}")] + HttpResponseCode(StatusCode), #[error("{0}")] HttpError(hyper::Error), #[error("Failed to open temporary file")] @@ -45,6 +47,10 @@ async fn download_file_impl( let https_connector = hyper_tls::HttpsConnector::new(); let client = hyper::Client::builder().build::<_, hyper::Body>(https_connector); let mut resp = client.get(uri).await.map_err(FileDownloadError::HttpError)?; + let status_code = resp.status(); + if !status_code.is_success() { + return Err(FileDownloadError::HttpResponseCode(status_code)); + } let bar = if let Some(file_size) = resp.size_hint().upper() { let bar = ProgressBar::new(file_size); bar.set_style( @@ -321,6 +327,41 @@ mod tests { check_file_download(payload, Err("Failed to decompress XZ stream: lzma data error")).await; } + #[tokio::test] + async fn test_file_download_bad_http_code() { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + let tmp_file = tempfile::NamedTempFile::new().unwrap(); + + tokio::task::spawn(async move { + let make_svc = make_service_fn(move |_conn| { + let handle_request = move |_: Request| async move { + Ok::<_, Infallible>( + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("")) + .unwrap(), + ) + }; + async move { Ok::<_, Infallible>(service_fn(handle_request)) } + }); + let server = Server::from_tcp(listener).unwrap().serve(make_svc); + if let Err(e) = server.await { + eprintln!("server error: {}", e); + } + }); + + let res = download_file(&format!("http://localhost:{}", port), tmp_file.path()) + .await + .map(|()| std::fs::read(tmp_file.path()).unwrap()); + + assert!( + matches!(res, Err(FileDownloadError::HttpResponseCode(StatusCode::NOT_FOUND))), + "got {:?}", + res + ); + } + fn auto_xz_test_write_file( buffer: &[u8], chunk_size: usize, diff --git a/nearcore/src/dyn_config.rs b/nearcore/src/dyn_config.rs index b02e24a3b02..9a480d0c17a 100644 --- a/nearcore/src/dyn_config.rs +++ b/nearcore/src/dyn_config.rs @@ -2,8 +2,10 @@ use crate::config::Config; use near_chain_configs::UpdateableClientConfig; use near_dyn_configs::{UpdateableConfigLoaderError, UpdateableConfigs}; use near_o11y::log_config::LogConfig; +use near_primitives::validator_signer::ValidatorSigner; use serde::Deserialize; use std::path::{Path, PathBuf}; +use std::sync::Arc; pub const LOG_CONFIG_FILENAME: &str = "log_config.json"; @@ -19,22 +21,34 @@ pub fn read_updateable_configs( None } }; - let updateable_client_config = - match Config::from_file(&home_dir.join(crate::config::CONFIG_FILENAME)) - .map(get_updateable_client_config) - { - Ok(config) => Some(config), - Err(err) => { - errs.push(UpdateableConfigLoaderError::ConfigFileError { - file: PathBuf::from(crate::config::CONFIG_FILENAME), - err: err.into(), - }); - None - } - }; + let config = match Config::from_file(&home_dir.join(crate::config::CONFIG_FILENAME)) { + Ok(config) => Some(config), + Err(err) => { + errs.push(UpdateableConfigLoaderError::ConfigFileError { + file: PathBuf::from(crate::config::CONFIG_FILENAME), + err: err.into(), + }); + None + } + }; + let updateable_client_config = config.as_ref().map(get_updateable_client_config); + + let validator_signer = if let Some(config) = config { + read_validator_key(home_dir, &config).unwrap_or_else(|err| { + errs.push(err); + None + }) + } else { + None + }; + if errs.is_empty() { crate::metrics::CONFIG_CORRECT.set(1); - Ok(UpdateableConfigs { log_config, client_config: updateable_client_config }) + Ok(UpdateableConfigs { + log_config, + client_config: updateable_client_config, + validator_signer, + }) } else { tracing::warn!(target: "neard", "Dynamically updateable configs are not valid. Please fix this ASAP otherwise the node will be unable to restart: {:?}", &errs); crate::metrics::CONFIG_CORRECT.set(0); @@ -42,7 +56,7 @@ pub fn read_updateable_configs( } } -pub fn get_updateable_client_config(config: Config) -> UpdateableClientConfig { +pub fn get_updateable_client_config(config: &Config) -> UpdateableClientConfig { // All fields that can be updated while the node is running should be explicitly set here. // Keep this list in-sync with `core/dyn-configs/README.md`. UpdateableClientConfig { @@ -87,3 +101,23 @@ where }, } } + +fn read_validator_key( + home_dir: &Path, + config: &Config, +) -> Result>, UpdateableConfigLoaderError> { + let validator_file: PathBuf = home_dir.join(&config.validator_key_file); + match crate::config::load_validator_key(&validator_file) { + Ok(Some(validator_signer)) => { + tracing::info!(target: "neard", "Hot loading validator key {}.", validator_file.display()); + Ok(Some(validator_signer)) + } + Ok(None) => { + tracing::info!(target: "neard", "No validator key {}.", validator_file.display()); + Ok(None) + } + Err(err) => { + Err(UpdateableConfigLoaderError::ValidatorKeyFileError { file: validator_file, err }) + } + } +} diff --git a/nearcore/src/lib.rs b/nearcore/src/lib.rs index ada0ac8baaf..00879b25c26 100644 --- a/nearcore/src/lib.rs +++ b/nearcore/src/lib.rs @@ -11,7 +11,7 @@ use anyhow::Context; use cold_storage::ColdStoreLoopHandle; use near_async::actix::AddrWithAutoSpanContextExt; use near_async::actix_wrapper::{spawn_actix_actor, ActixWrapper}; -use near_async::messaging::{noop, IntoMultiSender, IntoSender, LateBoundSender}; +use near_async::messaging::{IntoMultiSender, IntoSender, LateBoundSender}; use near_async::time::{self, Clock}; pub use near_chain::runtime::NightshadeRuntime; use near_chain::state_snapshot_actor::{ @@ -331,7 +331,7 @@ pub fn start_with_config_and_synchronization( let view_client_addr = ViewClientActorInner::spawn_actix_actor( Clock::real(), - config.validator_signer.as_ref().map(|signer| signer.validator_id().clone()), + config.validator_signer.clone(), chain_genesis.clone(), view_epoch_manager.clone(), view_shard_tracker, @@ -360,21 +360,15 @@ pub fn start_with_config_and_synchronization( ); let snapshot_callbacks = SnapshotCallbacks { make_snapshot_callback, delete_snapshot_callback }; - let (partial_witness_actor, partial_witness_arbiter) = if config.validator_signer.is_some() { - let my_signer = config.validator_signer.clone().unwrap(); - let (partial_witness_actor, partial_witness_arbiter) = - spawn_actix_actor(PartialWitnessActor::new( - Clock::real(), - network_adapter.as_multi_sender(), - client_adapter_for_partial_witness_actor.as_multi_sender(), - my_signer, - epoch_manager.clone(), - storage.get_hot_store(), - )); - (Some(partial_witness_actor), Some(partial_witness_arbiter)) - } else { - (None, None) - }; + let (partial_witness_actor, partial_witness_arbiter) = + spawn_actix_actor(PartialWitnessActor::new( + Clock::real(), + network_adapter.as_multi_sender(), + client_adapter_for_partial_witness_actor.as_multi_sender(), + config.validator_signer.clone(), + epoch_manager.clone(), + storage.get_hot_store(), + )); let (_gc_actor, gc_arbiter) = spawn_actix_actor(GCActor::new( runtime.store().clone(), @@ -402,10 +396,7 @@ pub fn start_with_config_and_synchronization( shutdown_signal, adv, config_updater, - partial_witness_actor - .clone() - .map(|actor| actor.with_auto_span_context().into_multi_sender()) - .unwrap_or_else(|| noop().into_multi_sender()), + partial_witness_actor.clone().with_auto_span_context().into_multi_sender(), true, None, ); @@ -419,7 +410,7 @@ pub fn start_with_config_and_synchronization( shard_tracker.clone(), network_adapter.as_sender(), client_adapter_for_shards_manager.as_sender(), - config.validator_signer.as_ref().map(|signer| signer.validator_id().clone()), + config.validator_signer.clone(), split_store.unwrap_or_else(|| storage.get_hot_store()), config.client_config.chunk_request_retry_period, ); @@ -439,7 +430,7 @@ pub fn start_with_config_and_synchronization( epoch_manager, shard_tracker, runtime, - account_id: config.validator_signer.as_ref().map(|signer| signer.validator_id().clone()), + validator: config.validator_signer.clone(), dump_future_runner: StateSyncDumper::arbiter_dump_future_runner(), handle: None, }; @@ -454,9 +445,7 @@ pub fn start_with_config_and_synchronization( config.network_config, client_sender_for_network(client_actor.clone(), view_client_addr.clone()), shards_manager_adapter.as_sender(), - partial_witness_actor - .map(|actor| actor.with_auto_span_context().into_multi_sender()) - .unwrap_or_else(|| noop().into_multi_sender()), + partial_witness_actor.with_auto_span_context().into_multi_sender(), genesis_id, ) .context("PeerManager::spawn()")?; @@ -507,13 +496,11 @@ pub fn start_with_config_and_synchronization( trie_metrics_arbiter, state_snapshot_arbiter, gc_arbiter, + partial_witness_arbiter, ]; if let Some(db_metrics_arbiter) = db_metrics_arbiter { arbiters.push(db_metrics_arbiter); } - if let Some(partial_witness_arbiter) = partial_witness_arbiter { - arbiters.push(partial_witness_arbiter); - } Ok(NearNode { client: client_actor, diff --git a/nearcore/src/metrics.rs b/nearcore/src/metrics.rs index 675141c58d3..dc1b178a592 100644 --- a/nearcore/src/metrics.rs +++ b/nearcore/src/metrics.rs @@ -1,5 +1,4 @@ -use std::rc::Rc; - +use crate::NearConfig; use actix_rt::ArbiterHandle; use near_async::time::Duration; use near_chain::{Block, ChainStore, ChainStoreAccess}; @@ -9,12 +8,10 @@ use near_o11y::metrics::{ try_create_int_gauge, try_create_int_gauge_vec, HistogramVec, IntCounterVec, IntGauge, IntGaugeVec, }; - use near_primitives::{shard_layout::ShardLayout, state_record::StateRecord, trie_key}; use near_store::{ShardUId, Store, Trie, TrieDBStorage}; use once_cell::sync::Lazy; - -use crate::NearConfig; +use std::sync::Arc; pub(crate) static POSTPONED_RECEIPTS_COUNT: Lazy = Lazy::new(|| { try_create_int_gauge_vec( @@ -160,7 +157,7 @@ fn get_postponed_receipt_count_for_shard( let chunk_extra = chain_store.get_chunk_extra(block.hash(), &shard_uid)?; let state_root = chunk_extra.state_root(); let storage = TrieDBStorage::new(store.clone(), shard_uid); - let storage = Rc::new(storage); + let storage = Arc::new(storage); let flat_storage_chunk_view = None; let trie = Trie::new(storage, *state_root, flat_storage_chunk_view); get_postponed_receipt_count_for_trie(trie) diff --git a/nearcore/src/migrations.rs b/nearcore/src/migrations.rs index bfccff49025..d57aa0f3dff 100644 --- a/nearcore/src/migrations.rs +++ b/nearcore/src/migrations.rs @@ -88,6 +88,7 @@ impl<'a> near_store::StoreMigrator for Migrator<'a> { 36 => near_store::migrations::migrate_36_to_37(store), 37 => near_store::migrations::migrate_37_to_38(store), 38 => near_store::migrations::migrate_38_to_39(store), + 39 => near_store::migrations::migrate_39_to_40(store), DB_VERSION.. => unreachable!(), } } diff --git a/nearcore/src/state_sync.rs b/nearcore/src/state_sync.rs index df96c111699..da7f72722b2 100644 --- a/nearcore/src/state_sync.rs +++ b/nearcore/src/state_sync.rs @@ -7,7 +7,7 @@ use futures::FutureExt; use near_async::time::{Clock, Duration, Instant}; use near_chain::types::RuntimeAdapter; use near_chain::{Chain, ChainGenesis, ChainStoreAccess, DoomslugThresholdMode, Error}; -use near_chain_configs::{ClientConfig, ExternalStorageLocation}; +use near_chain_configs::{ClientConfig, ExternalStorageLocation, MutableValidatorSigner}; use near_client::sync::external::{ create_bucket_readwrite, external_storage_location, StateFileType, }; @@ -35,7 +35,10 @@ pub struct StateSyncDumper { pub epoch_manager: Arc, pub shard_tracker: ShardTracker, pub runtime: Arc, - pub account_id: Option, + /// Contains validator key for this node. This field is mutable and optional. Use with caution! + /// Lock the value of mutable validator signer for the duration of a request to ensure consistency. + /// Please note that the locked value should not be stored anywhere or passed through the thread boundary. + pub validator: MutableValidatorSigner, pub dump_future_runner: Box) -> Box>, pub handle: Option, } @@ -79,7 +82,7 @@ impl StateSyncDumper { }; // Determine how many threads to start. - // TODO: Handle the case of changing the shard layout. + // TODO(resharding): Handle the case of changing the shard layout. let shard_ids = { // Sadly, `Chain` is not `Send` and each thread needs to create its own `Chain` instance. let chain = Chain::new_for_view_client( @@ -125,7 +128,7 @@ impl StateSyncDumper { dump_config.restart_dump_for_shards.clone().unwrap_or_default(), external.clone(), dump_config.iteration_delay.unwrap_or(Duration::seconds(10)), - self.account_id.clone(), + self.validator.clone(), keep_running.clone(), ) .boxed(), @@ -334,7 +337,7 @@ async fn state_sync_dump( restart_dump_for_shards: Vec, external: ExternalConnection, iteration_delay: Duration, - account_id: Option, + validator: MutableValidatorSigner, keep_running: Arc, ) { tracing::info!(target: "state_sync_dump", shard_id, "Running StateSyncDump loop"); @@ -348,6 +351,7 @@ async fn state_sync_dump( // Note that without this check the state dumping thread is unstoppable, i.e. non-interruptable. while keep_running.load(std::sync::atomic::Ordering::Relaxed) { tracing::debug!(target: "state_sync_dump", shard_id, "Running StateSyncDump loop iteration"); + let account_id = validator.get().map(|v| v.validator_id().clone()); let current_state = get_current_state( &chain, &shard_id, @@ -403,7 +407,7 @@ async fn state_sync_dump( None } else { Some(StateSyncDumpProgress::InProgress { - epoch_id: epoch_id.clone(), + epoch_id: epoch_id, epoch_height, sync_hash, }) @@ -659,7 +663,7 @@ fn get_latest_epoch( let final_hash = header.last_final_block(); let sync_hash = StateSync::get_epoch_start_sync_hash(chain, final_hash)?; let final_block_header = chain.get_block_header(&final_hash)?; - let epoch_id = final_block_header.epoch_id().clone(); + let epoch_id = *final_block_header.epoch_id(); let epoch_info = epoch_manager.get_epoch_info(&epoch_id)?; let prev_epoch_id = epoch_manager.get_prev_epoch_id_from_prev_block(&head.prev_block_hash)?; let epoch_height = epoch_info.epoch_height(); diff --git a/nearcore/src/test_utils.rs b/nearcore/src/test_utils.rs index d572eec5131..6586172dfb6 100644 --- a/nearcore/src/test_utils.rs +++ b/nearcore/src/test_utils.rs @@ -84,7 +84,7 @@ impl TestEnvNightshadeSetupExt for TestEnvBuilder { store: Store, contract_cache: Box, epoch_manager: Arc, - _, + runtime_config_store: RuntimeConfigStore, trie_config: TrieConfig| -> Arc { // TODO: It's not ideal to initialize genesis state with the nightshade runtime here for tests @@ -98,11 +98,13 @@ impl TestEnvNightshadeSetupExt for TestEnvBuilder { contract_cache, &genesis.config, epoch_manager, + Some(runtime_config_store), trie_config, state_snapshot_type.clone(), ) }; - let dummy_runtime_configs = vec![RuntimeConfigStore::test(); self.num_clients()]; + let dummy_runtime_configs = + vec![RuntimeConfigStore::test_congestion_control_disabled(); self.num_clients()]; self.internal_initialize_nightshade_runtimes( dummy_runtime_configs, trie_configs, diff --git a/nearcore/tests/economics.rs b/nearcore/tests/economics.rs index 8d0f5fb5e8c..ee529a12a61 100644 --- a/nearcore/tests/economics.rs +++ b/nearcore/tests/economics.rs @@ -10,6 +10,8 @@ use near_client::test_utils::TestEnv; use near_crypto::{InMemorySigner, KeyType}; use near_o11y::testonly::init_integration_logger; use near_primitives::transaction::SignedTransaction; +use near_primitives::version::ProtocolFeature::StatelessValidationV0; +use near_primitives::version::PROTOCOL_VERSION; use near_store::{genesis::initialize_genesis_state, test_utils::create_test_store}; use nearcore::NightshadeRuntime; use testlib::fees_utils::FeeHelper; @@ -78,7 +80,7 @@ fn test_burn_mint() { 1, "test0".parse().unwrap(), "test1".parse().unwrap(), - &signer, + &signer.into(), 1000, genesis_hash, ), @@ -111,11 +113,15 @@ fn test_burn_mint() { / U256::from(10u128.pow(9) * 24 * 60 * 60 * 365 * 10)) .as_u128() }; - assert_eq!( - block3.header().total_supply(), + // In stateless validation, chunk endorsements are also included in the reward calculation. + let expected_total_supply = if StatelessValidationV0.enabled(PROTOCOL_VERSION) { + // supply + 1% of protocol rewards + 2/3 * 9% of validator rewards. + initial_total_supply + epoch_total_reward * 700 / 1000 - half_transfer_cost + } else { // supply + 1% of protocol rewards + 3/4 * 9% of validator rewards. initial_total_supply + epoch_total_reward * 775 / 1000 - half_transfer_cost - ); + }; + assert_eq!(block3.header().total_supply(), expected_total_supply); assert_eq!(block3.chunks()[0].prev_balance_burnt(), half_transfer_cost); // Block 4: subtract 2nd part of transfer. let block4 = env.clients[0].chain.get_block_by_height(4).unwrap(); diff --git a/neard/Cargo.toml b/neard/Cargo.toml index 6b62110709c..7e327404ff6 100644 --- a/neard/Cargo.toml +++ b/neard/Cargo.toml @@ -132,7 +132,7 @@ calimero_zero_storage = [ # with this flag and then enable it at runtime with `--record-io-trace=path` option. io_trace = ["near-store/io_trace", "near-o11y/io_trace", "nearcore/io_trace"] -sandbox = ["nearcore/sandbox"] +sandbox = ["near-o11y/sandbox", "nearcore/sandbox"] [package.metadata.workspaces] independent = true diff --git a/neard/src/cli.rs b/neard/src/cli.rs index cba5f7f6a56..7aef39f7260 100644 --- a/neard/src/cli.rs +++ b/neard/src/cli.rs @@ -313,7 +313,7 @@ pub(super) struct InitCmd { /// Genesis file to use when initializing testnet (including downloading). #[clap(long)] genesis: Option, - /// Initialize boots nodes in @ format seperated by commas + /// Initialize boots nodes in @ format separated by commas /// to bootstrap the network and store them in config.json #[clap(long)] boot_nodes: Option, @@ -537,11 +537,7 @@ impl RunCmd { o11y_opts, near_config.client_config.chain_id.clone(), near_config.network_config.node_key.public_key().clone(), - near_config - .network_config - .validator - .as_ref() - .map(|validator| validator.account_id()), + near_config.network_config.validator.account_id(), ) .await .global(); diff --git a/nightly/expensive.txt b/nightly/expensive.txt index 8c81d01efab..6a6d04e92c7 100644 --- a/nightly/expensive.txt +++ b/nightly/expensive.txt @@ -140,6 +140,22 @@ expensive integration-tests integration_tests tests::client::chunks_management:: expensive integration-tests integration_tests tests::client::chunks_management::chunks_recovered_from_others --features nightly expensive integration-tests integration_tests tests::client::chunks_management::chunks_recovered_from_others_cop expensive integration-tests integration_tests tests::client::chunks_management::chunks_recovered_from_others_cop --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_2_vals_per_shard +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_2_vals_per_shard --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_one_val_per_shard +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_one_val_per_shard --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_chunk_distribution_network_disabled +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_chunk_distribution_network_disabled --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_chunk_distribution_network_wrong_urls +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_chunk_distribution_network_wrong_urls --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_chunk_distribution_network_incorrect_get_return +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_chunk_distribution_network_incorrect_get_return --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_2_vals_per_shard_should_succeed_even_without_forwarding +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_2_vals_per_shard_should_succeed_even_without_forwarding --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_one_val_per_shard_should_succeed_even_without_forwarding +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_one_val_per_shard_should_succeed_even_without_forwarding --features nightly +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_one_val_shard_cop +expensive integration-tests integration_tests tests::client::chunks_management::chunks_produced_and_distributed_one_val_shard_cop --features nightly expensive integration-tests integration_tests tests::client::process_blocks::test_gc_after_state_sync expensive integration-tests integration_tests tests::client::process_blocks::test_gc_after_state_sync --features nightly expensive integration-tests integration_tests tests::client::process_blocks::test_process_block_after_state_sync diff --git a/nightly/pytest-sanity.txt b/nightly/pytest-sanity.txt index af343e4bc7c..d518e641330 100644 --- a/nightly/pytest-sanity.txt +++ b/nightly/pytest-sanity.txt @@ -112,6 +112,10 @@ pytest --timeout=240 sanity/switch_node_key.py pytest --timeout=240 sanity/switch_node_key.py --features nightly pytest --timeout=240 sanity/validator_switch_key.py pytest --timeout=240 sanity/validator_switch_key.py --features nightly +pytest --timeout=120 sanity/validator_switch_key_quick.py +pytest --timeout=120 sanity/validator_switch_key_quick.py --features nightly +pytest --timeout=60 sanity/shadow_tracking.py +pytest --timeout=60 sanity/shadow_tracking.py --features nightly pytest sanity/proxy_simple.py pytest sanity/proxy_simple.py --features nightly pytest sanity/proxy_restart.py @@ -186,3 +190,13 @@ pytest sanity/slow_chunk.py --features nightly # TODO(congestion_control) - enable pytest on stabilization # pytest sanity/congestion_control.py pytest sanity/congestion_control.py --features nightly +pytest sanity/congestion_control_genesis_bootstrap.py +pytest sanity/congestion_control_genesis_bootstrap.py --features nightly + +# Tests the correct operation of the view client without using memtries (#11312). +pytest sanity/rpc_view_history.py +pytest sanity/rpc_view_history.py --features nightly + +# Tests switching between memtries and disktries. +pytest sanity/memtrie_disktrie_switch.py +pytest sanity/memtrie_disktrie_switch.py --features nightly \ No newline at end of file diff --git a/pytest/genesis.json b/pytest/genesis.json new file mode 100644 index 00000000000..3be1cc0d80c --- /dev/null +++ b/pytest/genesis.json @@ -0,0 +1,801 @@ +{ + "protocol_version": 29, + "genesis_time": "2020-07-21T16:55:51.591948Z", + "chain_id": "mainnet", + "genesis_height": 9820210, + "num_block_producer_seats": 100, + "num_block_producer_seats_per_shard": [ + 100 + ], + "avg_hidden_validator_seats_per_shard": [ + 0 + ], + "dynamic_resharding": false, + "protocol_upgrade_stake_threshold": [ + 4, + 5 + ], + "epoch_length": 43200, + "gas_limit": 1000000000000000, + "min_gas_price": "1000000000", + "max_gas_price": "10000000000000000000000", + "block_producer_kickout_threshold": 90, + "chunk_producer_kickout_threshold": 90, + "online_min_threshold": [ + 90, + 100 + ], + "online_max_threshold": [ + 99, + 100 + ], + "gas_price_adjustment_rate": [ + 1, + 100 + ], + "validators": [ + { + "account_id": "nfvalidator1.near", + "public_key": "ed25519:14pWWRutZtGFKX4B8q89KVFaUWY1Cqu1JcqYXhCDeFh1", + "amount": "50000000000000000000000000000" + }, + { + "account_id": "nfvalidator2.near", + "public_key": "ed25519:BwZk4bkYJxo79P2vSRw2uk1nfiqEfVkHvr5p8eVsqASC", + "amount": "50000000000000000000000000000" + }, + { + "account_id": "nfvalidator3.near", + "public_key": "ed25519:DMz11tmPvhdqpi7CzP2JULeeSE8SxYRD8pys5nKke4FS", + "amount": "50000000000000000000000000000" + }, + { + "account_id": "nfvalidator4.near", + "public_key": "ed25519:Fi3CQDHJoviKazVR27YmfFzWcFnvmoPBKEDd9ouq5Tjx", + "amount": "50000000000000000000000000000" + } + ], + "transaction_validity_period": 86400, + "protocol_reward_rate": [ + 0, + 1 + ], + "max_inflation_rate": [ + 0, + 1 + ], + "total_supply": "999999999792372916156395166000000", + "num_blocks_per_year": 31536000, + "protocol_treasury_account": "treasury.near", + "fishermen_threshold": "340282366920938463463374607431768211455", + "minimum_stake_divisor": 10, + "shard_layout": { + "V0": { + "num_shards": 1, + "version": 0 + } + }, + "num_chunk_only_producer_seats": 300, + "minimum_validators_per_shard": 1, + "max_kickout_stake_perc": 100, + "minimum_stake_ratio": [ + 1, + 6250 + ], + "use_production_config": false, + "records": [ + { + "Account": { + "account_id": "01.near", + "account": { + "amount": "49999999958035075000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 264, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "alex.near", + "account": { + "amount": "9999000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "bo.near", + "account": { + "amount": "50000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "bot.pulse.near", + "account": { + "amount": "791373397694044304600000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "bowen.near", + "account": { + "amount": "49999999506363398300200000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "contributors.near", + "account": { + "amount": "418000000000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "erik.near", + "account": { + "amount": "10000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "foundation.near", + "account": { + "amount": "581779979999999955363487500000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "illia.near", + "account": { + "amount": "9909124991408763970627200000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 321, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "kendall.near", + "account": { + "amount": "49998999710140992484400000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 462, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "ledger.vlad.near", + "account": { + "amount": "999999957937258742200000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 327, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "mike.near", + "account": { + "amount": "30999999915088987500000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "mikemikemikemikemikemikemikemike", + "account": { + "amount": "19000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "near", + "account": { + "amount": "8700003991476791004803600000", + "locked": "0", + "code_hash": "23tqXYRdbJVuvpLB14Pe9Su9bQBwfn3njKN6EBbKTQwh", + "storage_usage": 197868, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "nfvalidator1.near", + "account": { + "amount": "0", + "locked": "50000000000000000000000000000", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "nfvalidator2.near", + "account": { + "amount": "0", + "locked": "50000000000000000000000000000", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "nfvalidator3.near", + "account": { + "amount": "0", + "locked": "50000000000000000000000000000", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "nfvalidator4.near", + "account": { + "amount": "0", + "locked": "50000000000000000000000000000", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "patrick.near", + "account": { + "amount": "9998999875468925000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 263, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "peter.near", + "account": { + "amount": "1000874999955363487500000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "pulse.near", + "account": { + "amount": "48001118054588063403800000", + "locked": "0", + "code_hash": "2pMwiHggCBQAv3eFEPtJozDpbHpD8KkL3o3qRv6qs6DT", + "storage_usage": 26061, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "registrar", + "account": { + "amount": "10000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "treasury.near", + "account": { + "amount": "10000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "vlad.near", + "account": { + "amount": "8998999831159137500000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 346, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "wallet.pulse.near", + "account": { + "amount": "999899913398562500000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 264, + "version": "V1" + } + } + }, + { + "Account": { + "account_id": "yifang.near", + "account": { + "amount": "50000000000000000000000000", + "locked": "0", + "code_hash": "11111111111111111111111111111111", + "storage_usage": 182, + "version": "V1" + } + } + }, + { + "Contract": { + "account_id": "near", + "code": "AGFzbQEAAAAB8gNBYAJ/fwF/YAF/AGAAAX9gBX9+fn5+AGACf34AYAR/fn5+AX5gCH9+fn5+fn5+AGADf35+AGAKf35+fn5+fn5+fgBgBn9+fn5+fgF+YAF/AX5gBH9+fn4AYAN/fn4BfmACf34BfmACf38AYAN/f38Bf2ACfn4AYAF+AX5gAX4AYAABfmADfn5+AGAAAGAIfn5+fn5+fn4BfmAJfn5+fn5+fn5+AX5gAn5+AX5gA35+fgF+YAd+fn5+fn5+AGAEfn5+fgBgCX5+fn5+fn5+fgBgBX5+fn5+AX5gA39/fwBgAX8Bf2AEf39/fgBgBX9/f35/AGAFf39/f38AYAR/f39/AGAFf39/f38Bf2AEf39/fwF/YAV/fn5+fwBgBn9/f39/fwBgAn9/AX5gBX9/fn9/AGADf39+AGAJf35+fn5+fn5+AX5gCn9+fn5+fn5+fn4BfmAEf39+fgF/YAN/fn4Bf2AHf39/f39/fwBgA39/fwF+YAJ+fwF+YAN+f38AYAh+f39/f35+fgBgBH5+fn8AYAN+f34AYAh+f35+fn9/fwBgAn5/AGAHf39/f35+fgBgBH9/fn4AYAd/f39+fn9/AGAEf3x/fwF/YAN+f38Bf2AGf39/f39/AX9gBH5+f38Bf2AEf35+fwBgBn9+fn5+fwAC5QguA2Vudg1yZWFkX3JlZ2lzdGVyABADZW52DHJlZ2lzdGVyX2xlbgARA2VudhJjdXJyZW50X2FjY291bnRfaWQAEgNlbnYRc2lnbmVyX2FjY291bnRfaWQAEgNlbnYRc2lnbmVyX2FjY291bnRfcGsAEgNlbnYWcHJlZGVjZXNzb3JfYWNjb3VudF9pZAASA2VudgVpbnB1dAASA2VudgtibG9ja19pbmRleAATA2Vudg9ibG9ja190aW1lc3RhbXAAEwNlbnYMZXBvY2hfaGVpZ2h0ABMDZW52DXN0b3JhZ2VfdXNhZ2UAEwNlbnYPYWNjb3VudF9iYWxhbmNlABIDZW52FmFjY291bnRfbG9ja2VkX2JhbGFuY2UAEgNlbnYQYXR0YWNoZWRfZGVwb3NpdAASA2VudgtwcmVwYWlkX2dhcwATA2Vudgh1c2VkX2dhcwATA2VudgtyYW5kb21fc2VlZAASA2VudgZzaGEyNTYAFANlbnYJa2VjY2FrMjU2ABQDZW52CWtlY2NhazUxMgAUA2Vudgx2YWx1ZV9yZXR1cm4AEANlbnYFcGFuaWMAFQNlbnYKcGFuaWNfdXRmOAAQA2Vudghsb2dfdXRmOAAQA2Vudglsb2dfdXRmMTYAEANlbnYOcHJvbWlzZV9jcmVhdGUAFgNlbnYMcHJvbWlzZV90aGVuABcDZW52C3Byb21pc2VfYW5kABgDZW52FHByb21pc2VfYmF0Y2hfY3JlYXRlABgDZW52EnByb21pc2VfYmF0Y2hfdGhlbgAZA2VudiNwcm9taXNlX2JhdGNoX2FjdGlvbl9jcmVhdGVfYWNjb3VudAASA2VudiRwcm9taXNlX2JhdGNoX2FjdGlvbl9kZXBsb3lfY29udHJhY3QAFANlbnYicHJvbWlzZV9iYXRjaF9hY3Rpb25fZnVuY3Rpb25fY2FsbAAaA2Vudh1wcm9taXNlX2JhdGNoX2FjdGlvbl90cmFuc2ZlcgAQA2Vudhpwcm9taXNlX2JhdGNoX2FjdGlvbl9zdGFrZQAbA2Vudi1wcm9taXNlX2JhdGNoX2FjdGlvbl9hZGRfa2V5X3dpdGhfZnVsbF9hY2Nlc3MAGwNlbnYvcHJvbWlzZV9iYXRjaF9hY3Rpb25fYWRkX2tleV93aXRoX2Z1bmN0aW9uX2NhbGwAHANlbnYfcHJvbWlzZV9iYXRjaF9hY3Rpb25fZGVsZXRlX2tleQAUA2VudiNwcm9taXNlX2JhdGNoX2FjdGlvbl9kZWxldGVfYWNjb3VudAAUA2VudhVwcm9taXNlX3Jlc3VsdHNfY291bnQAEwNlbnYOcHJvbWlzZV9yZXN1bHQAGANlbnYOcHJvbWlzZV9yZXR1cm4AEgNlbnYNc3RvcmFnZV93cml0ZQAdA2VudgxzdG9yYWdlX3JlYWQAGQNlbnYOc3RvcmFnZV9yZW1vdmUAGQNlbnYPc3RvcmFnZV9oYXNfa2V5ABgDzgbMBh4OHg4eDh4OHg4eDg4fAQEBDg4BDg4OAA8OAQAAHwEOHx4BDiAOIQEhAQEhHiEfHw4hDx8fHyIBIgEBDg4ADh8OIw4OAQAAAAAAAAAAAAAAAAAAAAAADgEOAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ8kJSMiIw4BDiImIg4eDg4ODh4eDh4OHg4OHg8ADiUPDyUAHh4fAA4OHg4eDh4OHg4eDh4ODg4ODh8fDg4OAAAOJyMKHh4oKSMqKiojDg4eHiMOIx4qKioODh8fAQEOHh4eDg4OJycnJycnHgIAHiUABw0EBAQEBAoKCgoEBAQKCgQLCwsHAQcHByssDAwFBAsGBwMDCAsLCgwECQUFDAAAHh4jIy0uFRUVFRUVAB4lACMiHg4OIx4jIwEiHgAODgofAAAAAAAlHh4AAQEBAQEBAQEBAQEBAR4lAQ4BDi8eIyUeDg4BHh8AHg4ADx8eAAEODh4OHh4VJx4OAQEODw8ODg8OAB8fHh4eIwAeHx8ADg4eDh4eHwAeAB8fAR8fAQEfIwAfJA8jDgEOAA4ODgofDgABGAEfDgAoHg4AHg8eDw4ADgABHzAjCg4OACgeHg8oHgEfAR8wIx4PDgAOACgeDgAwIx4PHg8oHg4AAQ4BAQEAAAAOAR4ODgEjAA4BDg4BAQEBAQEBCgEBAQEBAQEBAQEBASUfDh8OHiIiIh4eHw4ODh4eDg4BJyMBDgEODhUEBAEBAQEBKAoxEjIzFDQ1Njc3EwQSDiUeAAEADh4OODkeOh4eAQ4ODh4BAgcHAA4eAAABASMiDicAAB4OIwAAHh4eFR4fHh4eJx8eDhUeCh4BAQ4OHgAAFR8OAAAADx4OIwAAAAEBASUlHgAeAA8OFR4eDgEOHiMeHh4VJwEOAQoKCgAAAAAAHgEBAQAfAAAPABUAAA4ACh8ODgAAHgAAIxUODg4BIw4ODg4OHwAAAAEOAQ4OFRUeDgABHh4ADg4PIwAOAA8jIyIjIgAjAA8AAAoAAB4fJB4AIgAfHwAAAB8PIx8PHx4PHzsAOwAADwAAADwAPSUADwAfHyMjDgAPAQ8AACIAHh4AAAAjHgAAAAAAAAAAPgAAAAAAAAAfHw4ADw8PPz8DA0AEBwFwAcsBywEFAwEAEQYZA38BQYCAwAALfwBBsPrCAAt/AEGw+sIACwecAQkGbWVtb3J5AgAEc2VuZADHAgVjbGFpbQDIAhhjcmVhdGVfYWNjb3VudF9hbmRfY2xhaW0AyQIOY3JlYXRlX2FjY291bnQAygISb25fYWNjb3VudF9jcmVhdGVkAMsCHm9uX2FjY291bnRfY3JlYXRlZF9hbmRfY2xhaW1lZADMAgpfX2RhdGFfZW5kAwELX19oZWFwX2Jhc2UDAgmTAwEAQQELygGQBYMBggGcBuQCgQGoBcQGswWNAe8FiwHoBp8BpAZYpAORAd4GjgF+mQF2mwF0ngF6iQF4jAF/nAF8mgGRApICkwKUApUClgKXApgCmQKaApsCnAKdAp4CnwKgAqECogKjAqQCpQKmAqcCqAKpAqoCqwKsAq0CrgKvArACsQKyArMCtAK1ArYCtwK4ArkCugK7ArwCvQK+AvsB+gGPAa4FtwOtBeICjwPlAvYC3wLgAuEC6wXeAuwF7QXuAowDiAOLA+wC9QLxBrwDwgPDA8QDlAT7BKIEzgPPA9ADzAPNA4oFiwWtBKcEowayBOUFswSxBIoG/ASsBJ0EnwSgBNMGhAWCBYUFgwWPBaMFpQWkBaIFoAXPBtoG5QbfBtQGpwW0BbcFvwW8Bb4FuAXkBuQF1wXzBdoF4QXfBeAF0gXcBeoF6AXpBdYF2AXiBdsF+AX5BdMF+gX7BdEF1QXUBYEG/gWABv8FqQagBpMGwgarBq8GsAaLBqIGsga9Br4G7Aa/BsAGwQbpBuoG6wYK9/QIzAY2AQF/I4CAgIAAQRBrIgMkgICAgAAgAyABIAIQp4OAgAAgACADEK+AgIAAIANBEGokgICAgAAL8wECAX8CfiOAgICAAEHAAGsiAiSAgICAACACQShqQQhqIAFBCGooAgA2AgAgAiABKQIANwMoIAJBCGogAkEoahCdg4CAACACQShqIAJBCGoQxICAgAACQAJAIAIoAihBAUcNACAAIAIoAiw2AgQgAEEBNgIADAELIAJBKGpBEGopAwAhAyACKQMwIQQgAiACQQhqELuAgIAAIgE2AigCQCABRQ0AIABBATYCACAAIAE2AgQMAQsgAkEoahC8gICAACAAQRBqIAM3AwAgAEEIaiAENwMAIABBADYCAAsgAkEUahC+gICAACACQcAAaiSAgICAAAs2AQF/I4CAgIAAQRBrIgMkgICAgAAgAyABIAIQp4OAgAAgACADELGAgIAAIANBEGokgICAgAALhAIBAX8jgICAgABBwABrIgIkgICAgAAgAkEwakEIaiABQQhqKAIANgIAIAIgASkCADcDMCACIAJBMGoQnYOAgAAgAkEwaiACEL+AgIAAAkACQAJAIAIoAjBBAUYNACACQSBqQQhqIAJBMGpBDGooAgA2AgAgAiACKQI0NwMgIAIgAhC7gICAACIBNgIwIAENASACQTBqELyAgIAAIAAgAikDIDcCBCAAQQA2AgAgAEEMaiACQShqKAIANgIADAILIAAgAigCNDYCBCAAQQE2AgAMAQsgAEEBNgIAIAAgATYCBCACQSBqEL6AgIAACyACQQxqEL6AgIAAIAJBwABqJICAgIAACzYBAX8jgICAgABBEGsiAySAgICAACADIAEgAhCng4CAACAAIAMQs4CAgAAgA0EQaiSAgICAAAuEAgEBfyOAgICAAEHAAGsiAiSAgICAACACQTBqQQhqIAFBCGooAgA2AgAgAiABKQIANwMwIAIgAkEwahCdg4CAACACQTBqIAIQwoCAgAACQAJAAkAgAigCMEEBRg0AIAJBIGpBCGogAkEwakEMaigCADYCACACIAIpAjQ3AyAgAiACELuAgIAAIgE2AjAgAQ0BIAJBMGoQvICAgAAgACACKQMgNwIEIABBADYCACAAQQxqIAJBKGooAgA2AgAMAgsgACACKAI0NgIEIABBATYCAAwBCyAAQQE2AgAgACABNgIEIAJBIGoQvoCAgAALIAJBDGoQvoCAgAAgAkHAAGokgICAgAALNgEBfyOAgICAAEEQayIDJICAgIAAIAMgASACEKeDgIAAIAAgAxC1gICAACADQRBqJICAgIAAC7UCAQJ/I4CAgIAAQeAAayICJICAgIAAIAJBwABqQQhqIAFBCGooAgA2AgAgAiABKQIANwNAIAJBCGogAkHAAGoQnYOAgAAgAkHAAGogAkEIahC6gICAAAJAAkACQCACKAJAQQFGDQAgAkE4aiIDIAJBwABqQRRqKQIANwMAIAJBKGpBCGogAkHAAGpBDGopAgA3AwAgAiACKQJENwMoIAIgAkEIahC7gICAACIBNgJAIAENASACQcAAahC8gICAACAAIAIpAyg3AgQgAEEANgIAIABBFGogAykDADcCACAAQQxqIAJBMGopAwA3AgAMAgsgACACKAJENgIEIABBATYCAAwBCyAAQQE2AgAgACABNgIEIAJBKGoQvYCAgAALIAJBFGoQvoCAgAAgAkHgAGokgICAgAALNgEBfyOAgICAAEEQayIDJICAgIAAIAMgASACEKeDgIAAIAAgAxC3gICAACADQRBqJICAgIAAC+ECAQN/I4CAgIAAQfAAayICJICAgIAAIAJByABqQQhqIAFBCGooAgA2AgAgAiABKQIANwNIIAJBCGogAkHIAGoQnYOAgAAgAkHIAGogAkEIahDDgICAAAJAAkACQCACKAJIQQFGDQAgAkEoakEYaiIDIAJByABqQSBqKQMANwMAIAJBKGpBEGoiBCACQcgAakEYaikDADcDACACQShqQQhqIAJByABqQRBqKQMANwMAIAIgAikDUDcDKCACIAJBCGoQu4CAgAAiATYCSCABDQEgAkHIAGoQvICAgAAgAEEIaiACKQMoNwMAIABBADYCACAAQSBqIAMpAwA3AwAgAEEYaiAEKQMANwMAIABBEGogAkEoakEIaikDADcDAAwCCyAAIAIoAkw2AgQgAEEBNgIADAELIABBATYCACAAIAE2AgQgBBC+gICAAAsgAkEUahC+gICAACACQfAAaiSAgICAAAs2AQF/I4CAgIAAQRBrIgMkgICAgAAgAyABIAIQp4OAgAAgACADELmAgIAAIANBEGokgICAgAALtQIBAn8jgICAgABB4ABrIgIkgICAgAAgAkHAAGpBCGogAUEIaigCADYCACACIAEpAgA3A0AgAkEIaiACQcAAahCdg4CAACACQcAAaiACQQhqEMCAgIAAAkACQAJAIAIoAkBBAUYNACACQThqIgMgAkHAAGpBFGopAgA3AwAgAkEoakEIaiACQcAAakEMaikCADcDACACIAIpAkQ3AyggAiACQQhqELuAgIAAIgE2AkAgAQ0BIAJBwABqELyAgIAAIAAgAikDKDcCBCAAQQA2AgAgAEEUaiADKQMANwIAIABBDGogAkEwaikDADcCAAwCCyAAIAIoAkQ2AgQgAEEBNgIADAELIABBATYCACAAIAE2AgQgAkEoahDBgICAAAsgAkEUahC+gICAACACQeAAaiSAgICAAAsUACAAIAEgACAAIAAgABCKgoCAAAt6AQJ/I4CAgIAAQSBrIgEkgICAgAAgAUEIaiAAEM2AgIAAAkACQCABLQAIQQFGDQBBACECAkAgAS0ACUEBRw0AIAFBEzYCECAAIAFBEGoQxYCAgAAhAgsgAUEIahDIgICAAAwBCyABKAIMIQILIAFBIGokgICAgAAgAgsVAAJAIAAoAgBFDQAgABDYgICAAAsLFQAgABC+gICAACAAQQxqEL6AgIAACxIAIAAQm4OAgAAgABCcg4CAAAsUACAAIAEgACAAIAAgABCIgoCAAAsUACAAIAEgACAAIAAgABCJgoCAAAsVACAAEL6AgIAAIABBDGoQvoCAgAALFAAgACABIAAgACAAIAAQhoKAgAALFAAgACABIAAgACAAIAAQhYKAgAALFAAgACABIAAgACAAIAAQh4KAgAALawECfyOAgICAAEEgayICJICAgIAAIAJBCGogABCjg4CAACACKAIMIQAgAigCCCEDIAJBEGpBCGogAUEIaigCADYCACACIAEpAgA3AxAgAkEQaiADIAAQooOAgAAhASACQSBqJICAgIAAIAEL4QEBAX8jgICAgABBMGsiAySAgICAACADIAEgAhCCg4CAACADKAIEIQIgAygCACEBAkACQANAAkAgAiABRw0AQQAhAQwDCyADQQhqIAAQx4CAgAACQCADLQAIQQFGDQACQCADLQAJQQFGDQBBBSECIANBEGohAQwDCwJAIAMtAAogAS0AAEYNAEEJIQIgA0EgaiEBDAMLIAFBAWohASADQQhqEMiAgIAADAELCyADKAIMIQEMAQsgASACNgIAIAAgARDJgICAACEBIANBCGoQyICAgAALIANBMGokgICAgAAgAQtNAQN/QQAhAgJAIAEoAggiAyABKAIETw0AIAEoAgAgA2otAAAhBEEBIQIgASADQQFqNgIICyAAIAI6AAEgAEEAOgAAIABBAmogBDoAAAsYAAJAIAAtAABFDQAgAEEEahDYgICAAAsLawECfyOAgICAAEEgayICJICAgIAAIAJBCGogABChg4CAACACKAIMIQAgAigCCCEDIAJBEGpBCGogAUEIaigCADYCACACIAEpAgA3AxAgAkEQaiADIAAQooOAgAAhASACQSBqJICAgIAAIAELZAEBfyOAgICAAEEQayICJICAgIAAAkACQCAAKAIMRQ0AIAAhAQwBCyACQQhqIABBCGooAgA2AgAgAiAAKQIANwMAIAEgAhDJgICAACEBIAAQ8oCAgAALIAJBEGokgICAgAAgAQulCgEGfyOAgICAAEHAAGsiASSAgICAACAAQQxqIgIQzICAgABBACEDAkACQAJAAkADQCABQSBqIAAQzYCAgAACQAJAIAEtACBBAUYNAAJAAkACQAJAAkACQAJAAkAgAS0AIUEBRw0AIAEtACIhBCABQSBqEMiAgIAAIARBIkYNBCAEQS1GDQMCQAJAIARB2wBGDQAgBEHmAEYNBCAEQe4ARg0BIARB9ABGDQMgBEH7AEYNACAEQVBqQf8BcUEKTw0HIAEgABDOgICAACIENgIwIAFBMGohBSAERQ0IDBALQQAhBSACIANB//8DcUEARyAGEM+AgIAAIAAQ0ICAgAAgBCEGDAgLIAAQ0ICAgAAgASAAQYeAwIAAQQMQxoCAgAAiBDYCMCABQTBqIQUgBA0ODAYLIAFBBTYCMCAAIAFBMGoQxYCAgAAhBCABQSBqEMiAgIAADA0LIAAQ0ICAgAAgASAAQYSAwIAAQQMQxoCAgAAiBDYCMCABQTBqIQUgBEUNBAwMCyAAENCAgIAAIAEgAEGAgMCAAEEEEMaAgIAAIgQ2AjAgAUEwaiEFIARFDQMMCwsgABDQgICAACABIAAQzoCAgAAiBDYCMCABQTBqIQUgBEUNAgwKCyAAENCAgIAAIAEgABCtg4CAACIENgIwIAFBMGohBSAERQ0BDAkLIAFBCjYCMCAAIAFBMGoQxYCAgAAhBAwICyAFELyAgIAAQQEhBSADQf//A3ENACABQRBqIAIQ0YCAgAAgAS0AEEEBcUUNBiABLQARIQYLAkADQCABQSBqIAAQzYCAgAAgAS0AIEEBRg0CAkACQAJAAkACQAJAAkACQAJAAkACQCABLQAhQQFGDQAgBkH/AXEiBkHbAEcNAUECIQQgAUEwaiEGDAgLIAEtACIiBEHdAEYNASAEQf0ARg0DIARBLEcNBCAFQQFxRQ0FIAAQ0ICAgAAMBQsgBkH7AEcNAUEDIQQgAUEwaiEGDAYLIAZB/wFxQdsARw0CDAYLQYqAwIAAQShB0IDAgAAQoYWAgAAACyAGQf8BcUH7AEYNBAsgBUEBcUUNACAGQf8BcSIGQdsARw0BQQchBCABQTBqIQYMAgsgAUEgahDIgICAAEEBIQMgBkH/AXFB+wBHDQggAUEYaiAAEM2AgIAAIAEtABhBAUYNBSABLQAZQQFGDQNBAyEEIAFBMGohBgwKCwJAIAZB+wBHDQBBCCEEIAFBMGohBgwBC0GKgMCAAEEoQdCAwIAAEKGFgIAAAAsgBiAENgIAIAAgBhDFgICAACEEIAFBIGoQyICAgAAMCgsgAUEgahDIgICAACAAENCAgIAAIAFBCGogAhDRgICAACABLQAIQQFxRQ0IIAEtAAkhBkEBIQUMAQsLAkAgAS0AGkEiRg0AQRAhBCABQSBqIQYMBgsgABDQgICAACABQRhqEMiAgIAAIAEgABCtg4CAACIENgIwIAQNByABQTBqELyAgIAAIAFBGGogABDNgICAACABLQAYQQFGDQACQCABLQAZQQFGDQBBAyEEIAFBMGohBgwFCyABLQAaQTpGDQJBBiEEIAFBIGohBgwECyABKAIcIQQMBgsgASgCJCEEDAULIAAQ0ICAgAAgAUEYahDIgICAAAwACwsgBiAENgIAIAAgBhDFgICAACEEIAFBGGoQyICAgAAMAgsgBiAENgIAIAAgBhDFgICAACEEIAFBGGoQyICAgAAMAQtBACEECyABQcAAaiSAgICAACAECwwAIABBABCUg4CAAAu4AQEDfyOAgICAAEEQayICJICAgIAAAkADQAJAIAEoAggiAyABKAIESQ0AQQAhASACQQA7AQgMAgsgAkGAAjsBCCACIAEoAgAgA2otAAAiAzoACgJAIANBd2oiBEEXSw0AQQEgBHRBk4CABHFFDQAgARDQgICAACACQQhqEMiAgIAADAELC0EBIQELIAAgAToAASAAQQA6AAAgAEECaiADOgAAIAJBCGoQyICAgAAgAkEQaiSAgICAAAvVAwEDfyOAgICAAEEgayIBJICAgIAAIAEgABDegICAAAJAAkACQAJAAkACQCABLQAAIgJBAUYNAAJAAkACQCABLQABIgNBMEYNACADQU9qQf8BcUEISw0EDAELIAFBCGogABDTgICAACABLQAIQQFGDQQgAUEIaiEDIAEtAAlBUGpB/wFxQQpPDQEgAUEMNgIQIAAgAUEQahDFgICAACEDIAFBCGoQ14CAgAAMBgsDQCABQRBqIAAQ04CAgAAgAS0AEEEBRg0FAkAgAS0AEUFQakH/AXFBCUsNACAAENCAgIAAIAFBEGoQ14CAgAAMAQsLIAFBEGohAwsgAxDXgICAACABENeAgIAAIAFBEGogABDTgICAAAJAAkACQCABLQAQQQFGDQAgAS0AESICQS5GDQECQCACQcUARg0AQQAhAyACQeUARw0DCyAAEN2AgIAAIQMMAgsgASgCFCEDDAcLIAAQ3ICAgAAhAwsgAUEQahDXgICAAAwFCyABKAIEIQMMBAsgAUEMNgIQIAAgAUEQahDJgICAACEDDAILIAEoAgwhAwwBCyABKAIUIQMgAkUNACABQQRyENiAgIAADAELIAEQ14CAgAALIAFBIGokgICAgAAgAwuEAQEDfyOAgICAAEEQayIDJICAgIAAIAAgARCNhYCAACAAEJuFgIAAIQQgACgCCCEFIAMgAjoACSADIAE6AAgCQANAIAMgA0EIahCwgYCAACADLQAAQQFxRQ0BIAQgBWogAy0AAToAACAFQQFqIQUMAAsLIAAgBTYCCCADQRBqJICAgIAACzQBAn8CQCAAKAIIIgFBAWoiAiABTw0AQbCCwIAAQRxB6InAgAAQjYaAgAAACyAAIAI2AggLbgECfyOAgICAAEEQayICJICAgIAAAkACQCABKAIIIgMNAEEAIQEMAQsgASADQX9qNgIIIAJBCGogARDPhYCAACACKAIIIAEoAghqLQAAIQNBASEBCyAAIAM6AAEgACABOgAAIAJBEGokgICAgAALjQMCAn8CfiOAgICAAEEgayIEJICAgIAAIARBCGogARDTgICAAAJAAkACQCAELQAIQQFGDQACQAJAAkACQCAELQAJIgVBLkYNACAFQcUARg0BIAVB5QBGDQFCASEGIAJFDQIgAyEHDAMLIARBEGogASACIANBABDUgICAACAEQRBqIQEgBCgCEEEBRg0EIAQpAxghByAEQRBqENWAgIAAQgAhBgwCCyAEQRBqIAEgAiADQQAQ1oCAgAAgBEEQaiEBIAQoAhBBAUYNAyAEKQMYIQcgBEEQahDVgICAAEIAIQYMAQtCACEGAkBCgICAgICAgICAf0IAIAN9IANCgICAgICAgICAf1EbIgdCAVkNAEICIQYMAQsgA7q9QoCAgICAgICAgH+FIQcLIABBADYCACAAQRBqIAc3AwAgAEEIaiAGNwMAIARBCGoQ14CAgAAMAgsgACAEKAIMNgIEIABBATYCAAwBCyAAQQE2AgAgACABKAIENgIEIARBCGoQ14CAgAALIARBIGokgICAgAALewECfyOAgICAAEEQayICJICAgIAAAkACQCABKAIIIgMgASgCBE8NACACQQE6AAkgAiABKAIAIANqLQAAIgE6AAoMAQtBACEBIAJBADoACQsgACABOgABIABBADoAACACQQA6AAggAkEIahDIgICAACACQRBqJICAgIAAC7cFAgV/AX4jgICAgABBMGsiBSSAgICAACABENCAgIAAQQAhBgJAAkACQAJAAkACQAJAAkACQAJAA0AgBUEQaiABENOAgIAAIAUtABAiB0EBRg0CIAUtABFBUGoiCEH/AXEiCUEKTw0BIAEQ0ICAgAACQAJAIANCmbPmzJmz5swZVA0AAkAgA0KZs+bMmbPmzBlSDQAgCUEGSQ0BCwNAIAVBIGogARDTgICAACAFLQAgQQFGDQYgBS0AIUFQakH/AXFBCUsNAiABENCAgIAAIAVBIGoQ14CAgAAMAAsLIANCCn4iCiAIrUL/AYN8IgMgClQNByAEQQBIIARBf0ogBEF/aiIEQX9KR3ENCCAFQRBqENeAgIAAQQEhBiAEIQQMAQsLIAVBIGoQ14CAgAAgBUEQahDXgICAAAwICyAFQRBqENeAgIAAIAZBAXENByABKAIIIgQgASgCBEkNAiAFQQA6AAlBBSEIIAVBIGohBAwDCyAAIAUoAhQ2AgQgAEEBNgIADAcLIAAgBSgCJDYCBCAAQQE2AgAgBw0EIAVBEGoQ14CAgAAMBgsgBUEBOgAJIAUgASgCACAEai0AADoACkEMIQggBUEQaiEECyAFQQA6AAggBCAINgIAIAEgBBDFgICAACEEIABBATYCACAAIAQ2AgQgBUEIahDIgICAAAwEC0GwgsCAAEEcQeSBwIAAEI2GgIAAAAtBwIHAgABBIUHMgsCAABCNhoCAAAALIAVBEGpBBHIQ2ICAgAAMAQsgBUEgaiABENOAgIAAAkAgBS0AIEEBRw0AIAAgBSgCJDYCBCAAQQE2AgAMAQsCQAJAIAUtACFBIHJB5QBGDQAgACABIAIgAyAEENmAgIAADAELIAAgASACIAMgBBDWgICAAAsgBUEgahDXgICAAAsgBUEwaiSAgICAAAsYAAJAIAAoAgBFDQAgAEEEahDYgICAAAsLwAUCBn8BfiOAgICAAEEgayIFJICAgIAAIAEQ0ICAgAAgBUEQaiABENOAgIAAQQEhBgJAAkAgBS0AEEEBRg0AAkAgBS0AEUFVaiIHQQJLDQACQAJAIAcOAwECAAELQQAhBgsgARDQgICAAAsgBUEQahDXgICAACAFQQhqIAEQx4CAgAACQCAFLQAIQQFGDQACQCAFLQAJQQFHDQAgBS0ACiEHIAVBCGoQyICAgAACQCAHQVBqQf8BcSIHQQpJDQAgBUEMNgIQIAEgBUEQahDJgICAACEBIABBATYCACAAIAE2AgQMBAsDQCAFQRBqIAEQ04CAgAACQAJAAkAgBS0AEEEBRg0AIAUtABFBUGpB/wFxIghBCUsNASABENCAgIAAIAdBy5mz5gBMDQICQCAHQcyZs+YARw0AIAhBB00NAwsgACABIAIgAyAGEN+AgIAAIAVBEGoQ14CAgAAMBwsgACAFKAIUNgIEIABBATYCAAwGCyAFQRBqENeAgIAAIAAgASACIANB/////wdBgICAgHggBCAHaiIJQQBIGyAJIARBf0oiCCAHQX9KIgpGIAggCUF/SkdxG0H/////B0GAgICAeCAEIAdrIgdBAEgbIAcgCCAKRyAIIAdBf0pHcRsgBhsQ2YCAgAAMBQsCQCAHrEIKfiILQiCIpyALpyIHQR91Rg0AQYCCwIAAQSFB7ILAgAAQjYaAgAAACwJAIAdBf0oiCSAIQX9KRiAJIAcgCGoiB0F/SkdxDQAgBUEQahDXgICAAAwBCwtBsILAgABBHEHsgsCAABCNhoCAAAALIAVBBTYCECABIAVBEGoQyYCAgAAhASAAQQE2AgAgACABNgIEIAVBCGoQyICAgAAMAgsgACAFKAIMNgIEIABBATYCAAwBCyAAIAUoAhQ2AgQgAEEBNgIACyAFQSBqJICAgIAACxgAAkAgAC0AAEUNACAAQQRqENiAgIAACwtZAQJ/AkAgACgCACIBKAIAIgJBAUsNAAJAAkAgAg4CAAEACyABQQhqKAIAIgJFDQEgASgCBCACQQEQzoKAgAAMAQsgAUEEahCNgYCAAAsgACgCABDygICAAAupAgQBfwF8AX8BfCOAgICAAEEQayIFJICAgIAAIAO6IQYCQAJAA0AgBCEHAkACQAJAAkACQCAEQX9KDQAgBEGAgICAeEYNAUEAIARrIQcLIAdBtQJJDQELIAZEAAAAAAAAAABhDQQgBEEASA0CIAVBDTYCACAAIAEgBRDJgICAADYCBAwBCyAHQQN0QeCjwIAAaisDACEIAkAgBEF/Sg0AIAYgCKMhBgwECyAGIAiiIga9Qv///////////wCDv0QAAAAAAADwf2INAyAFQQ02AgAgACABIAUQyYCAgAA2AgQLQQEhBAwDCyAGRKDI64XzzOF/oyEGIARBtAJqIQQMAAsLIABBCGogBiAGmiACGzkDAEEAIQQLIAAgBDYCACAFQRBqJICAgIAAC7IFBAJ/AX4BfwF+I4CAgIAAQSBrIgMkgICAgAAgA0EIaiABEMeAgIAAAkACQAJAAkACQAJAAkACQAJAAkAgAy0ACEEBRg0AIAMtAAlBAUcNASADLQAKIQQgA0EIahDIgICAAAJAAkAgBEH/AXFBMEYNACAEQU9qQf8BcUEJSQ0BIANBDDYCECABIANBEGoQyYCAgAAhASAAQQE2AgAgACABNgIEDAsLIANBCGogARDTgICAACADLQAIQQFGDQMCQAJAIAMtAAlBUGpB/wFxQQlLDQAgA0EMNgIQIAEgA0EQahDFgICAACEBIABBATYCACAAIAE2AgQMAQsgACABIAJCABDSgICAAAsgA0EIahDXgICAAAwKCyAEQVBqrUL/AYMhBQNAIANBCGogARDTgICAACADLQAIQQFGDQQgAy0ACUFQaiIEQf8BcSIGQQlLDQUgARDQgICAAAJAIAVCmLPmzJmz5swZWA0AAkAgBUKZs+bMmbPmzBlSDQAgBkEGSQ0BC0EBIQQgA0EQaiABIAIgBUEBENuAgIAAIAMoAhBBAUcNByAAIAMoAhQ2AgQMCAsgBUIKfiIHIAStQv8Bg3wiBSAHVA0IIANBCGoQ14CAgAAMAAsLIAAgAygCDDYCBCAAQQE2AgAMCAsgA0EFNgIQIAEgA0EQahDJgICAACEBIABBATYCACAAIAE2AgQgA0EIahDIgICAAAwHCyAAIAMoAgw2AgQgAEEBNgIADAYLIAAgAygCDDYCBCAAQQE2AgAMBQsgACABIAIgBRDSgICAAAwDCyAAQRBqIAMpAxg3AwAgAEEIakIANwMAIANBEGoQ1YCAgABBACEECyAAIAQ2AgAMAQtBsILAgABBHEHcgsCAABCNhoCAAAALIANBCGoQ14CAgAALIANBIGokgICAgAALjQIBAn8jgICAgABBEGsiBSSAgICAAAJAAkACQAJAAkADQCAFQQhqIAEQ04CAgAAgBS0ACEEBRg0BAkAgBS0ACSIGQVBqQf8BcUEJSw0AIAEQ0ICAgAAgBEF/SiIGIAYgBEEBaiIEQX9KR3ENBiAFQQhqENeAgIAADAELCyAGQS5GDQECQCAGQcUARg0AIAZB5QBGDQAgACABIAIgAyAEENmAgIAADAMLIAAgASACIAMgBBDWgICAAAwCCyAAIAUoAgw2AgQgAEEBNgIADAILIAAgASACIAMgBBDUgICAAAsgBUEIahDXgICAAAsgBUEQaiSAgICAAA8LQbCCwIAAQRxB/ILAgAAQjYaAgAAAC+UBAQJ/I4CAgIAAQRBrIgEkgICAgAAgABDQgICAAEEBIQICQAJAA0AgASAAENOAgIAAIAEtAABBAUYNAQJAIAEtAAFBUGpB/wFxQQpPDQAgABDQgICAACABENeAgIAAQQAhAgwBCwsgARDXgICAAAJAIAJBAXENACABIAAQ04CAgAAgAS0AAEEBRg0BAkACQCABLQABQSByQeUARg0AQQAhAAwBCyAAEN2AgIAAIQALIAEQ14CAgAAMAgsgAUEMNgIAIAAgARDFgICAACEADAELIAEoAgQhAAsgAUEQaiSAgICAACAAC7cCAQJ/I4CAgIAAQSBrIgEkgICAgAAgABDQgICAACABQRBqIAAQ04CAgAACQAJAIAEtABBBAUYNAAJAIAEtABFBVWoiAkECSw0AAkAgAg4DAAEAAAsgABDQgICAAAsgAUEQahDXgICAACABQQhqIAAQ3oCAgAACQAJAIAEtAAhBAUYNACABLQAJQVBqQf8BcUEJSw0BIAFBCGoQ14CAgAADQCABQRBqIAAQ04CAgAAgAS0AEEEBRg0DAkAgAS0AEUFQakH/AXFBCk8NACAAENCAgIAAIAFBEGoQ14CAgAAMAQsLIAFBEGoQ14CAgABBACEADAMLIAEoAgwhAAwCCyABQQw2AhAgACABQRBqEMmAgIAAIQAgAUEIahDXgICAAAwBCyABKAIUIQALIAFBIGokgICAgAAgAAt4AQF/I4CAgIAAQRBrIgIkgICAgAAgAkEIaiABEMeAgIAAQQEhAQJAAkAgAi0ACEEBRg0AQQAhASAAIAItAApBACACLQAJGzoAASACQQhqEMiAgIAADAELIABBBGogAigCDDYCAAsgACABOgAAIAJBEGokgICAgAAL0gEBAX8jgICAgABBEGsiBSSAgICAAAJAAkAgA1ANACAERQ0AIAVBDTYCACABIAUQyYCAgAAhASAAQQE2AgAgACABNgIEDAELAkADQCAFIAEQ04CAgAAgBS0AAEEBRg0BAkAgBS0AAUFQakH/AXFBCUsNACABENCAgIAAIAUQ14CAgAAMAQsLIAUQ14CAgAAgAEEANgIAIABBCGpEAAAAAAAAAABEAAAAAAAAAIAgAhs5AwAMAQsgACAFKAIENgIEIABBATYCAAsgBUEQaiSAgICAAAvxBQEDfyOAgICAAEEwayIDJICAgIAAIANBCGogABDTgICAAEEAIAMtAAkgAy0ACCIEQQFGGyEFAkAgBEUNACADQQhqENeAgIAACwJAAkACQAJAAkACQAJAAkAgBUH/AXEiBEEiRg0AAkACQCAEQS1GDQACQCAEQeYARg0AAkACQCAEQe4ARg0AIARB9ABGDQEgBEHbAEYNBiAEQfsARg0HIAVBUGpB/wFxQQpPDQogA0EIaiAAQQEQ2oCAgAAgAygCCEEBRg0EIANBKGogA0EYaikDADcDACADIAMpAxA3AyAgA0EgaiABIAIQnoOAgAAhBAwLCyAAENCAgIAAIAMgAEGHgMCAAEEDEMaAgIAAIgQ2AgggBA0LIANBCGoQvICAgAAgA0EHOgAIIANBCGogASACEJ+DgIAAIQQMCgsgABDQgICAACADIABBhIDAgABBAxDGgICAACIENgIIIAQNCiADQQhqELyAgIAAIANBgAI7AQggA0EIaiABIAIQn4OAgAAhBAwJCyAAENCAgIAAIAMgAEGAgMCAAEEEEMaAgIAAIgQ2AgggBA0JIANBCGoQvICAgAAgA0EAOwEIIANBCGogASACEJ+DgIAAIQQMCAsgABDQgICAACADQQhqIABBABDagICAACADKAIIQQFHDQQLIAMoAgwhBAwHCyAAENCAgIAAIABBDGoiBBDMgICAACADQSBqIAAgBBCpg4CAACADKAIgQQFHDQMgAygCJCEEDAYLIANBCjoACCADQQhqIAEgAhCfg4CAACEEDAQLIANBCzoACCADQQhqIAEgAhCfg4CAACEEDAMLIANBKGogA0EYaikDADcDACADIAMpAxA3AyAgA0EgaiABIAIQnoOAgAAhBAwCCyADIANBKGopAwA3AgwgA0EFOgAIIANBCGogASACEJ+DgIAAIQQMAQsgA0EKNgIIIAAgA0EIahDFgICAACEECyAEIAAQyoCAgAAhBAsgA0EwaiSAgICAACAEC6wBAQF/I4CAgIAAQSBrIgEkgICAgAAgAUEIaiAAEM2AgIAAAkACQAJAAkAgAS0ACEEBRg0AIAEtAAlBAUYNASABQQM2AhAgACABQRBqEMWAgIAAIQAMAgsgASgCDCEADAILAkAgAS0ACkE6Rw0AIAAQ0ICAgABBACEADAELIAFBBjYCECAAIAFBEGoQxYCAgAAhAAsgAUEIahDIgICAAAsgAUEgaiSAgICAACAAC9EBAQJ/I4CAgIAAQSBrIgEkgICAgAAgAUEIaiAAEM2AgIAAAkACQAJAAkAgAS0ACEEBRg0AIAEtAAlBAUYNASABQQM2AhAgACABQRBqEMWAgIAAIQAMAgsgASgCDCEADAILAkACQCABLQAKIgJB/QBGDQAgAkEsRw0BIAFBEjYCECAAIAFBEGoQxYCAgAAhAAwCCyAAENCAgIAAQQAhAAwBCyABQRM2AhAgACABQRBqEMWAgIAAIQALIAFBCGoQyICAgAALIAFBIGokgICAgAAgAAugAgEDfyOAgICAAEEwayIBJICAgIAAIAEgABDNgICAAAJAAkACQAJAIAEtAABBAUYNACABLQABQQFGDQEgAUECNgIgIAAgAUEgahDFgICAACEADAILIAEoAgQhAAwCCwJAIAEtAAIiAkHdAEYNAAJAIAJBLEcNACAAENCAgIAAIAFBCGogABDNgICAAAJAAkAgAS0ACA0AIAEtAAlFDQAgAS0ACkHdAEcNAEESIQMgAUEQaiECDAELQRMhAyABQSBqIQILIAIgAzYCACAAIAIQxYCAgAAhACABQQhqEMiAgIAADAILIAFBEzYCICAAIAFBIGoQxYCAgAAhAAwBCyAAENCAgIAAQQAhAAsgARDIgICAAAsgAUEwaiSAgICAACAAC6sBAQF/I4CAgIAAQRBrIgUkgICAgAAgASgCAEGNg8CAAEEBEJiFgIAAIAVBAzoACCAFQQhqEOWAgIAAIAVBCGogASAFIAMgBBDmgICAAAJAAkAgBS0ACEEDRw0AIAVBCGoQ5YCAgAAgASgCAEGNg8CAAEEBEJiFgIAAIAVBAzoACCAFQQhqEOWAgIAAIABBAzoAAAwBCyAAIAUpAwg3AgALIAVBEGokgICAgAALFwACQCAALQAAQQNGDQAgABCNgYCAAAsL+QYBDX8jgICAgABBMGsiBSSAgICAACAFQSBqIAMgAyAEahD/goCAACADQX9qIQYgBEF/cyEHIAUgBUEgahCag4CAAEEAIQggBSgCCCEJIAUoAgQhCiAFKAIAIQsDQCAKIAtrIQxBACENAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQANAIAwgDUYNASAJIA1qIg5BAWoiDyAOSQ0CIAsgDWohDiANQQFqIQ0gDi0AACIQQZy9wIAAai0AACIORQ0ACyAJIA1qIgxBf2oiESAITQ0EIAUgBDYCFCAFIAM2AhAgBSAINgIYIAUgETYCHCAIRQ0DIAggBEYNAyAIIARPDQUgAyAIaiwAAEG/f0oNAwwFCxCehYCAACAIIARGDQEgBSAENgIEIAUgAzYCACAFIAg2AhwgBSAENgIQAkACQCAIRQ0AIAggBE8NASADIAhqLAAAQb9/TA0BCyABKAIAIAMgCGogBCAIaxCYhYCAACAFQQM6ACAgBUEgahDlgICAAAwCCyAFIAVBEGo2AiggBSAFQRxqNgIkIAUgBTYCICAFQSBqEOeAgIAAAAtBsILAgABBHEHwh8CAABCNhoCAAAALIABBAzoAACAFQTBqJICAgIAADwsCQCAHIAlqIA1qRQ0AIBEgBE8NAiAGIAlqIA1qLAAAQb9/TA0CCyABKAIAIAMgCGogCSAIayANakF/ahCYhYCAACAFQQM6ACAgBUEgahDlgICAAAsgDkGSf2oiCUEHTQ0BIA5Bnn9qIglBBE0NAkGpg8CAACEJIA5BIkYNCiAOQdwARw0DQaeDwIAAIQkMCgsgBSAFQRxqNgIoIAUgBUEYajYCJCAFIAVBEGo2AiAgBUEgahDogICAAAALIAkOCAYBAQEFAQQDBgsgCQ4FAQAAAAYBC0GKgMCAAEEoQdCAwIAAEKGFgIAAAAtBpYPAgAAhCQwFCyAFQdzqwYEDNgAQIAUgEEEPcUGMvcCAAGotAAA6ABUgBSAQQQR2QYy9wIAAai0AADoAFCABKAIAIAVBEGpBBhCYhYCAAAwFC0Gdg8CAACEJDAMLQZ+DwIAAIQkMAgtBoYPAgAAhCQwBC0Gjg8CAACEJCyABKAIAIAlBAhCYhYCAAAsgBUEDOgAgIAsgDWohCyAFQSBqEOWAgIAAIA8hCSAMIQgMAAsLKgEBfyAAKAIAIgEoAgAgASgCBCAAKAIEKAIAIAAoAggoAgAQkoaAgAAACyoBAX8gACgCACIBKAIAIAEoAgQgACgCBCgCACAAKAIIKAIAEJKGgIAAAAuvAQEBfyOAgICAAEEgayICJICAgIAAIAJBCGpBgAEQ6oCAgAAgAiACQQhqNgIYIAIgASACQRhqEOuAgIAAIgE2AhwCQAJAIAENACACQRxqELyAgIAAIAJBADYCFCACQRRqELyAgIAAIABBDGogAkEQaigCADYCACAAIAIpAwg3AgQgAEEANgIADAELIABBATYCACAAIAE2AgQgAkEIahC+gICAAAsgAkEgaiSAgICAAAtGAgF/AX4jgICAgABBEGsiAiSAgICAACACQQhqIAFBABCWg4CAACACKQMIIQMgAEEANgIIIAAgAzcCACACQRBqJICAgIAAC7YBAQJ/I4CAgIAAQSBrIgIkgICAgAACQCAAKAIIDQAgAEEIaiIDQX8Q/4SAgAAaIABBDGpBAToAACADIAAoAghBAWoQ/4SAgAAaIAEoAgBBl4PAgABBBBCYhYCAACACQQM6ABAgAiACQRBqEO2AgIAAIgA2AgwCQCAADQAgAkEMahC8gICAAAsgAkEgaiSAgICAACAADwtBxITAgABBECACQRhqQciFwIAAQZiFwIAAEKqGgIAAAAvZAQEBfyOAgICAAEEgayICJICAgIAAIAJBgAEQ6oCAgAAgAkGOg8CAAEGSg8CAACABLQAAIgEbQQRBBSABGxCYhYCAACACQQM6ABggAiACQRhqEO2AgIAAIgE2AhQCQAJAIAENACACQRRqELyAgIAAIAJBADYCECACQRBqELyAgIAAIAJBADYCDCACQQxqELyAgIAAIABBDGogAkEIaigCADYCACAAIAIpAwA3AgQgAEEANgIADAELIABBATYCACAAIAE2AgQgAhC+gICAAAsgAkEgaiSAgICAAAtUAQF/I4CAgIAAQRBrIgEkgICAgAACQCAALQAAQQNGDQAgASAAKQIANwMIIAFBCGoQh4OAgAAhACABQRBqJICAgIAAIAAPCyABQRBqJICAgIAAQQALFAAgACABQYyDwIAAQQEQ74CAgAALGAAgASgCACACIAMQmIWAgAAgAEEDOgAACxQAIAAgAUGbg8CAAEEBEO+AgIAACxQAIAAgAUGrg8CAAEEBEO+AgIAACw4AIABBFEEEEM6CgIAAC4ABAQF/I4CAgIAAQTBrIgIkgICAgAAgAiABNgIMIAIgADYCCCACIAJBCGpBgYCAgAAQk4WAgAAgAkEkakEBNgIAIAJCAjcCFCACQayIwIAANgIQIAIgAikDADcDKCACIAJBKGo2AiAgAkEQahClg4CAACEAIAJBMGokgICAgAAgAAsMACABIAEQ9YCAgAALEgAgAUHsk8CAAEEMEMiGgIAACwwAIAEgARD3gICAAAsSACABQeyTwIAAQQwQyIaAgAALDAAgASABEPmAgIAACxIAIAFB7JPAgABBDBDIhoCAAAsMACABIAEQ+4CAgAALEgAgAUHsk8CAAEEMEMiGgIAACwwAIAEgARD9gICAAAsSACABQeyTwIAAQQwQyIaAgAALDAAgACABEKyFgIAACwwAIAEgARCAgYCAAAsSACABQeyTwIAAQQwQyIaAgAALDwAgACgCACABEOeGgIAACw8AIAAoAgAgARCehoCAAAsdACAAKAIAIgAoAgAgASAAKAIEKAIgEYCAgIAAAAuqAQEBfyOAgICAAEHAAGsiAiSAgICAACACIAE2AgwgAkEANgIYIAJCATcDECACQTRqQQE2AgAgAkIBNwIkIAJBrIPAgAA2AiAgAkGCgICAADYCPCACIAJBOGo2AjAgAiACQQxqNgI4IAJBEGogAkEgahDqgoCAABCFgYCAACACQRBqEJCDgIAAIABBCGogAigCGDYCACAAIAIpAxA3AgAgAkHAAGokgICAgAALSAEBfyOAgICAAEEQayIBJICAgIAAAkAgAEUNAEG0g8CAAEE3IAFBCGpBuIXAgABBtITAgAAQqoaAgAAACyABQRBqJICAgIAAC6oBAQF/I4CAgIAAQcAAayICJICAgIAAIAIgATYCDCACQQA2AhggAkIBNwMQIAJBNGpBATYCACACQgE3AiQgAkGsg8CAADYCICACQYOAgIAANgI8IAIgAkE4ajYCMCACIAJBDGo2AjggAkEQaiACQSBqEOqCgIAAEIWBgIAAIAJBEGoQkIOAgAAgAEEIaiACKAIYNgIAIAAgAikDEDcCACACQcAAaiSAgICAAAsYAAJAIAAoAgBFDQAgAEEEahDYgICAAAsLFQACQCAAKAIARQ0AIAAQvoCAgAALCwIACzwBAn8gACgCACAAKAIEKAIAEYGAgIAAAAJAIAAoAgQiASgCBCICRQ0AIAAoAgAgAiABKAIIEM6CgIAACwsCAAsCAAtgAQN/AkAgAC0AAEECSQ0AIABBBGooAgAiASgCACABKAIEKAIAEYGAgIAAAAJAIAEoAgQiAigCBCIDRQ0AIAEoAgAgAyACKAIIEM6CgIAACyAAKAIEQQxBBBDOgoCAAAsLAgALAgALGAACQCAAKAIARQ0AIABBBGoQjYGAgAALCwIACxgAAkAgACgCAEUNACAAQQRqEI2BgIAACwsVAAJAIAAoAgBFDQAgABDYgICAAAsLIwAgAEEwahC+gICAACAAQQhqEL6AgIAAIABBIGoQvoCAgAALFQACQCAAKAIARQ0AIAAQvoCAgAALCxgAAkAgACgCAEUNACAAQQRqENiAgIAACwsYAAJAIAAoAgBFDQAgAEEEahDYgICAAAsLLgEBfyAAEPaEgIAAIABBBGohAQJAIAAoAgANACABEKmEgIAADwsgARCqhICAAAsCAAsCAAsCAAsCAAsVAAJAIAAoAgBFDQAgABC+gICAAAsLAgALAgALJQACQCAAIAEgAhDGg4CAACIARQ0AIABBACABEPKGgIAAGgsgAAs9AQF/AkAgACAEIAMQxoOAgAAiBUUNACAFIAEgBCACIAIgBEsbEPOGgIAAGiAAIAEgAiADEMeDgIAACyAFCxMAIAAgASACIAMQ54KAgABBAXMLPQEBfyOAgICAAEEQayIEJICAgIAAIARBCGpBACADIAEgAhCkgYCAACAAIAQpAwg3AgAgBEEQaiSAgICAAAtAAAJAAkAgAiABSQ0AIAQgAk8NASACIAQQj4aAgAAACyABIAIQkIaAgAAACyAAIAIgAWs2AgQgACADIAFqNgIACz0BAX8jgICAgABBEGsiBCSAgICAACAEQQhqIAMgAiABIAIQpIGAgAAgACAEKQMINwIAIARBEGokgICAgAALOQACQAJAIAEoAghFDQAgACABQcAAEPOGgIAAGgwBCyAAEKeBgIAAIAEoAghFDQAgARCUgYCAAA8LCzIBAX8jgICAgABBEGsiASSAgICAACABEM2EgIAAIAAgARDggYCAACABQRBqJICAgIAACyYBAX8gAS8AACECIAFBADsAACAAIAJBCHY6AAEgACACQQFxOgAACzMAAkAgASgCAA0AIAIgAyAEEKiGgIAAAAsgACABKQIANwIAIABBCGogAUEIaigCADYCAAspAAJAIAGnDQBB5JXAgABBFSAEEKiGgIAAAAsgACACNwMAIAAgAzcDCAtpAQF/I4CAgIAAQRBrIgUkgICAgAACQCABKAIAQQFHDQAgBSABKAIENgIMIAIgAyAFQQxqQdiFwIAAIAQQqoaAgAAACyAAIAEpAgQ3AgAgAEEIaiABQQxqKAIANgIAIAVBEGokgICAgAALRAEBfyOAgICAAEEQayICJICAgIAAIAJBCGogAUEIaigCADYCACACIAEpAgA3AwAgACACEPiEgIAAIAJBEGokgICAgAALQAEBfyOAgICAAEEQayIDJICAgIAAIAMgASkDADcDCCACIANBCGpBCBCYhYCAACAAQQM6AAAgA0EQaiSAgICAAAvwAQICfwJ+I4CAgIAAQTBrIgIkgICAgAACQAJAAkAgASgCBCIDQQhJDQAgAkEQaiABKAIAIANBCBCjgYCAACACQRhqIAIoAhAgAigCFBCXhYCAACACLQAYQQFGDQIgAikAGSEEIAJBCGogASgCACABKAIEQQgQpYGAgAAgAikDCCEFIABBCGogBDcDACAAQQA2AgAgASAFNwIADAELIAJBGGpBC0GkhsCAAEEaENmCgIAAIABBATYCACAAIAIpAxg3AgQLIAJBMGokgICAgAAPC0HohcCAAEErIAJBKGpBlIbAgABBmIfAgAAQqoaAgAAAC0QBAX8jgICAgABBEGsiAiSAgICAACACQQhqIAFBCGooAgA2AgAgAiABKQIANwMAIAAgAhD5hICAACACQRBqJICAgIAAC0gBAX8jgICAgABBEGsiAiSAgICAACACQQhqIAEQqIGAgAAgAi0ACCEBIAAgAi0ACToAASAAIAFBAXE6AAAgAkEQaiSAgICAAAs8AAJAIAAoAgggACgCBEcNACAAQQEQjYWAgAALIAAQm4WAgAAgACgCCGogAToAACAAIAAoAghBAWo2AggLUAEBfyOAgICAAEEQayIDJICAgIAAIAMgAhDqgICAACADIAEgAhCYhYCAACAAQQhqIANBCGooAgA2AgAgACADKQMANwIAIANBEGokgICAgAAL8QEBAX8jgICAgABBoAFrIgMkgICAgAAgAyACNgIMIAMgATYCCCADQdAAaiADQQhqELSBgIAAAkACQCADKAJQQQFHDQAgAyADKQJUNwMQIANBmAFqIANBEGoQwIWAgAAgACADKQOYATcCBCAAQQE2AgAMAQsgA0EQaiADQdAAakEIakHAABDzhoCAABoCQCADKAIMDQAgAEEIaiADQRBqQcAAEPOGgIAAGiAAQQA2AgAMAQsgA0HQAGpBDEGIiMCAAEESENmCgIAAIABBATYCACAAIAMpA1A3AgQgA0EQahCUgYCAAAsgA0GgAWokgICAgAALjAEBAX8jgICAgABB4ABrIgIkgICAgAAgAkEIaiABEIKCgIAAAkACQCACKAIIQQFHDQAgAiACKQIMNwNYIAJB0ABqIAJB2ABqEMCFgIAAIAAgAikDUDcCBCAAQQE2AgAMAQsgAEEIaiACQQhqQQhqQcAAEPOGgIAAGiAAQQA2AgALIAJB4ABqJICAgIAAC6kDAgF/An4jgICAgABB0ABrIgMkgICAgAACQAJAAkACQAJAIAJBEEkNACADQRBqIAEgAkEQEKOBgIAAIANBMGogAygCECADKAIUEL+EgIAAIAMtADBBAUYNAiADQTlqKQAAIQQgAykAMSEFIANBCGogASACQRAQpYGAgAAgA0E8aiAENwIAIAMgBTcCNEEAIQEgAygCDCECDAELIANBMGpBC0GkhsCAAEEaENmCgIAAQQEhAQsgA0EkaiADQThqKQMANwIAIANBLGogA0EwakEQaigCADYCACADIAE2AhggAyADKQMwNwIcAkAgAUUNACADIAMpAhw3AzAgA0HIAGogA0EwahDAhYCAACAAIAMpA0g3AgQgAEEBNgIADAMLIAINASADQRhqQRBqKQMAIQQgAykDICEFIABBADYCACAAQRBqIAQ3AwAgAEEIaiAFNwMADAILQeiFwIAAQSsgA0HIAGpBlIbAgABBmIfAgAAQqoaAgAAACyADQRhqQQxBiIjAgABBEhDZgoCAACAAQQE2AgAgACADKQMYNwIECyADQdAAaiSAgICAAAvCAQIBfwF+I4CAgIAAQTBrIgIkgICAgAAgAkEIakGACBDqgICAACACQShqIAEgAkEIahC3gYCAACACIAIpAygiAzcDGAJAAkAgA6dB/wFxQQNHDQAgAkEYahDlgICAACAAIAIpAwg3AgQgAEEMaiACQRBqKAIANgIAQQAhAQwBCyACIAM3AyggAkEgaiACQShqEMCFgIAAIAAgAikDIDcCBCACQQhqEL6AgIAAQQEhAQsgACABNgIAIAJBMGokgICAgAALhAECAX8BfiOAgICAAEEgayIDJICAgIAAIANBGGogASACEP+BgIAAIAMgAykDGCIENwMIAkACQCAEp0H/AXFBA0cNACADQQhqEOWAgIAAIABBAzoAAAwBCyADIAQ3AxggA0EQaiADQRhqEMCFgIAAIAAgAykDEDcCAAsgA0EgaiSAgICAAAuNAQEBfyOAgICAAEEwayICJICAgIAAIAJBCGpBgAgQ6oCAgAAgAiABQQhqKQMANwMoIAIgASkDADcDICACQQhqIAJBIGpBEBCYhYCAACACQgM3AxggAkEYahDlgICAACAAQQxqIAJBCGpBCGooAgA2AgAgACACKQMINwIEIABBADYCACACQTBqJICAgIAAC8IBAgF/AX4jgICAgABBMGsiAiSAgICAACACQQhqQYAIEOqAgIAAIAJBKGogASACQQhqELqBgIAAIAIgAikDKCIDNwMYAkACQCADp0H/AXFBA0cNACACQRhqEOWAgIAAIAAgAikDCDcCBCAAQQxqIAJBEGooAgA2AgBBACEBDAELIAIgAzcDKCACQSBqIAJBKGoQwIWAgAAgACACKQMgNwIEIAJBCGoQvoCAgABBASEBCyAAIAE2AgAgAkEwaiSAgICAAAt5AQF/I4CAgIAAQRBrIgMkgICAgAAgAyABKAIINgIEIAIgA0EEakEEEJiFgIAAIANCAzcDCCADQQhqEOWAgIAAIAIgARCbhYCAACABKAIIEJiFgIAAIANCAzcDCCADQQhqEOWAgIAAIABBAzoAACADQRBqJICAgIAAC7MBAgF/AX4jgICAgABB0ABrIgMkgICAgAAgAyACNgIkIAMgATYCICADIAA2AhwgA0EQaiADQRxqQYSAgIAAEJGFgIAAIAMpAxAhBCADQQhqIANBIGpBhYCAgAAQ6IKAgAAgA0E8akECNgIAIAMgBDcDQCADQgI3AiwgA0HYiMCAADYCKCADIAMpAwg3A0ggAyADQcAAajYCOCADQShqEKWDgIAAIQAgA0HQAGokgICAgAAgAAuAAQEBfyOAgICAAEEwayICJICAgIAAIAIgATYCDCACIAA2AgggAiACQQhqQYGAgIAAEJOFgIAAIAJBJGpBATYCACACQgI3AhQgAkH8iMCAADYCECACIAIpAwA3AyggAiACQShqNgIgIAJBEGoQpYOAgAAhACACQTBqJICAgIAAIAAL7wMBBH8jgICAgABBMGsiAiSAgICAACACQQhqIAEQzYCAgAACQAJAAkACQAJAIAItAAhBAUYNACACLQAJQQFHDQEgAi0ACiEDIAJBCGoQyICAgAACQCADQSJGDQAgASACQShqQbCQwIAAEOCAgIAAIQMgAkEBNgIIIAIgAzYCDAwECyABENCAgIAAIAFBDGoiAxDMgICAACACQRhqIAEgAxCpg4CAACACKAIcIQMCQAJAAkAgAigCGEEBRg0AIAJBGGpBDGooAgAhBCACQSBqKAIAIQUgAw0BIAJBCGogBSAEEJmDgIAADAILIABBATYCACAAIAM2AgQMBAsgAkEIaiAFIAQQmYOAgAALQQEhAyACKAIIQQFGDQMgACACKQIMNwIEIABBDGogAkEIakEMaigCADYCAEEAIQEMBAsgACACKAIMNgIEIABBATYCAAwBCyACQQU2AhggASACQRhqEMWAgIAAIQEgAEEBNgIAIAAgATYCBCACQQhqEMiAgIAACyACQTBqJICAgIAADwsgACACKAIMIAEQyoCAgAA2AgRBASEBQQAhAwsgACABNgIAAkACQCACKAIIDQAgAUUNASACQQhqQQRyEL6AgIAADAELIANFDQAgAkEIakEEchDYgICAAAsgAkEwaiSAgICAAAvvAQEBfyOAgICAAEEgayIEJICAgIAAIAQgACABIAIQv4GAgAAiATYCGAJAAkAgAQ0AIARBGGoQvICAgAAgBEEYaiAAKAIAEPGAgIAAIAQgBEEYahDtgICAACIBNgIUIAENASAEQRRqELyAgIAAIAAoAgAhASAEQQhqIAMQz4WAgAAgBCABIAQoAgggBCgCDBDAgYCAACIBNgIYIAENASAEQRhqELyAgIAAIARBAzoAGCAEIARBGGoQ7YCAgAAiATYCFCABDQEgBEEUahC8gICAAEEAIQEMAQsgARCNg4CAACEBCyAEQSBqJICAgIAAIAELyAEBAn8jgICAgABBEGsiAySAgICAAAJAIAAtAARBAUYNACAAKAIAKAIAQZyDwIAAQQEQmIWAgAALIANBAzoACCADIANBCGoQ7YCAgAAiBDYCBAJAIAQNACADQQRqELyAgIAAIABBAjoABCADIAAoAgAgASACEMCBgIAAIgQ2AgggBA0AIANBCGoQvICAgAAgA0EDOgAIIAMgA0EIahDtgICAACIENgIEIAQNACADQQRqELyAgIAAQQAhBAsgA0EQaiSAgICAACAEC1kBAX8jgICAgABBEGsiAySAgICAACADQQhqIAAgACABIAIQ5ICAgAAgAyADQQhqEO2AgIAAIgA2AgQCQCAADQAgA0EEahC8gICAAAsgA0EQaiSAgICAACAAC9YBAQF/I4CAgIAAQRBrIgQkgICAgAAgBCAAIAEgAhC/gYCAACIBNgIIAkACQCABDQAgBEEIahC8gICAACAEQQhqIAAoAgAQ8YCAgAAgBCAEQQhqEO2AgIAAIgE2AgQgAQ0BIARBBGoQvICAgAAgBCADIAAoAgAQwoGAgAAiATYCCCABDQEgBEEIahC8gICAACAEQQM6AAggBCAEQQhqEO2AgIAAIgE2AgQgAQ0BIARBBGoQvICAgABBACEBDAELIAEQjYOAgAAhAQsgBEEQaiSAgICAACABC9gBAQF/I4CAgIAAQcAAayICJICAgIAAIAIgADYCDCACQQA2AhggAkIBNwMQIAJBNGpBATYCACACQgE3AiQgAkGsg8CAADYCICACQYaAgIAANgI8IAIgAkE4ajYCMCACIAJBDGo2AjggAkEQaiACQSBqEOqCgIAAEIWBgIAAIAJBEGoQkIOAgAAgAkEoaiACKAIYNgIAIAIgAikDEDcDICACIAJBIGoQz4WAgAAgASACKAIAIAIoAgQQwIGAgAAhASACQSBqEL6AgIAAIAJBwABqJICAgIAAIAELIgAgASACEPOAgIAAEI2DgIAAIQEgAEEBNgIAIAAgATYCBAsiACABIAIQ84CAgAAQjYOAgAAhASAAQQE2AgAgACABNgIECz4BAn8jgICAgABBEGsiASSAgICAACABIAAQiYaAgAAgARCmg4CAACECIAAQvoCAgAAgAUEQaiSAgICAACACC10BAn8jgICAgABBEGsiAiSAgICAACACQQhqIAAQz4WAgAAgAigCDCEAIAIoAgghAyACIAEQz4WAgAAgAyAAIAIoAgAgAigCBBCBg4CAACEBIAJBEGokgICAgAAgAQuXBAEDfyOAgICAAEHQAGsiAiSAgICAAAJAAkACQAJAIAEoAgQiA0EESQ0AIAJBGGogASgCACADQQQQo4GAgAAgAkEwaiACKAIYIAIoAhwQw4WAgAAgAi0AMEEBRg0CIAIoADEhAyACQRBqIAEoAgAgASgCBEEEEKWBgIAAIAEgAikDEDcCACACIAOtNwJEIAJBADYCQCACQcAAahCSgYCAACADRQ0BAkAgASgCBCIEIANJDQAgAkEIaiABKAIAIAQgAxCjgYCAACACQSBqIAIoAgggAigCDBCygYCAACACIAEoAgAgASgCBCADEKWBgIAAIAEgAikDADcCACACQcAAakEIaiIBIAJBIGpBCGooAgA2AgAgAiACKQMgNwNAIAJBMGogAkHAAGoQm4WAgAAgASgCACACKAJEEMiFgIAAIABBDGogAkEwakEIaigCADYCACAAIAIpAzA3AgQgAEEANgIADAQLIAJBwABqQQtBpIbAgABBGhDZgoCAACAAQQE2AgAgACACKQNANwIEDAMLIAJBMGpBC0GkhsCAAEEaENmCgIAAIAIgAikDMDcDMCACQSBqIAJBMGoQwIWAgAAgACACKQMgNwIEIABBATYCAAwCCyAAQoCAgIAQNwIAIABBCGpCADcCAAwBC0HohcCAAEErIAJBIGpBlIbAgABBmIfAgAAQqoaAgAAACyACQdAAaiSAgICAAAvWBQEDfyOAgICAAEEgayICJICAgIAAIAJBCGogASgCABDNgICAAAJAAkACQAJAAkACQCACLQAIQQFGDQACQCACLQAJQQFGDQAgASgCACEBIAJBAzYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAULAkACQAJAAkAgAi0ACiIDQSxGDQAgA0H9AEcNASAAQYAEOwEADAgLIAEtAAQNASABKAIAENCAgIAAIAJBEGogASgCABDNgICAACACLQAQQQFGDQQgAi0AEiEDIAItABEhBCACQRBqEMiAgIAAIAJBCGoQyICAgAAgBA0CIAEoAgAhASACQQU2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwICyABLQAEDQAgASgCACEBIAJBCDYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAYLIAFBADoABCACQQhqEMiAgIAACyADQf8BcSIDQSJGDQMgA0H9AEcNAiABKAIAIQEgAkESNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMBQsgAEEBOgAAIABBBGogAigCDDYCAAwECyAAQQE6AAAgAEEEaiACKAIUNgIADAILIAEoAgAhASACQRA2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwCCyABKAIAIgEQ0ICAgAAgAUEMaiIDEMyAgIAAIAJBEGogASADEKmDgIAAAkACQAJAAkAgAigCEEEBRw0AIAIgAigCFCIBNgIMIAJBAToACAwBCyACQQhqIAJBGGooAgAgAkEQakEMaigCABDJgYCAACACLQAIQQFHDQEgAigCDCEBCyAAQQRqIAE2AgBBASEBDAELIAAgAi0ACToAAUEAIQELIAAgAToAAAwBCyACQQhqEMiAgIAACyACQSBqJICAgIAACycAIAEgAkHmk8CAAEEGEIGDgIAAIQEgAEEAOgAAIAAgAUEBczoAAQvWBQEDfyOAgICAAEEgayICJICAgIAAIAJBCGogASgCABDNgICAAAJAAkACQAJAAkACQCACLQAIQQFGDQACQCACLQAJQQFGDQAgASgCACEBIAJBAzYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAULAkACQAJAAkAgAi0ACiIDQSxGDQAgA0H9AEcNASAAQYAGOwEADAgLIAEtAAQNASABKAIAENCAgIAAIAJBEGogASgCABDNgICAACACLQAQQQFGDQQgAi0AEiEDIAItABEhBCACQRBqEMiAgIAAIAJBCGoQyICAgAAgBA0CIAEoAgAhASACQQU2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwICyABLQAEDQAgASgCACEBIAJBCDYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAYLIAFBADoABCACQQhqEMiAgIAACyADQf8BcSIDQSJGDQMgA0H9AEcNAiABKAIAIQEgAkESNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMBQsgAEEBOgAAIABBBGogAigCDDYCAAwECyAAQQE6AAAgAEEEaiACKAIUNgIADAILIAEoAgAhASACQRA2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwCCyABKAIAIgEQ0ICAgAAgAUEMaiIDEMyAgIAAIAJBEGogASADEKmDgIAAAkACQAJAAkAgAigCEEEBRw0AIAIgAigCFCIBNgIMIAJBAToACAwBCyACQQhqIAJBGGooAgAgAkEQakEMaigCABDLgYCAACACLQAIQQFHDQEgAigCDCEBCyAAQQRqIAE2AgBBASEBDAELIAAgAi0ACToAAUEAIQELIAAgAToAAAwBCyACQQhqEMiAgIAACyACQSBqJICAgIAAC1UAAkACQCABIAJB3JvAgABBFhCBg4CAAEUNACAAQQA6AAEMAQsCQCABIAJB5pPAgABBBhCBg4CAAEUNACAAQQE6AAEMAQsgAEECOgABCyAAQQA6AAAL1gUBA38jgICAgABBIGsiAiSAgICAACACQQhqIAEoAgAQzYCAgAACQAJAAkACQAJAAkAgAi0ACEEBRg0AAkAgAi0ACUEBRg0AIAEoAgAhASACQQM2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwFCwJAAkACQAJAIAItAAoiA0EsRg0AIANB/QBHDQEgAEGABDsBAAwICyABLQAEDQEgASgCABDQgICAACACQRBqIAEoAgAQzYCAgAAgAi0AEEEBRg0EIAItABIhAyACLQARIQQgAkEQahDIgICAACACQQhqEMiAgIAAIAQNAiABKAIAIQEgAkEFNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMCAsgAS0ABA0AIAEoAgAhASACQQg2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwGCyABQQA6AAQgAkEIahDIgICAAAsgA0H/AXEiA0EiRg0DIANB/QBHDQIgASgCACEBIAJBEjYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAULIABBAToAACAAQQRqIAIoAgw2AgAMBAsgAEEBOgAAIABBBGogAigCFDYCAAwCCyABKAIAIQEgAkEQNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMAgsgASgCACIBENCAgIAAIAFBDGoiAxDMgICAACACQRBqIAEgAxCpg4CAAAJAAkACQAJAIAIoAhBBAUcNACACIAIoAhQiATYCDCACQQE6AAgMAQsgAkEIaiACQRhqKAIAIAJBEGpBDGooAgAQzYGAgAAgAi0ACEEBRw0BIAIoAgwhAQsgAEEEaiABNgIAQQEhAQwBCyAAIAItAAk6AAFBACEBCyAAIAE6AAAMAQsgAkEIahDIgICAAAsgAkEgaiSAgICAAAsnACABIAJB35rAgABBChCBg4CAACEBIABBADoAACAAIAFBAXM6AAEL1gUBA38jgICAgABBIGsiAiSAgICAACACQQhqIAEoAgAQzYCAgAACQAJAAkACQAJAAkAgAi0ACEEBRg0AAkAgAi0ACUEBRg0AIAEoAgAhASACQQM2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwFCwJAAkACQAJAIAItAAoiA0EsRg0AIANB/QBHDQEgAEGABDsBAAwICyABLQAEDQEgASgCABDQgICAACACQRBqIAEoAgAQzYCAgAAgAi0AEEEBRg0EIAItABIhAyACLQARIQQgAkEQahDIgICAACACQQhqEMiAgIAAIAQNAiABKAIAIQEgAkEFNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMCAsgAS0ABA0AIAEoAgAhASACQQg2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwGCyABQQA6AAQgAkEIahDIgICAAAsgA0H/AXEiA0EiRg0DIANB/QBHDQIgASgCACEBIAJBEjYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAULIABBAToAACAAQQRqIAIoAgw2AgAMBAsgAEEBOgAAIABBBGogAigCFDYCAAwCCyABKAIAIQEgAkEQNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMAgsgASgCACIBENCAgIAAIAFBDGoiAxDMgICAACACQRBqIAEgAxCpg4CAAAJAAkACQAJAIAIoAhBBAUcNACACIAIoAhQiATYCDCACQQE6AAgMAQsgAkEIaiACQRhqKAIAIAJBEGpBDGooAgAQz4GAgAAgAi0ACEEBRw0BIAIoAgwhAQsgAEEEaiABNgIAQQEhAQwBCyAAIAItAAk6AAFBACEBCyAAIAE6AAAMAQsgAkEIahDIgICAAAsgAkEgaiSAgICAAAsnACABIAJB3JPAgABBChCBg4CAACEBIABBADoAACAAIAFBAXM6AAEL1gUBA38jgICAgABBIGsiAiSAgICAACACQQhqIAEoAgAQzYCAgAACQAJAAkACQAJAAkAgAi0ACEEBRg0AAkAgAi0ACUEBRg0AIAEoAgAhASACQQM2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwFCwJAAkACQAJAIAItAAoiA0EsRg0AIANB/QBHDQEgAEGABjsBAAwICyABLQAEDQEgASgCABDQgICAACACQRBqIAEoAgAQzYCAgAAgAi0AEEEBRg0EIAItABIhAyACLQARIQQgAkEQahDIgICAACACQQhqEMiAgIAAIAQNAiABKAIAIQEgAkEFNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMCAsgAS0ABA0AIAEoAgAhASACQQg2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwGCyABQQA6AAQgAkEIahDIgICAAAsgA0H/AXEiA0EiRg0DIANB/QBHDQIgASgCACEBIAJBEjYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAULIABBAToAACAAQQRqIAIoAgw2AgAMBAsgAEEBOgAAIABBBGogAigCFDYCAAwCCyABKAIAIQEgAkEQNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMAgsgASgCACIBENCAgIAAIAFBDGoiAxDMgICAACACQRBqIAEgAxCpg4CAAAJAAkACQAJAIAIoAhBBAUcNACACIAIoAhQiATYCDCACQQE6AAgMAQsgAkEIaiACQRhqKAIAIAJBEGpBDGooAgAQ0YGAgAAgAi0ACEEBRw0BIAIoAgwhAQsgAEEEaiABNgIAQQEhAQwBCyAAIAItAAk6AAFBACEBCyAAIAE6AAAMAQsgAkEIahDIgICAAAsgAkEgaiSAgICAAAtVAAJAAkAgASACQZybwIAAQQ4QgYOAgABFDQAgAEEAOgABDAELAkAgASACQaqbwIAAQQ4QgYOAgABFDQAgAEEBOgABDAELIABBAjoAAQsgAEEAOgAAC9YFAQN/I4CAgIAAQSBrIgIkgICAgAAgAkEIaiABKAIAEM2AgIAAAkACQAJAAkACQAJAIAItAAhBAUYNAAJAIAItAAlBAUYNACABKAIAIQEgAkEDNgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMBQsCQAJAAkACQCACLQAKIgNBLEYNACADQf0ARw0BIABBgAY7AQAMCAsgAS0ABA0BIAEoAgAQ0ICAgAAgAkEQaiABKAIAEM2AgIAAIAItABBBAUYNBCACLQASIQMgAi0AESEEIAJBEGoQyICAgAAgAkEIahDIgICAACAEDQIgASgCACEBIAJBBTYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAgLIAEtAAQNACABKAIAIQEgAkEINgIQIAEgAkEQahDFgICAACEBIABBAToAACAAQQRqIAE2AgAMBgsgAUEAOgAEIAJBCGoQyICAgAALIANB/wFxIgNBIkYNAyADQf0ARw0CIAEoAgAhASACQRI2AhAgASACQRBqEMWAgIAAIQEgAEEBOgAAIABBBGogATYCAAwFCyAAQQE6AAAgAEEEaiACKAIMNgIADAQLIABBAToAACAAQQRqIAIoAhQ2AgAMAgsgASgCACEBIAJBEDYCECABIAJBEGoQxYCAgAAhASAAQQE6AAAgAEEEaiABNgIADAILIAEoAgAiARDQgICAACABQQxqIgMQzICAgAAgAkEQaiABIAMQqYOAgAACQAJAAkACQCACKAIQQQFHDQAgAiACKAIUIgE2AgwgAkEBOgAIDAELIAJBCGogAkEYaigCACACQRBqQQxqKAIAENOBgIAAIAItAAhBAUcNASACKAIMIQELIABBBGogATYCAEEBIQEMAQsgACACLQAJOgABQQAhAQsgACABOgAADAELIAJBCGoQyICAgAALIAJBIGokgICAgAALVQACQAJAIAEgAkGcm8CAAEEOEIGDgIAARQ0AIABBADoAAQwBCwJAIAEgAkGqm8CAAEEOEIGDgIAARQ0AIABBAToAAQwBCyAAQQI6AAELIABBADoAAAtkAQJ/I4CAgIAAQRBrIgIkgICAgAAgAiABKAIAEOGAgIAAIgM2AgwCQAJAIAMNACACQQxqELyAgIAAIAAgASgCABC9gYCAAAwBCyAAQQE2AgAgACADNgIECyACQRBqJICAgIAAC2QBAn8jgICAgABBEGsiAiSAgICAACACIAEoAgAQ4YCAgAAiAzYCDAJAAkAgAw0AIAJBDGoQvICAgAAgACABKAIAENaBgIAADAELIABBATYCACAAIAM2AgQLIAJBEGokgICAgAALmAICAn8BfiOAgICAAEHAAGsiAiSAgICAACACQRhqIAEQvYGAgABBASEBAkACQCACKAIYQQFHDQAgACACKAIcEI2DgIAANgIEDAELIAJBCGpBCGogAkEYakEEciIBQQhqKAIAIgM2AgAgAiABKQIAIgQ3AwggAkEwakEIaiADNgIAIAIgBDcDMCACQRhqIAJBMGoQr4GAgAACQCACKAIYQQFHDQAgAiACKQIcNwMoIAJBMGogAkEoahCEgYCAACACQTBqEMWBgIAAIQEgAkEoahCKgYCAACAAIAE2AgRBASEBDAELIAAgAikCHDcCBCAAQQxqIAJBGGpBDGooAgA2AgBBACEBCyAAIAE2AgAgAkHAAGokgICAgAALZAECfyOAgICAAEEQayICJICAgIAAIAIgASgCABDhgICAACIDNgIMAkACQCADDQAgAkEMahC8gICAACAAIAEoAgAQ2IGAgAAMAQsgAEEBNgIAIAAgAzYCBAsgAkEQaiSAgICAAAuFAwMBfwJ+AX8jgICAgABB4ABrIgIkgICAgAAgAkEwaiABEL2BgIAAAkACQCACKAIwQQFHDQAgAigCNBCNg4CAACEBIABBATYCACAAIAE2AgQMAQsgAkEIakEIaiACQTBqQQRyIgFBCGooAgA2AgAgAiABKQIANwMIIAIgAkEIahDPhYCAACACQTBqIAIoAgAgAigCBEEKENyGgIAAAkACQCACLQAwQQFGDQAgAkHAAGopAwAhAyACQTBqQQhqKQMAIQRBACEBDAELIAIgAi0AMToATyACQdAAaiACQc8AahCGgYCAACACQdAAahDFgYCAACEFQQEhAQsgAkEYakEQaiADNwMAIAIgBDcDICACIAU2AhwgAiABNgIYAkAgAUUNACAFEI2DgIAAIQEgAEEBNgIAIAAgATYCBCACQQhqEL6AgIAADAELIABBADYCACAAQRBqIAM3AwAgAEEIaiAENwMAIAJBCGoQvoCAgAAgAkEYahCHgYCAAAsgAkHgAGokgICAgAALUwECfyOAgICAAEEQayIBJICAgIAAIAEgACgCABDhgICAACICNgIMAkAgAg0AIAFBDGoQvICAgAAgACgCABDagYCAACECCyABQRBqJICAgIAAIAILQwEBfyOAgICAAEEQayIBJICAgIAAIAEgABDLgICAACIANgIMAkAgAA0AIAFBDGoQvICAgAALIAFBEGokgICAgAAgAAu+BAEDfyOAgICAAEEgayICJICAgIAAIAIgASgCABDNgICAAAJAAkACQAJAAkAgAi0AAEEBRg0AAkAgAi0AAUEBRg0AIAEoAgAhASACQQI2AgggASACQQhqEMWAgIAAIQEgAEEBNgIAIAAgATYCBAwECwJAAkACQAJAIAItAAIiA0EsRg0AIANB3QBHDQEgAEEANgIAIABBCGpCADcDAAwHCyABLQAEDQEgASgCABDQgICAACACQQhqIAEoAgAQzYCAgAAgAi0ACEEBRg0EIAItAAohAyACLQAJIQQgAkEIahDIgICAACACEMiAgIAAIAQNAiABKAIAIQEgAkEFNgIIIAEgAkEIahDFgICAACEBIABBATYCACAAIAE2AgQMBwsgAS0ABA0AIAEoAgAhASACQQc2AgggASACQQhqEMWAgIAAIQEgAEEBNgIAIAAgATYCBAwFCyABQQA6AAQgAhDIgICAAAsCQCADQf8BcUHdAEcNACABKAIAIQEgAkESNgIIIAEgAkEIahDFgICAACEBIABBATYCACAAIAE2AgQMBQsgAkEIaiABKAIAENiBgIAAIAIoAghBAUcNAiAAIAIoAgw2AgQgAEEBNgIADAQLIAAgAigCBDYCBCAAQQE2AgAMAwsgACACKAIMNgIEIABBATYCAAwBCyAAQQA2AgAgAEEQaiACKQMQNwMAIABBCGpCATcDACAAQRhqIAJBCGpBEGopAwA3AwAMAQsgAhDIgICAAAsgAkEgaiSAgICAAAuzBAEDfyOAgICAAEEgayICJICAgIAAIAJBCGogASgCABDNgICAAAJAAkACQAJAAkAgAi0ACEEBRg0AAkAgAi0ACUEBRg0AIAEoAgAhASACQQI2AhAgASACQRBqEMWAgIAAIQEgAEEBNgIAIAAgATYCBAwECwJAAkACQAJAIAItAAoiA0EsRg0AIANB3QBHDQEgAEIANwIADAcLIAEtAAQNASABKAIAENCAgIAAIAJBEGogASgCABDNgICAACACLQAQQQFGDQQgAi0AEiEDIAItABEhBCACQRBqEMiAgIAAIAJBCGoQyICAgAAgBA0CIAEoAgAhASACQQU2AhAgASACQRBqEMWAgIAAIQEgAEEBNgIAIAAgATYCBAwHCyABLQAEDQAgASgCACEBIAJBBzYCECABIAJBEGoQxYCAgAAhASAAQQE2AgAgACABNgIEDAULIAFBADoABCACQQhqEMiAgIAACyADQf8BcUHdAEcNAiABKAIAIQEgAkESNgIQIAEgAkEQahDFgICAACEBIABBATYCACAAIAE2AgQMBAsgACACKAIMNgIEIABBATYCAAwDCyAAIAIoAhQ2AgQgAEEBNgIADAELIAJBEGogASgCABDWgYCAAAJAIAIoAhBBAUcNACAAIAIoAhQ2AgQgAEEBNgIADAILIAAgAikCFDcCBCAAQQA2AgAgAEEMaiACQRBqQQxqKAIANgIADAELIAJBCGoQyICAgAALIAJBIGokgICAgAALswQBA38jgICAgABBIGsiAiSAgICAACACQQhqIAEoAgAQzYCAgAACQAJAAkACQAJAIAItAAhBAUYNAAJAIAItAAlBAUYNACABKAIAIQEgAkECNgIQIAEgAkEQahDFgICAACEBIABBATYCACAAIAE2AgQMBAsCQAJAAkACQCACLQAKIgNBLEYNACADQd0ARw0BIABCADcCAAwHCyABLQAEDQEgASgCABDQgICAACACQRBqIAEoAgAQzYCAgAAgAi0AEEEBRg0EIAItABIhAyACLQARIQQgAkEQahDIgICAACACQQhqEMiAgIAAIAQNAiABKAIAIQEgAkEFNgIQIAEgAkEQahDFgICAACEBIABBATYCACAAIAE2AgQMBwsgAS0ABA0AIAEoAgAhASACQQc2AhAgASACQRBqEMWAgIAAIQEgAEEBNgIAIAAgATYCBAwFCyABQQA6AAQgAkEIahDIgICAAAsgA0H/AXFB3QBHDQIgASgCACEBIAJBEjYCECABIAJBEGoQxYCAgAAhASAAQQE2AgAgACABNgIEDAQLIAAgAigCDDYCBCAAQQE2AgAMAwsgACACKAIUNgIEIABBATYCAAwBCyACQRBqIAEoAgAQvYGAgAACQCACKAIQQQFHDQAgACACKAIUNgIEIABBATYCAAwCCyAAIAIpAhQ3AgQgAEEANgIAIABBDGogAkEQakEMaigCADYCAAwBCyACQQhqEMiAgIAACyACQSBqJICAgIAACxQAIABB5pPAgABBBiABEMGBgIAAC2MBAX8jgICAgABBEGsiAiSAgICAAAJAAkAgAUH/AXFFDQAgAkEIaiAAEO6AgIAAIAIgAkEIahDtgICAACIBNgIEIAENASACQQRqELyAgIAAC0EAIQELIAJBEGokgICAgAAgAQv4AgEDfyOAgICAAEEwayICJICAgIAAAkACQAJAIAEoAggiA0EBaiIEIANJDQAgAiAEEOqAgIAAIAIgARDygYCAACACQekAELGBgIAAIAEoAggiA0EBaiIEIANJDQEgAkEQaiAEEOqAgIAAIAJBEGogARDygYCAACACQRBqQesAELGBgIAAIAEoAggiA0EBaiIEIANJDQIgAkEgaiAEEOqAgIAAIAJBIGogARDygYCAACACQSBqQfYAELGBgIAAIABCADcDACAAQgA3AxggAEE4aiACQQhqKAIANgIAIAAgAikDADcCMCAAIAIpAxA3AgggAEEQaiACQRBqQQhqKAIANgIAIABBIGogAikDIDcCACAAQShqIAJBIGpBCGooAgA2AgAgARC+gICAACACQTBqJICAgIAADwtBsILAgABBHEHIjMCAABCNhoCAAAALQbCCwIAAQRxB2IzAgAAQjYaAgAAAC0GwgsCAAEEcQeiMwIAAEI2GgIAAAAvcAgEBfyOAgICAAEHQAGsiBiSAgICAACAGQSBqIAEgAiADEOKBgIAAIAZBGGogBkEgahDPhYCAACAGQTBqIAYoAhggBigCHBDphICAAAJAAkAgBigCMA0AIAYgARDjgYCAADcDQCAGQQhqIAZBIGoQz4WAgAAgBigCCCAGKAIMIAZBwABqQQgQ6ISAgAAaIAEgAiADEOSBgIAAIAFBGGogBCAFEOWBgIAAIABBADYCAEEBIQEMAQsgBkHAAGpBCGogBkEwakEIaigCADYCACAGIAYpAzA3A0AgBkEQaiAGQcAAahDPhYCAACAAIAFBGGogBigCECAGKAIUEOaBgIAAIAQgBRDngYCAACAGQcAAahC+gICAAEEAIQELIAZBIGoQvoCAgAACQAJAIAYoAjBFDQAgAUUNASAGQTBqEL6AgIAADAELIAZBMGoQiIGAgAALIAZB0ABqJICAgIAAC6sBAQN/I4CAgIAAQSBrIgQkgICAgAACQCABQThqKAIAIgUgA2oiBiAFSQ0AIARBEGogBhDqgICAACAEQQhqIAFBMGoQz4WAgAAgBEEQaiAEKAIIIAQoAgwQmIWAgAAgBEEQaiACIAMQmIWAgAAgAEEIaiAEQRBqQQhqKAIANgIAIAAgBCkDEDcCACAEQSBqJICAgIAADwtBsILAgABBHEG4jMCAABCNhoCAAAALKQEBfgJAIAApAwAiASAAKQMYUg0AIAEPC0HsisCAAEHmABDPhICAAAALlQECAX8CfiOAgICAAEEgayIDJICAgIAAIANBEGogACAAKQMAEPeBgIAAAkAgACkDACIEQgF8IgUgBFQNACAAIAU3AwAgA0EIaiADQRBqEM+FgIAAIAMoAgggAygCDCABIAIQ6ISAgAAaIANBEGoQvoCAgAAgA0EgaiSAgICAAA8LQbCCwIAAQRxBzI7AgAAQjYaAgAAAC5UBAgF/An4jgICAgABBIGsiAySAgICAACADQRBqIAAgACkDABD2gYCAAAJAIAApAwAiBEIBfCIFIARUDQAgACAFNwMAIANBCGogA0EQahDPhYCAACADKAIIIAMoAgwgASACEOiEgIAAGiADQRBqEL6AgIAAIANBIGokgICAgAAPC0GwgsCAAEEcQcyOwIAAEI2GgIAAAAtDAgF/AX4jgICAgABBEGsiAiSAgICAACACQgA3AwggAkEIakEIIAAgARCOhYCAACACKQMIIQMgAkEQaiSAgICAACADC9QBAQF/I4CAgIAAQTBrIgUkgICAgAACQAJAAkAgASkDACACWA0AIAVBEGogASACEPaBgIAAIAVBCGogBUEQahDPhYCAACAFKAIIIAUoAgwgAyAEEOiEgIAARQ0BIAVBIGoQ64SAgAAgBSgCIEUNAiAAIAUpAyA3AgAgAEEIaiAFQSBqQQhqKAIANgIAIAVBEGoQvoCAgAAgBUEwaiSAgICAAA8LQfiMwIAAQRMQz4SAgAAAC0HsisCAAEHmABDPhICAAAALQeyKwIAAQeYAEM+EgIAAAAujBQIBfwJ+I4CAgIAAQZABayIEJICAgIAAIARBwABqIAEgAiADEOKBgIAAIARBOGogBEHAAGoQz4WAgAAgBEHQAGogBCgCOCAEKAI8EOmEgIAAAkACQAJAIAQoAlANACAAQQA2AgAgBEHAAGoQvoCAgAAMAQsgBEHgAGpBCGogBEHQAGpBCGooAgA2AgAgBCAEKQNQNwNgAkACQAJAIAEQ44GAgABCAVENAAJAIAEQ44GAgAAiBUJ/fCIGIAVWDQAgBEGAAWogASAGEOmBgIAAIAQoAoABRQ0CIARB8ABqQQhqIARBgAFqQQhqKAIANgIAIAQgBCkDgAE3A3AgBEEwaiAEQcAAahDPhYCAACAEKAIwIAQoAjQQ6oSAgAAaIARBKGogBEHwAGoQz4WAgAACQCAEKAIoIAQoAiwgAiADEKKBgIAARQ0AIARBIGogBEHwAGoQz4WAgAAgBEGAAWogASAEKAIgIAQoAiQQ4oGAgAAgBEEYaiAEQYABahDPhYCAACAEKAIcIQIgBCgCGCEDIARBEGogBEHgAGoQz4WAgAAgAyACIAQoAhAgBCgCFBDohICAABogBEGAAWoQvoCAgAALIARB8ABqEL6AgIAADAMLQcCBwIAAQSFB3IrAgAAQjYaAgAAACyAEQQhqIARBwABqEM+FgIAAIAQoAgggBCgCDBDqhICAABoMAQtB7IrAgABB5gAQz4SAgAAACyAEIARB4ABqEM+FgIAAIARBgAFqIAEgBCgCACAEKAIEEOaBgIAAIgUQ6oGAgAAgBEGAAWoQvoCAgAAgACABQRhqIAUQ64GAgAAgBEHgAGoQvoCAgAAgBCgCUCEBIARBwABqEL6AgIAAIAENAQsgBEHQAGoQiIGAgAALIARBkAFqJICAgIAAC68BAQF/I4CAgIAAQTBrIgMkgICAgAACQAJAAkAgASkDACACWA0AIANBEGogASACEPeBgIAAIANBCGogA0EQahDPhYCAACADQSBqIAMoAgggAygCDBDphICAACADKAIgDQFB7IrAgABB5gAQz4SAgAAACyAAQQA2AgAMAQsgACADKQMgNwIAIABBCGogA0EgakEIaigCADYCACADQRBqEL6AgIAACyADQTBqJICAgIAAC4EDAwF/AX4BfyOAgICAAEHAAGsiAySAgICAAAJAIAEpAwAiBCACWA0AAkACQAJAAkACQCACQgF8IARRDQAgA0EQaiABIAIQ94GAgAAgA0EwaiABEPiBgIAAIANBIGogA0EwakHyjcCAAEEpQZyOwIAAEKmBgIAAIANBCGogA0EQahDPhYCAACADKAIMIQEgAygCCCEFIAMgA0EgahDPhYCAACAFIAEgAygCACADKAIEEOiEgIAADQFB7IrAgABB5gAQz4SAgAAACyADQTBqIAEQ+IGAgAAgAygCMA0BQeyKwIAAQeYAEM+EgIAAAAsgA0EwahDrhICAACADKAIwDQFB7IrAgABB5gAQz4SAgAAACyAAIAMpAzA3AgAgAEEIaiADQTBqQQhqKAIANgIADAELIAAgAykDMDcCACAAQQhqIANBMGpBCGooAgA2AgAgA0EgahC+gICAACADQRBqEL6AgIAACyADQcAAaiSAgICAAA8LQfiMwIAAQRMQz4SAgAAAC4EDAwF/AX4BfyOAgICAAEHAAGsiAySAgICAAAJAIAEpAwAiBCACWA0AAkACQAJAAkACQCACQgF8IARRDQAgA0EQaiABIAIQ9oGAgAAgA0EwaiABEPmBgIAAIANBIGogA0EwakHyjcCAAEEpQZyOwIAAEKmBgIAAIANBCGogA0EQahDPhYCAACADKAIMIQEgAygCCCEFIAMgA0EgahDPhYCAACAFIAEgAygCACADKAIEEOiEgIAADQFB7IrAgABB5gAQz4SAgAAACyADQTBqIAEQ+YGAgAAgAygCMA0BQeyKwIAAQeYAEM+EgIAAAAsgA0EwahDrhICAACADKAIwDQFB7IrAgABB5gAQz4SAgAAACyAAIAMpAzA3AgAgAEEIaiADQTBqQQhqKAIANgIADAELIAAgAykDMDcCACAAQQhqIANBMGpBCGooAgA2AgAgA0EgahC+gICAACADQRBqEL6AgIAACyADQcAAaiSAgICAAA8LQfiMwIAAQRMQz4SAgAAAC+kBAgF/An4jgICAgABBwABrIgQkgICAgAAgBEEQaiABIAIgAxDigYCAACAEQQhqIARBEGoQz4WAgAAgBEEgaiAEKAIIIAQoAgwQ6YSAgAACQAJAAkAgBCgCIA0AQgAhBQwBCyAEQTBqQQhqIARBIGpBCGooAgA2AgAgBCAEKQMgNwMwIAQgBEEwahDPhYCAACAEKAIAIAQoAgQQ5oGAgAAhBiAEQTBqEL6AgIAAQgEhBSAEKAIgDQELIARBIGoQiIGAgAALIARBEGoQvoCAgAAgACAGNwMIIAAgBTcDACAEQcAAaiSAgICAAAtgAQF/I4CAgIAAQRBrIgIkgICAgAAgAiABELmBgIAAAkAgAigCAEEBRw0AQdKLwIAAQR8Qz4SAgAAACyAAIAIpAgQ3AgAgAEEIaiACQQxqKAIANgIAIAJBEGokgICAgAALYAEBfyOAgICAAEEQayICJICAgIAAIAIgARC4gYCAAAJAIAIoAgBBAUcNAEHxi8CAAEEhEM+EgIAAAAsgACACKQIENwIAIABBCGogAkEMaigCADYCACACQRBqJICAgIAAC2gCAX8BfiOAgICAAEEgayIDJICAgIAAIANBCGogASACELWBgIAAAkAgAygCCEEBRw0AQZKMwIAAQSMQz4SAgAAACyADKQMQIQQgACADQRhqKQMANwMIIAAgBDcDACADQSBqJICAgIAAC4gCAgF/A34jgICAgABB0ABrIgMkgICAgAAgA0EwaiACEO2BgIAAIANBGGogA0EwahDPhYCAACADQSBqIAEgAygCGCADKAIcEPGBgIAAAkACQAJAIAMoAiANAEIAIQQMAQsgA0HAAGpBCGogA0EgakEIaigCADYCACADIAMpAyA3A0AgA0EQaiADQcAAahDPhYCAACADIAMoAhAgAygCFBDvgYCAACADQQhqKQMAIQUgAykDACEGIANBwABqEL6AgIAAQgEhBCADKAIgDQELIANBIGoQiIGAgAALIANBMGoQvoCAgAAgAEEQaiAFNwMAIAAgBjcDCCAAIAQ3AwAgA0HQAGokgICAgAALkwEBAX8jgICAgABBIGsiBCSAgICAACAEIAEgAiADEOyBgIAAAkACQAJAIAQpAwCnDQAgAEEANgIADAELIARBEGogAUEYaiAEKQMIEPWBgIAAIAQoAhBFDQEgACAEKQMQNwIAIABBCGogBEEQakEIaigCADYCAAsgBEEgaiSAgICAAA8LQeyKwIAAQeYAEM+EgIAAAAs/AQF/I4CAgIAAQRBrIgIkgICAgAAgAkEIaiABEP6BgIAAIAAgAigCCCACKAIMEIyFgIAAIAJBEGokgICAgAALzgICAn8DfiOAgICAAEHwAGsiBCSAgICAACAEQcAAaiACEO2BgIAAIARBKGogBEHAAGoQz4WAgAAgBCgCLCECIAQoAighBSAEQdAAaiADEO6BgIAAIARBIGogBEHQAGoQz4WAgAAgBEEwaiABIAUgAiAEKAIgIAQoAiQQ4YGAgAACQAJAAkAgBCgCMA0AQgAhBgwBCyAEQeAAakEIaiAEQTBqQQhqKAIANgIAIAQgBCkDMDcDYCAEQRhqIARB4ABqEM+FgIAAIARBCGogBCgCGCAEKAIcEO+BgIAAIARBCGpBCGopAwAhByAEKQMIIQggBEHgAGoQvoCAgABCASEGIAQoAjANAQsgBEEwahCIgYCAAAsgBEHQAGoQvoCAgAAgBEHAAGoQvoCAgAAgAEEQaiAHNwMAIAAgCDcDCCAAIAY3AwAgBEHwAGokgICAgAALiAICAX8DfiOAgICAAEHQAGsiAySAgICAACADQTBqIAIQ7YGAgAAgA0EYaiADQTBqEM+FgIAAIANBIGogASADKAIYIAMoAhwQ6IGAgAACQAJAAkAgAygCIA0AQgAhBAwBCyADQcAAakEIaiADQSBqQQhqKAIANgIAIAMgAykDIDcDQCADQRBqIANBwABqEM+FgIAAIAMgAygCECADKAIUEO+BgIAAIANBCGopAwAhBSADKQMAIQYgA0HAAGoQvoCAgABCASEEIAMoAiANAQsgA0EgahCIgYCAAAsgA0EwahC+gICAACAAQRBqIAU3AwAgACAGNwMIIAAgBDcDACADQdAAaiSAgICAAAuvAQEBfyOAgICAAEEwayIDJICAgIAAAkACQAJAIAEpAwAgAlgNACADQRBqIAEgAhD2gYCAACADQQhqIANBEGoQz4WAgAAgA0EgaiADKAIIIAMoAgwQ6YSAgAAgAygCIA0BQeyKwIAAQeYAEM+EgIAAAAsgAEEANgIADAELIAAgAykDIDcCACAAQQhqIANBIGpBCGooAgA2AgAgA0EQahC+gICAAAsgA0EwaiSAgICAAAuyAQEDfyOAgICAAEEgayIDJICAgIAAAkAgAUEQaigCACIEQQhqIgUgBEkNACADQQhqIAUQ6oCAgAAgAyABQQhqEM+FgIAAIANBCGogAygCACADKAIEEJiFgIAAIAMgAjcDGCADQQhqIANBGGpBCBCYhYCAACAAQQhqIANBCGpBCGooAgA2AgAgACADKQMINwIAIANBIGokgICAgAAPC0GwgsCAAEEcQayOwIAAEI2GgIAAAAuyAQEDfyOAgICAAEEgayIDJICAgIAAAkAgAUEQaigCACIEQQhqIgUgBEkNACADQQhqIAUQ6oCAgAAgAyABQQhqEM+FgIAAIANBCGogAygCACADKAIEEJiFgIAAIAMgAjcDGCADQQhqIANBGGpBCBCYhYCAACAAQQhqIANBCGpBCGooAgA2AgAgACADKQMINwIAIANBIGokgICAgAAPC0GwgsCAAEEcQayOwIAAEI2GgIAAAAuEAgIBfwJ+I4CAgIAAQTBrIgIkgICAgAACQAJAAkACQAJAIAEpAwAiA0IAUg0AIABBADYCAAwBCyACQRBqIAEgA0J/fBD3gYCAACABKQMAIgNCf3wiBCADVg0BIAEgBDcDACACQQhqIAJBEGoQz4WAgAAgAigCCCACKAIMEOqEgIAARQ0CIAJBIGoQ64SAgAAgAigCIEUNAyAAIAIpAyA3AgAgAEEIaiACQSBqQQhqKAIANgIAIAJBEGoQvoCAgAALIAJBMGokgICAgAAPC0HAgcCAAEEhQbyOwIAAEI2GgIAAAAtB7IrAgABB5gAQz4SAgAAAC0HsisCAAEHmABDPhICAAAALhAICAX8CfiOAgICAAEEwayICJICAgIAAAkACQAJAAkACQCABKQMAIgNCAFINACAAQQA2AgAMAQsgAkEQaiABIANCf3wQ9oGAgAAgASkDACIDQn98IgQgA1YNASABIAQ3AwAgAkEIaiACQRBqEM+FgIAAIAIoAgggAigCDBDqhICAAEUNAiACQSBqEOuEgIAAIAIoAiBFDQMgACACKQMgNwIAIABBCGogAkEgakEIaigCADYCACACQRBqEL6AgIAACyACQTBqJICAgIAADwtBwIHAgABBIUG8jsCAABCNhoCAAAALQeyKwIAAQeYAEM+EgIAAAAtB7IrAgABB5gAQz4SAgAAACwQAQQALBABBAAv9AQEBfyOAgICAAEGAAWsiASSAgICAACABQRBqQdyOwIAAQQUQ6YSAgAACQAJAAkACQCABKAIQDQAgAEEANgIIDAELIAFBIGpBCGogAUEQakEIaigCADYCACABIAEpAxA3AyAgAUEIaiABQSBqEM+FgIAAIAFBMGogASgCCCABKAIMELOBgIAAIAEoAjBBAUYNAiAAIAFBMGpBCGpBwAAQ84aAgAAaIAFBIGoQvoCAgAAgASgCEA0BCyABQRBqEIiBgIAACyABQYABaiSAgICAAA8LIAEgASkCNDcDeEHhjsCAAEEmIAFB+ABqQaiFwIAAQeyPwIAAEKqGgIAAAAuyAQEBfyOAgICAAEEwayIBJICAgIAAIAFBIGogABC2gYCAAAJAIAEoAiBBAUcNACABIAEpAiQ3AxBB/I/AgABBJCABQRBqQaiFwIAAQaCQwIAAEKqGgIAAAAsgAUEYaiABQSxqKAIANgIAIAEgASkCJDcDECABQQhqIAFBEGoQz4WAgABB3I7AgABBBSABKAIIIAEoAgwQ6ISAgAAaIAFBEGoQvoCAgAAgAUEwaiSAgICAAAtKAQJ/I4CAgIAAQRBrIgIkgICAgAAgAkEIaiABEM+FgIAAIAIoAgwhASAAIAIoAggiAzYCACAAIAMgAWo2AgQgAkEQaiSAgICAAAu6AgIBfwF+I4CAgIAAQSBrIgMkgICAgAAgA0EYaiABQTBqIAIQuoGAgAAgAyADKQMYIgQ3AwgCQAJAAkACQCAEp0H/AXFBA0cNACADQQhqEOWAgIAAIANBGGogASACEICCgIAAIAMgAykDGCIENwMIIASnQf8BcUEDRw0BIANBCGoQ5YCAgAAgA0EYaiABQRhqIAIQgYKAgAAgAyADKQMYIgQ3AwggBKdB/wFxQQNHDQIgA0EIahDlgICAACAAQQM6AAAMAwsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgAMAgsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgAMAQsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgALIANBIGokgICAgAAL3wECAX8BfiOAgICAAEEgayIDJICAgIAAIANBGGogASACEK2BgIAAIAMgAykDGCIENwMIAkACQAJAIASnQf8BcUEDRw0AIANBCGoQ5YCAgAAgA0EYaiABQQhqIAIQuoGAgAAgAyADKQMYIgQ3AwggBKdB/wFxQQNHDQEgA0EIahDlgICAACAAQQM6AAAMAgsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgAMAQsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgALIANBIGokgICAgAAL3wECAX8BfiOAgICAAEEgayIDJICAgIAAIANBGGogASACEK2BgIAAIAMgAykDGCIENwMIAkACQAJAIASnQf8BcUEDRw0AIANBCGoQ5YCAgAAgA0EYaiABQQhqIAIQuoGAgAAgAyADKQMYIgQ3AwggBKdB/wFxQQNHDQEgA0EIahDlgICAACAAQQM6AAAMAgsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgAMAQsgAyAENwMYIANBEGogA0EYahDAhYCAACAAIAMpAxA3AgALIANBIGokgICAgAALlAUBA38jgICAgABB0AFrIgIkgICAgAAgAkHYAGogARDHgYCAAAJAAkACQAJAAkACQAJAIAIoAlhBAUYNACACQcgAakEIaiACQeQAaigCADYCACACIAIpAlw3A0ggAkGAAWogARCDgoCAACACQdgAakEEciEDIAIoAoABQQFGDQEgAkHoAGpBEGoiBCACQYABakEYaikDADcDACACQegAakEIaiACQYABakEQaikDADcDACACIAIpA4gBNwNoIAJBoAFqIAEQhIKAgAAgAigCoAFBAUcNAyACIAIpAqQBNwPIASACQcABaiACQcgBahDAhYCAACAAIAIpA8ABNwIEIABBATYCACACQfAAahC+gICAACACQcgAahC+gICAACACKAKAAQ0CDAQLIAIgAikCXDcDoAEgAkGAAWogAkGgAWoQwIWAgAAgACACKQOAATcCBCAAQQE2AgAMBQsgAiACKQKEATcDoAEgAkHIAWogAkGgAWoQwIWAgAAgACACKQPIATcCBCAAQQE2AgAgAkHIAGoQvoCAgAAgAigCWEUNBAwDCyACQYABakEEchCNgYCAAAwBCyACQcAAaiACQcgAakEIaigCADYCACACQQhqQQhqIAJB6ABqQQhqKQMANwMAIAJBCGpBEGogBCkDADcDACACQShqIAJBoAFqQRBqKQMANwMAIAJBMGogAkGgAWpBGGopAwA3AwAgAiACKQNINwM4IAIgAikDaDcDCCACIAIpA6gBNwMgIABBCGogAkEIakHAABDzhoCAABogAEEANgIAAkAgAigCgAFFDQAgAkGAAWpBBHIQjYGAgAALIAIoAlgNAQwCCyACKAJYRQ0BCyADEI2BgIAACyACQdABaiSAgICAAAv4AQIBfwF+I4CAgIAAQTBrIgIkgICAgAAgAiABEK6BgIAAAkACQCACKAIAQQFGDQAgAikDCCEDIAJBEGogARDHgYCAAAJAAkAgAigCEEEBRw0AIAIgAikCFDcDKCACQSBqIAJBKGoQwIWAgAAgACACKQMgNwIEIABBATYCAAwBCyAAQQA2AgAgAEEIaiADNwMAIABBEGogAkEQakEEciIBKQIANwIAIABBGGogAUEIaigCADYCAAsgAhCQgYCAAAwBCyACIAIpAgQ3AxAgAkEoaiACQRBqEMCFgIAAIAAgAikDKDcCBCAAQQE2AgALIAJBMGokgICAgAAL+AECAX8BfiOAgICAAEEwayICJICAgIAAIAIgARCugYCAAAJAAkAgAigCAEEBRg0AIAIpAwghAyACQRBqIAEQx4GAgAACQAJAIAIoAhBBAUcNACACIAIpAhQ3AyggAkEgaiACQShqEMCFgIAAIAAgAikDIDcCBCAAQQE2AgAMAQsgAEEANgIAIABBCGogAzcDACAAQRBqIAJBEGpBBHIiASkCADcCACAAQRhqIAFBCGooAgA2AgALIAIQkIGAgAAMAQsgAiACKQIENwMQIAJBKGogAkEQahDAhYCAACAAIAIpAyg3AgQgAEEBNgIACyACQTBqJICAgIAAC+IRBQN/AX4BfwJ+A38jgICAgABBoAFrIgYkgICAgAAgBkEIaiABEM2AgIAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAGLQAIQQFGDQAgBi0ACUEBRw0BIAYtAAohByAGQQhqEMiAgIAAAkACQCAHQdsARg0AIAdB+wBGDQEgASAGQZgBakHAkMCAABDggICAACEHIAZBATYCCCAGIAc2AgwMEAsgAS0AGEF/aiIHQf8BcSIIIAdHDQQgASAHOgAYAkAgCA0AIAZBiAFqIQcMEQsgARDQgICAACAGIAE2AnAgBkEBOgB0IAZBwABqIAZB8ABqEN2BgIAAAkACQCAGKAJAQQFGDQAgBkGIAWpBCGoiByAGQcwAaigCADYCACAGIAYpAkQiCTcDiAEgCacNAUEAQdSbwIAAQYybwIAAELuBgIAAIQogBkGIAWoQlYGAgAAMDwsgBigCRCEKDA4LIAZB+ABqQQhqIAcoAgA2AgAgBiAGKQOIATcDeCAGQcAAaiAGQfAAahDbgYCAACAGKAJAQQFHDQsgBigCRCEKDAwLIAEtABhBf2oiB0H/AXEiCCAHRw0CIAEgBzoAGAJAIAgNACAGQcAAaiEHDBALIAEQ0ICAgAAgBkEBOgB0IAYgATYCcCAGQQA2AnggBkHAAGpBBHIhCEIAIQkgBkHQAGohCgJAA0AgBkGIAWogBkHwAGoQyoGAgAACQAJAIAYtAIgBQQFGDQAgBi0AiQEiB0EDRg0DAkAgB0EBSw0AAkACQCAHDgIAAQALAkAgBigCeA0AIAZBwABqIAZB8ABqENSBgIAAIAYoAkBBAUcNBAwNC0Hcm8CAAEEWELyBgIAAIQcMDQsgCUIBUQ0JIAZBwABqIAZB8ABqENeBgIAAIAYoAkBBAUYNCyAKKQMAIQsgBikDSCEMIAZBwABqEJaBgIAAQgEhCQwDCyAGIAZB8ABqENmBgIAAIgc2AkAgBw0LIAZBwABqEJOBgIAADAILIAYoAowBIQcMCgsgBkH4AGoQlYGAgAAgBkH4AGpBCGogCEEIaigCADYCACAGIAgpAgA3A3gMAAsLIAYoAngNBUHcm8CAAEEWEPOAgIAAIQcMBwsgACAGKAIMNgIEIABBATYCAAwPCyAGQQU2AkAgASAGQcAAahDFgICAACEBIABBATYCACAAIAE2AgQgBkEIahDIgICAAAwOC0HAgcCAAEEhQdCQwIAAEI2GgIAAAAtBwIHAgABBIUHQkMCAABCNhoCAAAALQeaTwIAAQQYQvIGAgAAhBwwCCyAGQYgBakEIaiIHIAZB+ABqQQhqKAIANgIAIAYgBikDeDcDiAECQCAJQgFSDQAgBkEwakEIaiAHKAIANgIAIAYgBikDiAE3AzBBACEKDAQLIAZBwABqQeaTwIAAQQYQw4GAgAACQCAGKAJAQQFGDQAgBkHQAGopAwAhCyAGKQNIIQwgBkHAAGoQloGAgAAgBkEwakEIaiAGQYgBakEIaigCADYCACAGIAYpA4gBNwMwQQAhCgJAIAYoAngNACAGQfgAahCVgYCAAAsMBAsgBigCRCEHIAZBiAFqEL6AgIAAQQAhCAwCCyAGKAJEIQcLQQEhCAsCQAJAAkAgBigCeEUNAEEBIQogCA0BDAILIAZB+ABqEJWBgIAAQQEhCgwBCyAGQfgAahC+gICAAAsLAkACQAJAIAEtABhBAWoiCEH/AXEgCEcNACABIAg6ABggARDigICAACEIIAZB0ABqIAs3AwAgBkHAAGpBCGoiDSAMNwMAIAZB2ABqIg4gBikDMDcDACAGQeAAaiAGQTBqQQhqKAIANgIAIAYgBzYCRCAGIAo2AkAgBiAINgJoIAZB6ABqIQ8CQCAKDQACQCAIDQAgBkEoaiANQRhqKQMANwMAIAZBCGpBGGogDUEQaikDADcDACAGQQhqQRBqIA1BCGopAwA3AwAgBkEANgIIIAYgDSkDADcDEAwECyAGQQE2AgggBiAINgIMIA4QvoCAgABBACEKIAYoAmghCAwCC0EBIQogBkEBNgIIIAYgBzYCDAwBC0GwgsCAAEEcQeCQwIAAEI2GgIAAAAsgCEUNACAKRQ0FIA8Q2ICAgAAMBQsgDxC8gICAAAwECwJAIAYpA0hCAVINACAGQdgAaikDACEJIAZB0ABqKQMAIQwgBkHAAGoQl4GAgAAgBkEwakEIaiAGQfgAakEIaigCADYCACAGIAYpA3g3AzBBACEIDAMLQQFB1JvAgABBjJvAgAAQu4GAgAAhCiAGQcAAahCXgYCAAAsgBkH4AGoQvoCAgAALQQEhCAsCQCABLQAYQQFqIgdB/wFxIAdGDQBBsILAgABBHEHgkMCAABCNhoCAAAALIAEgBzoAGCABEOOAgIAAIQcgBkHQAGogCTcDACAGQcAAakEIaiINIAw3AwAgBkHYAGoiDiAGKQMwNwMAIAZB4ABqIAZBMGpBCGooAgA2AgAgBiAKNgJEIAYgCDYCQCAGIAc2AmggBkHoAGohDwJAAkACQCAIDQACQCAHDQAgBkEoaiANQRhqKQMANwMAIAZBCGpBGGogDUEQaikDADcDACAGQQhqQRBqIA1BCGopAwA3AwAgBkEANgIIIAYgDSkDADcDEAwDCyAGQQE2AgggBiAHNgIMIA4QvoCAgABBACEIIAYoAmghBwwBC0EBIQggBkEBNgIIIAYgCjYCDAsgB0UNACAIRQ0BIA8Q2ICAgAAMAQsgDxC8gICAAAsCQAJAIAYoAghBAUcNACAAIAYoAgwgARDKgICAADYCBCAAQQE2AgAgBigCCA0BIAZBIGoQvoCAgAAMAQsgAEEANgIAIABBCGogBikDEDcDACAAQSBqIAZBCGpBIGopAwA3AwAgAEEYaiAGQQhqQRhqKQMANwMAIABBEGogBkEIakEQaikDADcDAAsgBkGgAWokgICAgAAPCyAHQRU2AgAgASAHEMWAgIAAIQEgAEEBNgIAIAAgATYCBAsgBkGgAWokgICAgAALvAsDA38BfgJ/I4CAgIAAQdAAayIGJICAgIAAIAYgARDNgICAAAJAAkACQAJAAkACQAJAAkACQAJAAkAgBi0AAEEBRg0AIAYtAAFBAUcNASAGLQACIQcgBhDIgICAAAJAAkAgB0HbAEYNACAHQfsARg0BIAEgBkHIAGpB8JDAgAAQ4ICAgAAhByAGQQE2AgAgBiAHNgIEDAoLIAEtABhBf2oiB0H/AXEiCCAHRw0EIAEgBzoAGAJAIAgNACAGQTBqIQcMCwsgARDQgICAACAGIAE2AkBBASEIIAZBAToARCAGQRBqIAZBwABqEN2BgIAAIAYoAhBBAUYNBSAGQThqIAZBHGooAgA2AgAgBiAGKQIUIgk3AzACQCAJpyIKDQBBAEGEm8CAAEGMm8CAABC7gYCAACEKIAZBMGoQlYGAgABBASEIDAkLQQAhCCAGKQI0IQkMCAsgAS0AGEF/aiIHQf8BcSIIIAdHDQIgASAHOgAYAkAgCA0AIAZBEGohBwwKCyABENCAgIAAIAZBAToALCAGIAE2AiggBkEANgIwIAZBEGpBBHIhCAJAA0AgBkHAAGogBkEoahDOgYCAAAJAAkAgBi0AQEEBRg0AIAYtAEEiB0ECRw0BIAYoAjAiBw0DQdyTwIAAQQoQ84CAgAAhBwwJCyAGKAJEIQcMCAsCQCAHQQFxRQ0AIAYgBkEoahDZgYCAACIHNgIQIAcNCCAGQRBqEJOBgIAADAELAkACQCAGKAIwDQAgBkEQaiAGQShqENSBgIAAIAYoAhBBAUcNASAGKAIUIQcMCQtB3JPAgABBChC8gYCAACEHDAgLIAZBMGoQlYGAgAAgBkEwakEIaiAIQQhqKAIANgIAIAYgCCkCADcDMAwACwtBACEKIAYpAjQhCQwGCyAAIAYoAgQ2AgQgAEEBNgIADAkLIAZBBTYCECABIAZBEGoQxYCAgAAhASAAQQE2AgAgACABNgIEIAYQyICAgAAMCAtBwIHAgABBIUHQkMCAABCNhoCAAAALQcCBwIAAQSFB0JDAgAAQjYaAgAAACyAGKAIUIQoMAgsgBkEwahCVgYCAAEEBIQoLAkACQAJAIAEtABhBAWoiCEH/AXEgCEcNACABIAg6ABggARDigICAACEIIAZBGGogCTcDACAGIAg2AiAgBiAHNgIUIAYgCjYCECAGQSBqIQsCQCAKDQACQCAIDQAgBkEMaiAGQRBqQQxqKAIANgIAIAYgBikCFDcCBCAGQQA2AgAMBAsgBkEBNgIAIAYgCDYCBCAGQRBqQQRyEL6AgIAAQQAhCiAGKAIgIQgMAgtBASEKIAZBATYCACAGIAc2AgQMAQtBsILAgABBHEHgkMCAABCNhoCAAAALIAhFDQAgCkUNAiALENiAgIAADAILIAsQvICAgAAMAQsCQAJAAkAgAS0AGEEBaiIHQf8BcSAHRw0AIAEgBzoAGCABEOOAgIAAIQcgBkEYaiAJNwMAIAYgBzYCICAGIAo2AhQgBiAINgIQIAZBIGohCwJAIAgNAAJAIAcNACAGQQxqIAZBEGpBDGooAgA2AgAgBiAGKQIUNwIEIAZBADYCAAwECyAGQQE2AgAgBiAHNgIEIAZBEGpBBHIQvoCAgABBACEIIAYoAiAhBwwCC0EBIQggBkEBNgIAIAYgCjYCBAwBC0GwgsCAAEEcQeCQwIAAEI2GgIAAAAsgB0UNACAIRQ0BIAsQ2ICAgAAMAQsgCxC8gICAAAsCQAJAIAYoAgBBAUcNACAAIAYoAgQgARDKgICAADYCBCAAQQE2AgAgBigCAA0BIAZBBHIQvoCAgAAMAQsgAEEANgIAIAAgBkEEciIBKQIANwIEIABBDGogAUEIaigCADYCAAsgBkHQAGokgICAgAAPCyAHQRU2AgAgASAHEMWAgIAAIQEgAEEBNgIAIAAgATYCBAsgBkHQAGokgICAgAALoQoEA38CfgJ/AX4jgICAgABBwABrIgYkgICAgAAgBkEwaiABEM2AgIAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgBi0AMEEBRg0AIAYtADFBAUcNASAGLQAyIQcgBkEwahDIgICAAAJAAkAgB0HbAEYNACAHQfsARg0BIAEgBkE4akGAkcCAABDggICAACEHDBMLIAEtABhBf2oiB0H/AXEiCCAHRw0GIAEgBzoAGCAIRQ0IIAEQ0ICAgAAgBiABNgIwQQEhCCAGQQE6ADQgBkEIaiAGQTBqENuBgIAAAkACQAJAAkAgBigCCEEBRg0AIAYpAxBCAVINASAGQSBqKQMAIQkgBkEYaikDACEKIAZBCGoQl4GAgABBACEIDAMLIAYoAgwhBwwBC0EAQYSbwIAAQYybwIAAELuBgIAAIQcgBkEIahCXgYCAAEEBIQgLCyABLQAYQQFqIgtB/wFxIAtHDQcgASALOgAYIAEQ44CAgAAhCyAGQRhqIAk3AwAgBkEQaiAKNwMAIAYgCzYCICAGIAc2AgwgBiAINgIIIAZBIGohDCAIDQRBASEIIAtFDQMgCyEHDBELIAEtABhBf2oiB0H/AXEiCCAHRw0EIAEgBzoAGAJAIAhFDQAgARDQgICAACAGQQE6ACwgBiABNgIoQgAhDSAGQRhqIQgCQAJAA0AgBkEwaiAGQShqEMiBgIAAIAYtADBBAUYNASAGLQAxIgdBAkYNAgJAIAdBAXFFDQAgBiAGQShqENmBgIAAIgc2AgggBw0TIAZBCGoQk4GAgAAMAQsgDUIBUQ0MIAZBCGogBkEoahDXgYCAACAGKAIIQQFGDREgCCkDACEJIAYpAxAhCiAGQQhqEJaBgIAAQgEhDQwACwsgBigCNCEHDBALQQAhCCANQgFSDQ0MEAsgBkEVNgIIIAEgBkEIahDFgICAACEBIABBATYCACAAIAE2AgQMCwsgACAGKAI0NgIEIABBATYCAAwKCyAGQQU2AgggASAGQQhqEMWAgIAAIQEgAEEBNgIAIAAgATYCBCAGQTBqEMiAgIAADAkLQQAhCAwGCyALDQZBASEIDAULQcCBwIAAQSFB0JDAgAAQjYaAgAAAC0HAgcCAAEEhQdCQwIAAEI2GgIAAAAtBsILAgABBHEHgkMCAABCNhoCAAAALIAZBFTYCCCABIAZBCGoQxYCAgAAhASAAQQE2AgAgACABNgIEDAMLQeaTwIAAQQYQvIGAgAAhBwwFCyAMELyAgIAADAYLIAwQ2ICAgABBASEIDAULIAZBwABqJICAgIAADwsgBkEIakHmk8CAAEEGEMOBgIAAIAYoAghBAUYNACAGQRhqKQMAIQkgBikDECEKIAZBCGoQloGAgAAMAgsgBigCDCEHC0EBIQgLAkAgAS0AGEEBaiILQf8BcSALRg0AQbCCwIAAQRxB4JDAgAAQjYaAgAAACyABIAs6ABggARDigICAACELIAZBGGogCTcDACAGQRBqIAo3AwAgBiALNgIgIAYgBzYCDCAGIAg2AgggBkEgaiEMAkACQAJAIAgNAEEBIQgCQCALRQ0AIAshBwwEC0EAIQgMAQsgCw0BQQEhCAsgDBC8gICAAAwBCyAMENiAgIAAQQEhCAsgCA0AIABBEGogCTcDACAAQQhqIAo3AwBBACEBDAELIAAgByABEMqAgIAANgIEQQEhAQsgACABNgIAIAZBwABqJICAgIAAC4UMAwN/AX4CfyOAgICAAEHQAGsiBiSAgICAACAGIAEQzYCAgAACQAJAAkACQAJAAkACQAJAAkACQAJAIAYtAABBAUYNACAGLQABQQFHDQEgBi0AAiEHIAYQyICAgAACQAJAIAdB2wBGDQAgB0H7AEYNASABIAZByABqQZCRwIAAEOCAgIAAIQcgBkEBNgIAIAYgBzYCBAwKCyABLQAYQX9qIgdB/wFxIgggB0cNBCABIAc6ABgCQCAIDQAgBkEwaiEHDAsLIAEQ0ICAgAAgBiABNgJAQQEhCCAGQQE6AEQgBkEQaiAGQcAAahDcgYCAACAGKAIQQQFGDQUgBkE4aiAGQRxqKAIANgIAIAYgBikCFCIJNwMwAkAgCaciCg0AQQBBhJvAgABBjJvAgAAQu4GAgAAhCiAGQTBqEJ2BgIAAQQEhCAwJC0EAIQggBikCNCEJDAgLIAEtABhBf2oiB0H/AXEiCCAHRw0CIAEgBzoAGAJAIAgNACAGQRBqIQcMCgsgARDQgICAACAGQQE6ACwgBiABNgIoIAZBADYCMCAGQRBqQQRyIQgCQAJAA0AgBkHAAGogBkEoahDMgYCAAAJAAkACQAJAIAYtAEBBAUYNACAGLQBBIgdBAkcNASAGKAIwIgdFDQJBACEKIAYpAjQhCQwNCyAGKAJEIQcMCwsCQCAHQQFxRQ0AIAYgBkEoahDZgYCAACIHNgIQIAcNCyAGQRBqEJOBgIAADAMLAkAgBigCMA0AIAZBEGogBkEoahDVgYCAACAGKAIQQQFHDQIgBigCFCEHDAsLQd+awIAAQQoQvIGAgAAhBwwKCyAGQRBqQd+awIAAQQoQxIGAgAAgBigCFCEHIAYoAhBBAUYNCSAGQRhqKQMAIQkgBigCMEUNAiAGQTBqEL6AgIAADAMLIAZBMGoQnYGAgAAgBkEwakEIaiAIQQhqKAIANgIAIAYgCCkCADcDMAwACwsgBkEwahCdgYCAAAtBACEKDAYLIAAgBigCBDYCBCAAQQE2AgAMCQsgBkEFNgIQIAEgBkEQahDFgICAACEBIABBATYCACAAIAE2AgQgBhDIgICAAAwIC0HAgcCAAEEhQdCQwIAAEI2GgIAAAAtBwIHAgABBIUHQkMCAABCNhoCAAAALIAYoAhQhCgwCCyAGQTBqEJ2BgIAAQQEhCgsCQAJAAkAgAS0AGEEBaiIIQf8BcSAIRw0AIAEgCDoAGCABEOKAgIAAIQggBkEYaiAJNwMAIAYgCDYCICAGIAc2AhQgBiAKNgIQIAZBIGohCwJAIAoNAAJAIAgNACAGQQxqIAZBEGpBDGooAgA2AgAgBiAGKQIUNwIEIAZBADYCAAwECyAGQQE2AgAgBiAINgIEIAZBEGpBBHIQvoCAgABBACEKIAYoAiAhCAwCC0EBIQogBkEBNgIAIAYgBzYCBAwBC0GwgsCAAEEcQeCQwIAAEI2GgIAAAAsgCEUNACAKRQ0CIAsQ2ICAgAAMAgsgCxC8gICAAAwBCwJAAkACQCABLQAYQQFqIgdB/wFxIAdHDQAgASAHOgAYIAEQ44CAgAAhByAGQRhqIAk3AwAgBiAHNgIgIAYgCjYCFCAGIAg2AhAgBkEgaiELAkAgCA0AAkAgBw0AIAZBDGogBkEQakEMaigCADYCACAGIAYpAhQ3AgQgBkEANgIADAQLIAZBATYCACAGIAc2AgQgBkEQakEEchC+gICAAEEAIQggBigCICEHDAILQQEhCCAGQQE2AgAgBiAKNgIEDAELQbCCwIAAQRxB4JDAgAAQjYaAgAAACyAHRQ0AIAhFDQEgCxDYgICAAAwBCyALELyAgIAACwJAAkAgBigCAEEBRw0AIAAgBigCBCABEMqAgIAANgIEIABBATYCACAGKAIADQEgBkEEchC+gICAAAwBCyAAQQA2AgAgACAGQQRyIgEpAgA3AgQgAEEMaiABQQhqKAIANgIACyAGQdAAaiSAgICAAA8LIAdBFTYCACABIAcQxYCAgAAhASAAQQE2AgAgACABNgIECyAGQdAAaiSAgICAAAuREwMDfwF+A38jgICAgABB4AFrIgYkgICAgAAgBkEIaiABEM2AgIAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAYtAAhBAUYNACAGLQAJQQFHDQEgBi0ACiEHIAZBCGoQyICAgAACQAJAIAdB2wBGDQAgB0H7AEYNASABIAZB2AFqQaCRwIAAEOCAgIAAIQcgBkEBNgIIIAYgBzYCDAwQCyABLQAYQX9qIgdB/wFxIgggB0cNBCABIAc6ABgCQCAIDQAgBkEoaiEHDA4LIAEQ0ICAgAAgBiABNgKAASAGQQE6AIQBIAZBwABqIAZBgAFqEN2BgIAAIAYoAkBBAUYNBSAGQbABakEIaiIHIAZBzABqKAIANgIAIAYgBikCRCIJNwOwAQJAIAmnDQBBAEHUm8CAAEGMm8CAABC7gYCAACEKIAZBsAFqEJWBgIAADAwLIAZBkAFqQQhqIAcoAgA2AgAgBiAGKQOwATcDkAEgBkHAAGogBkGAAWoQ3IGAgAACQAJAAkAgBigCQEEBRw0AIAYoAkQhCgwBCyAGQbABakEIaiIHIAZBzABqKAIANgIAIAYgBikCRCIJNwOwASAJpw0BQQFB1JvAgABBjJvAgAAQu4GAgAAhCiAGQbABahCdgYCAAAsgBkGQAWoQvoCAgAAMDAsgBkGgAWpBCGogBygCACIHNgIAIAZBKGpBCGogBikDsAE3AwAgBkE4aiAHNgIAIAYgBikClAE3AyggBigCkAEhCkEAIQcMDAsgAS0AGEF/aiIHQf8BcSIIIAdHDQIgASAHOgAYAkAgCA0AIAZBwABqIQcMDQsgARDQgICAACAGQQE6AHwgBiABNgJ4IAZBADYCgAEgBkEANgKQASAGQcAAakEEciEIAkADQCAGQbABaiAGQfgAahDSgYCAAAJAAkAgBi0AsAFBAUYNACAGLQCxASIHQQNGDQMCQCAHQQFLDQACQAJAIAcOAgABAAsCQCAGKAKAAQ0AIAZBwABqIAZB+ABqENSBgIAAIAYoAkBBAUcNBAwNC0Gcm8CAAEEOELyBgIAAIQcMDQsCQCAGKAKQAQ0AIAZBwABqIAZB+ABqENWBgIAAIAYoAkBBAUYNDCAGQZABahCdgYCAACAGQZABakEIaiAIQQhqKAIANgIAIAYgCCkCADcDkAEMBAtBqpvAgABBDhC8gYCAACEHDAwLIAYgBkH4AGoQ2YGAgAAiBzYCQCAHDQsgBkHAAGoQk4GAgAAMAgsgBigCtAEhBwwKCyAGQYABahCVgYCAACAGQYABakEIaiAIQQhqKAIANgIAIAYgCCkCADcDgAEMAAsLIAYoAoABDQVBnJvAgABBDhDzgICAACEHDAcLIAAgBigCDDYCBCAAQQE2AgAMDAsgBkEFNgJAIAEgBkHAAGoQxYCAgAAhASAAQQE2AgAgACABNgIEIAZBCGoQyICAgAAMCwtBwIHAgABBIUHQkMCAABCNhoCAAAALQcCBwIAAQSFB0JDAgAAQjYaAgAAACyAGKAJEIQoMBQsgBkGgAWpBCGogBkGAAWpBCGooAgA2AgAgBiAGKQOAATcDoAECQAJAAkACQAJAIAYoApABIggNACAGQcAAakGqm8CAAEEOEMSBgIAAQQEhCiAGKAJAQQFGDQIgBkG4AWogBkHMAGooAgA2AgAgBiAGKQJENwOwASAGKAKQASEIDAELIAZBsAFqQQhqIAZBkAFqQQhqKAIANgIAIAYgBikDkAE3A7ABQQAhCgsgBkEoakEIaiAGKQOwATcDACAGQThqIAZBsAFqQQhqKAIANgIAIAYgBikCpAE3AyggBigCoAEhByAIRQ0BIApFDQIgBkGQAWoQvoCAgAAMAgsgBigCRCEHIAZBoAFqEL6AgIAAQQAhCgwECyAGQZABahCdgYCAAAtBACEIIAYoAoABDQMgBkGAAWoQlYGAgAAMAwsgBigCRCEHC0EBIQoLIAZBkAFqEJ2BgIAAAkAgBigCgAFFDQBBASEIIApFDQEgBkGAAWoQvoCAgAAMAQsgBkGAAWoQlYGAgABBASEICwJAAkAgAS0AGEEBaiIKQf8BcSAKRw0AIAEgCjoAGCABEOKAgIAAIQogBkHAAGpBCGogBikDKDcDACAGQcAAakEQaiAGQShqQQhqKQMANwMAIAZB2ABqIAZBKGpBEGooAgA2AgAgBiAHNgJEIAYgCDYCQCAGIAo2AlwCQAJAAkACQCAIDQAgCkUNAkEAIQtBASEMDAELQQEhC0EAIQwgByEKCyAGQQE2AgggBiAKNgIMIAgNASAGQcAAakEEchDBgICAAAwDCyAGQQhqQRRqIAZBwABqQRRqKQIANwIAIAZBCGpBDGogBkHAAGpBDGopAgA3AgAgBiAGKQJENwIMIAZBADYCCEEBIQsMAgsgDEUNASAGQcAAakEEchDYgICAAAwBC0GwgsCAAEEcQeCQwIAAEI2GgIAAAAsgBkHcAGohBwJAIAYoAlwNACAHELyAgIAADAULIAtFDQQgBxDYgICAAAwEC0EBIQcLAkACQCABLQAYQQFqIghB/wFxIAhHDQAgASAIOgAYIAEQ44CAgAAhCCAGQcAAakEIaiAGKQMoNwMAIAZBwABqQRBqIAZBKGpBCGopAwA3AwAgBkHYAGogBkEoakEQaigCADYCACAGIAo2AkQgBiAHNgJAIAYgCDYCXAJAAkACQAJAIAcNACAIRQ0CQQAhC0EBIQwMAQtBASELQQAhDCAKIQgLIAZBATYCCCAGIAg2AgwgBw0BIAZBwABqQQRyEMGAgIAADAMLIAZBCGpBFGogBkHAAGpBFGopAgA3AgAgBkEIakEMaiAGQcAAakEMaikCADcCACAGIAYpAkQ3AgwgBkEANgIIQQEhCwwCCyAMRQ0BIAZBwABqQQRyENiAgIAADAELQbCCwIAAQRxB4JDAgAAQjYaAgAAACyAGQdwAaiEHAkAgBigCXA0AIAcQvICAgAAMAwsgC0UNAiAHENiAgIAADAILIAdBFTYCACABIAcQxYCAgAAhASAAQQE2AgAgACABNgIECyAGQeABaiSAgICAAA8LAkACQCAGKAIIQQFHDQAgACAGKAIMIAEQyoCAgAA2AgQgAEEBNgIAIAYoAggNASAGQQhqQQRyEMGAgIAADAELIABBADYCACAAIAZBCGpBBHIiASkCADcCBCAAQRRqIAFBEGopAgA3AgAgAEEMaiABQQhqKQIANwIACyAGQeABaiSAgICAAAuREwMDfwF+A38jgICAgABB4AFrIgYkgICAgAAgBkEIaiABEM2AgIAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAYtAAhBAUYNACAGLQAJQQFHDQEgBi0ACiEHIAZBCGoQyICAgAACQAJAIAdB2wBGDQAgB0H7AEYNASABIAZB2AFqQbCRwIAAEOCAgIAAIQcgBkEBNgIIIAYgBzYCDAwQCyABLQAYQX9qIgdB/wFxIgggB0cNBCABIAc6ABgCQCAIDQAgBkEoaiEHDA4LIAEQ0ICAgAAgBiABNgKAASAGQQE6AIQBIAZBwABqIAZBgAFqEN2BgIAAIAYoAkBBAUYNBSAGQbABakEIaiIHIAZBzABqKAIANgIAIAYgBikCRCIJNwOwAQJAIAmnDQBBAEHUm8CAAEGMm8CAABC7gYCAACEKIAZBsAFqEJWBgIAADAwLIAZBkAFqQQhqIAcoAgA2AgAgBiAGKQOwATcDkAEgBkHAAGogBkGAAWoQ3IGAgAACQAJAAkAgBigCQEEBRw0AIAYoAkQhCgwBCyAGQbABakEIaiIHIAZBzABqKAIANgIAIAYgBikCRCIJNwOwASAJpw0BQQFB1JvAgABBjJvAgAAQu4GAgAAhCiAGQbABahCdgYCAAAsgBkGQAWoQvoCAgAAMDAsgBkGgAWpBCGogBygCACIHNgIAIAZBKGpBCGogBikDsAE3AwAgBkE4aiAHNgIAIAYgBikClAE3AyggBigCkAEhCkEAIQcMDAsgAS0AGEF/aiIHQf8BcSIIIAdHDQIgASAHOgAYAkAgCA0AIAZBwABqIQcMDQsgARDQgICAACAGQQE6AHwgBiABNgJ4IAZBADYCgAEgBkEANgKQASAGQcAAakEEciEIAkADQCAGQbABaiAGQfgAahDQgYCAAAJAAkAgBi0AsAFBAUYNACAGLQCxASIHQQNGDQMCQCAHQQFLDQACQAJAIAcOAgABAAsCQCAGKAKAAQ0AIAZBwABqIAZB+ABqENSBgIAAIAYoAkBBAUcNBAwNC0Gcm8CAAEEOELyBgIAAIQcMDQsCQCAGKAKQAQ0AIAZBwABqIAZB+ABqENWBgIAAIAYoAkBBAUYNDCAGQZABahCdgYCAACAGQZABakEIaiAIQQhqKAIANgIAIAYgCCkCADcDkAEMBAtBqpvAgABBDhC8gYCAACEHDAwLIAYgBkH4AGoQ2YGAgAAiBzYCQCAHDQsgBkHAAGoQk4GAgAAMAgsgBigCtAEhBwwKCyAGQYABahCVgYCAACAGQYABakEIaiAIQQhqKAIANgIAIAYgCCkCADcDgAEMAAsLIAYoAoABDQVBnJvAgABBDhDzgICAACEHDAcLIAAgBigCDDYCBCAAQQE2AgAMDAsgBkEFNgJAIAEgBkHAAGoQxYCAgAAhASAAQQE2AgAgACABNgIEIAZBCGoQyICAgAAMCwtBwIHAgABBIUHQkMCAABCNhoCAAAALQcCBwIAAQSFB0JDAgAAQjYaAgAAACyAGKAJEIQoMBQsgBkGgAWpBCGogBkGAAWpBCGooAgA2AgAgBiAGKQOAATcDoAECQAJAAkACQAJAIAYoApABIggNACAGQcAAakGqm8CAAEEOEMSBgIAAQQEhCiAGKAJAQQFGDQIgBkG4AWogBkHMAGooAgA2AgAgBiAGKQJENwOwASAGKAKQASEIDAELIAZBsAFqQQhqIAZBkAFqQQhqKAIANgIAIAYgBikDkAE3A7ABQQAhCgsgBkEoakEIaiAGKQOwATcDACAGQThqIAZBsAFqQQhqKAIANgIAIAYgBikCpAE3AyggBigCoAEhByAIRQ0BIApFDQIgBkGQAWoQvoCAgAAMAgsgBigCRCEHIAZBoAFqEL6AgIAAQQAhCgwECyAGQZABahCdgYCAAAtBACEIIAYoAoABDQMgBkGAAWoQlYGAgAAMAwsgBigCRCEHC0EBIQoLIAZBkAFqEJ2BgIAAAkAgBigCgAFFDQBBASEIIApFDQEgBkGAAWoQvoCAgAAMAQsgBkGAAWoQlYGAgABBASEICwJAAkAgAS0AGEEBaiIKQf8BcSAKRw0AIAEgCjoAGCABEOKAgIAAIQogBkHAAGpBCGogBikDKDcDACAGQcAAakEQaiAGQShqQQhqKQMANwMAIAZB2ABqIAZBKGpBEGooAgA2AgAgBiAHNgJEIAYgCDYCQCAGIAo2AlwCQAJAAkACQCAIDQAgCkUNAkEAIQtBASEMDAELQQEhC0EAIQwgByEKCyAGQQE2AgggBiAKNgIMIAgNASAGQcAAakEEchC9gICAAAwDCyAGQQhqQRRqIAZBwABqQRRqKQIANwIAIAZBCGpBDGogBkHAAGpBDGopAgA3AgAgBiAGKQJENwIMIAZBADYCCEEBIQsMAgsgDEUNASAGQcAAakEEchDYgICAAAwBC0GwgsCAAEEcQeCQwIAAEI2GgIAAAAsgBkHcAGohBwJAIAYoAlwNACAHELyAgIAADAULIAtFDQQgBxDYgICAAAwEC0EBIQcLAkACQCABLQAYQQFqIghB/wFxIAhHDQAgASAIOgAYIAEQ44CAgAAhCCAGQcAAakEIaiAGKQMoNwMAIAZBwABqQRBqIAZBKGpBCGopAwA3AwAgBkHYAGogBkEoakEQaigCADYCACAGIAo2AkQgBiAHNgJAIAYgCDYCXAJAAkACQAJAIAcNACAIRQ0CQQAhC0EBIQwMAQtBASELQQAhDCAKIQgLIAZBATYCCCAGIAg2AgwgBw0BIAZBwABqQQRyEL2AgIAADAMLIAZBCGpBFGogBkHAAGpBFGopAgA3AgAgBkEIakEMaiAGQcAAakEMaikCADcCACAGIAYpAkQ3AgwgBkEANgIIQQEhCwwCCyAMRQ0BIAZBwABqQQRyENiAgIAADAELQbCCwIAAQRxB4JDAgAAQjYaAgAAACyAGQdwAaiEHAkAgBigCXA0AIAcQvICAgAAMAwsgC0UNAiAHENiAgIAADAILIAdBFTYCACABIAcQxYCAgAAhASAAQQE2AgAgACABNgIECyAGQeABaiSAgICAAA8LAkACQCAGKAIIQQFHDQAgACAGKAIMIAEQyoCAgAA2AgQgAEEBNgIAIAYoAggNASAGQQhqQQRyEL2AgIAADAELIABBADYCACAAIAZBCGpBBHIiASkCADcCBCAAQRRqIAFBEGopAgA3AgAgAEEMaiABQQhqKQIANwIACyAGQeABaiSAgICAAAugAgEBfyOAgICAAEEQayIDJICAgIAAAkACQAJAAkAgAkUNACADQQhqIAEQ8ICAgAAgAyADQQhqEO2AgIAAIgI2AgQgAg0BIANBBGoQvICAgAAgAEEIakEBOgAAIAAgATYCBCAAQQA2AgAMAwsgA0EIaiABEPCAgIAAIAMgA0EIahDtgICAACICNgIEIAINASADQQRqELyAgIAAIANBCGogARDugICAACADIANBCGoQ7YCAgAAiAjYCBAJAIAINACADQQRqELyAgIAAIABBCGpBADoAACAAIAE2AgQgAEEANgIADAMLIABBATYCACAAIAI2AgQMAgsgAEEBNgIAIAAgAjYCBAwBCyAAQQE2AgAgACACNgIECyADQRBqJICAgIAAC9QCBAF/AX4BfwF+I4CAgIAAQfAAayIAJICAgIAAIAAQ5ISAgAAiATcDGAJAIAFCAVINACAAQdgAakIAEOWEgIAAAkAgACgCWCICQQFHDQAgAEHYAGpBBHIQvoCAgAALIABB8ABqJICAgIAAIAJBAUYPCyAAIABBGGo2AlAgAEGAiMCAADYCVCAAQdgAakEUakEANgIAIABBrIPAgAA2AmggAEIBNwJcIABBwJLAgAA2AlggAEEQaiAAQdAAakGHgICAABDpgoCAACAAKQMQIQEgAEEIaiAAQdQAakGHgICAABDpgoCAACAAKQMIIQMgACAAQdgAakGIgICAABCShYCAACAAQSBqQRRqQQM2AgAgACADNwNAIAAgATcDOCAAQgM3AiQgAEH8kcCAADYCICAAIAApAwA3A0ggACAAQThqNgIwIABBIGpB1JLAgAAQ5oWAgAAACxIAQfj5woAAIAAgARDGg4CAAAsUAEH4+cKAACAAIAEgAhDHg4CAAAsWAEH4+cKAACAAIAEgAiADEKGBgIAACxIAQfj5woAAIAAgARCggYCAAAsMACABIAIQgICAgAALCgAgARCBgICAAAsKACABEIKAgIAACwoAIAEQg4CAgAALCgAgARCEgICAAAsKACABEIWAgIAACwoAIAEQhoCAgAALCAAQh4CAgAALCAAQiICAgAALCAAQiYCAgAALCAAQioCAgAALCgAgARCLgICAAAsKACABEIyAgIAACwoAIAEQjYCAgAALCAAQjoCAgAALCAAQj4CAgAALCgAgARCQgICAAAsOACABIAIgAxCRgICAAAsOACABIAIgAxCSgICAAAsOACABIAIgAxCTgICAAAsMACABIAIQlICAgAALCAAQlYCAgAALDAAgASACEJaAgIAACwwAIAEgAhCXgICAAAsMACABIAIQmICAgAALGAAgASACIAMgBCAFIAYgByAIEJmAgIAACxoAIAEgAiADIAQgBSAGIAcgCCAJEJqAgIAACwwAIAEgAhCbgICAAAsMACABIAIQnICAgAALDgAgASACIAMQnYCAgAALCgAgARCegICAAAsOACABIAIgAxCfgICAAAsWACABIAIgAyAEIAUgBiAHEKCAgIAACwwAIAEgAhChgICAAAsQACABIAIgAyAEEKKAgIAACxAAIAEgAiADIAQQo4CAgAALGgAgASACIAMgBCAFIAYgByAIIAkQpICAgAALDgAgASACIAMQpYCAgAALDgAgASACIAMQpoCAgAALCAAQp4CAgAALDAAgASACEKiAgIAACwoAIAEQqYCAgAALEgAgASACIAMgBCAFEKqAgIAACw4AIAEgAiADEKuAgIAACw4AIAEgAiADEKyAgIAACwwAIAEgAhCtgICAAAvEAQEBfyOAgICAAEEgayICJICAgIAAIAJBEGogAUECEIuCgIAAAkACQCACKAIQQQFHDQAgAigCFCEBDAELIAIgAigCFDYCCCACIAJBGGotAAA6AAwgAiACQQhqQdyTwIAAQQogAEEQahC+gYCAACIBNgIQIAENACACQRBqELyAgIAAIAIgAkEIaiAAEN6BgIAAIgE2AhAgAQ0AIAJBEGoQvICAgAAgAigCCCACLQAMEN+BgIAAIQELIAJBIGokgICAgAAgAQuWAQEBfyOAgICAAEEgayICJICAgIAAIAJBEGogAUEBEIuCgIAAAkACQCACKAIQQQFHDQAgAigCFCEBDAELIAIgAigCFDYCCCACIAJBGGotAAA6AAwgAiACQQhqIAAQ3oGAgAAiATYCECABDQAgAkEQahC8gICAACACKAIIIAItAAwQ34GAgAAhAQsgAkEgaiSAgICAACABC6kEAwF/A34BfyOAgICAAEGgAWsiAySAgICAACADQcAAahDXhICAAAJAIAMpA0BCgYCA9d246+Q1VCADQcAAakEIaikDACIEQjZUIARCNlEbDQAgA0HgAGpBCGogAkEIaigCADYCACADIAIpAgA3A2AgA0HQAGogA0HgAGoQrIGAgAAgA0EoaiABIANB0ABqEPCBgIAAIAMoAighAiADKQMwIQQgA0E4aikDACEFIANBGGoQ14SAgAACQAJAIARCACACGyIEIAMpAxh8IgYgBFQiByAFQgAgAhsiBSADQRhqQQhqKQMAfCAHrXwiBCAFVCAEIAVRGw0AIAZCgICAi6LHlJtKfCIFIAZWIAQgBkKAgID13bjr5DVUrX1CSnwiBiAEViAGIARRGw0BIAMgBTcDYCADIAY3A2ggAyABIANB0ABqIANB4ABqEPOBgIAAIANBkAFqENOEgIAAIANB4ABqIANBkAFqEO2EgIAAIANB8ABqQQhqIANB0ABqQQhqKAIANgIAIAMgAykDUDcDcCADQYABahDThICAACADQZABakHklMCAAEEeELKBgIAAIAAgA0HgAGogA0HwAGpCgICA9d246+Q1QjYgA0GAAWogA0GQAWoQ84SAgAAgA0GgAWokgICAgAAPC0GwgsCAAEEcQcSUwIAAEI2GgIAAAAtBwIHAgABBIUHUlMCAABCNhoCAAAALQfiTwIAAQTpBtJTAgAAQoYWAgAAAC4sFAgF/An4jgICAgABBwAFrIgMkgICAgAAgA0HQAGoQ1YSAgAAgA0HgAGoQ04SAgAACQAJAIANB0ABqIANB4ABqEMaBgIAARQ0AIANB4ABqEL6AgIAAIANB0ABqEL6AgIAAIANBMGogAhDPhYCAACADKAIwIAMoAjQQ7ISAgAANAUHAlcCAAEESQdSVwIAAEKGFgIAAAAsgAyADQdAAajYCoAEgAyADQeAAajYCpAEgA0GoAWpBFGpBADYCACADQayDwIAANgK4ASADQgE3AqwBIANBqJXAgAA2AqgBIANByABqIANBoAFqQYmAgIAAEKaFgIAAIAMpA0ghBCADQcAAaiADQaQBakGJgICAABCmhYCAACADKQNAIQUgA0E4aiADQagBakGIgICAABCShYCAACADQfAAakEUakEDNgIAIAMgBTcDkAEgAyAENwOIASADQgM3AnQgA0H8kcCAADYCcCADIAMpAzg3A5gBIAMgA0GIAWo2AoABIANB8ABqQbCVwIAAEOaFgIAAAAsgA0GoAWoQ1ISAgAAgA0EYaiABIANBqAFqEPSBgIAAIANBCGogAykDGCADKQMgIANBKGopAwBB/JXAgAAQqoGAgAAgA0EIakEIaikDACEEIAMpAwghBSADQagBahC+gICAACADQYgBahDThICAACADQagBaiADQYgBahDthICAACADQfAAahDUhICAACADQYgBaiADQagBaiADQfAAahD0hICAACADQYgBahCYgYCAACADQYgBakEIaiACQQhqKAIANgIAIAMgAikCADcDiAEgA0GoAWogA0GIAWoQ7YSAgAAgACADQagBaiAFIAQQ8YSAgAAgA0HAAWokgICAgAALvQgCAX8CfiOAgICAAEHwAWsiBCSAgICAACAEQeABahDVhICAACAEQaABahDThICAAAJAIARB4AFqIARBoAFqEMaBgIAARQ0AIARBoAFqEL6AgIAAIARB4AFqEL6AgIAAIARBOGogAhDPhYCAAAJAIAQoAjggBCgCPBDshICAAEUNACAEQYgBahDUhICAACAEQSBqIAEgBEGIAWoQ9IGAgAAgBEEQaiAEKQMgIAQpAyggBEEwaikDAEHslsCAABCqgYCAACAEQRBqQQhqKQMAIQUgBCkDECEGIARBiAFqEL6AgIAAIARB8ABqQQhqIAJBCGooAgA2AgAgBCACKQIANwNwIARBiAFqIARB8ABqEO2EgIAAIARB8ABqIARBiAFqEO+EgIAAIARBiAFqQQhqIANBCGooAgA2AgAgBCADKQIANwOIASAEQdgAaiAEQYgBahCsgYCAACAEQYgBaiAEQfAAaiAEQdgAahDyhICAACAEQaABaiAEQYgBaiAGIAUQ8YSAgAAgBCAGIAUQ/YSAgAAgBEEIaikDACEFIAQpAwAhBiAEQbABahDThICAACAEIAU3A3ggBCAGNwNwIARB2ABqQYABEOqAgIAAIAQgBEHYAGo2AtABIAQgBEHwAGogBEHQAWoQwIKAgAAiAjYC4AECQAJAIAINACAEQeABahC8gICAAEEAIQIgBEEANgLAASAEQcABahC8gICAACAEQZQBaiAEQdgAakEIaigCADYCACAEIAQpA1g3AowBDAELIAQgAjYCjAEgBEHYAGoQvoCAgABBASECCyAEIAI2AogBIARBwAFqIARBiAFqQeSSwIAAQTdBnJPAgAAQq4GAgAAgBEHYAGogBEGwAWoQiYaAgAAgBEGIAWogBEHYAGoQ7YSAgAAgBEHQAWpBvpPAgABBHhCygYCAACAEQeABakEIaiAEQcABakEIaigCADYCACAEIAQpA8ABNwPgASAEQdgAaiAEQYgBaiAEQdABaiAEQeABakIAQgBCgIDpg7HeFhDwhICAACAAIARBoAFqIARB2ABqEPWEgIAAIARBsAFqEL6AgIAAIARB8AFqJICAgIAADwtBwJXAgABBEkHclsCAABChhYCAAAALIAQgBEHgAWo2AsABIAQgBEGgAWo2AtABIARBiAFqQRRqQQA2AgAgBEGsg8CAADYCmAEgBEIBNwKMASAEQcSWwIAANgKIASAEQdAAaiAEQcABakGJgICAABCmhYCAACAEKQNQIQUgBEHIAGogBEHQAWpBiYCAgAAQpoWAgAAgBCkDSCEGIARBwABqIARBiAFqQYiAgIAAEJKFgIAAIARB2ABqQRRqQQM2AgAgBCAGNwN4IAQgBTcDcCAEQgM3AlwgBEH8kcCAADYCWCAEIAQpA0A3A4ABIAQgBEHwAGo2AmggBEHYAGpBzJbAgAAQ5oWAgAAAC/8FAgF/An4jgICAgABB0AFrIgQkgICAgAAgBEEoaiACEM+FgIAAAkAgBCgCKCAEKAIsEOyEgIAARQ0AIARBGGoQ14SAgAAgBEEYakEIaikDACEFIAQpAxghBiAEQaABakEIaiACQQhqKAIANgIAIAQgAikCADcDoAEgBEHwAGogBEGgAWoQ7YSAgAAgBEGgAWogBEHwAGoQ74SAgAAgBEHwAGpBCGogA0EIaigCADYCACAEIAMpAgA3A3AgBEHAAGogBEHwAGoQrIGAgAAgBEHwAGogBEGgAWogBEHAAGoQ8oSAgAAgBEEwaiAEQfAAaiAGIAUQ8YSAgAAgBEHQAGoQ1YSAgAAgBEEIaiAGIAUQ/YSAgAAgBEEIakEIaikDACEFIAQpAwghBiAEQeAAahDThICAACAEQYgBaiAEQdAAakEIaigCADYCACAEIAU3A3ggBCAGNwNwIAQgBCkDUDcDgAEgBEHAAGpBgAEQ6oCAgAAgBCAEQcAAajYCsAEgBCAEQfAAaiAEQbABahC/goCAACICNgLAASAEQYABaiEDAkACQCACDQAgBEHAAWoQvICAgABBACECIARBADYCkAEgBEGQAWoQvICAgAAgBEGsAWogBEHAAGpBCGooAgA2AgAgBCAEKQNANwKkAQwBCyAEIAI2AqQBIARBwABqEL6AgIAAQQEhAgsgBCACNgKgASAEQZABaiAEQaABakHkksCAAEE3QZyTwIAAEKuBgIAAIARBwABqIARB4ABqEImGgIAAIARBoAFqIARBwABqEO2EgIAAIARBsAFqQayTwIAAQRIQsoGAgAAgBEHAAWpBCGogBEGQAWpBCGooAgA2AgAgBCAEKQOQATcDwAEgBEHAAGogBEGgAWogBEGwAWogBEHAAWpCAEIAQoCA6YOx3hYQ8ISAgAAgAxC+gICAACAAIARBMGogBEHAAGoQ9YSAgAAgBEHgAGoQvoCAgAAgBEHQAWokgICAgAAPC0HAlcCAAEESQfyWwIAAEKGFgIAAAAvgAwECfyOAgICAAEGgAWsiBCSAgICAACAEQTBqENWEgIAAIARBwABqENOEgIAAAkACQCAEQTBqIARBwABqEMaBgIAARQ0AIARBwABqEL6AgIAAIARBMGoQvoCAgAACQBCMgoCAACIFDQAgBEHoAGpBCGogAUEIaigCADYCACAEIAEpAgA3A2ggBEGIAWogBEHoAGoQ7YSAgAAgBEEIaiACIAMQ/oSAgAAgBEHoAGogBEGIAWogBCkDCCAEQQhqQQhqKQMAEPGEgIAAIARB6ABqEJiBgIAADAILIAEQvoCAgAAMAQsgBCAEQTBqNgKAASAEIARBwABqNgKEASAEQYgBakEUakEANgIAIARBrIPAgAA2ApgBIARCATcCjAEgBEG8l8CAADYCiAEgBEEoaiAEQYABakGJgICAABCmhYCAACAEKQMoIQIgBEEgaiAEQYQBakGJgICAABCmhYCAACAEKQMgIQMgBEEYaiAEQYgBakGIgICAABCShYCAACAEQdAAakEUakEDNgIAIAQgAzcDcCAEIAI3A2ggBEIDNwJUIARB/JHAgAA2AlAgBCAEKQMYNwN4IAQgBEHoAGo2AmAgBEHQAGpBxJfAgAAQ5oWAgAAACyAEQaABaiSAgICAACAFC5QEAQJ/I4CAgIAAQbABayIDJICAgIAAIANBwABqENWEgIAAIANB0ABqENOEgIAAAkAgA0HAAGogA0HQAGoQxoGAgABFDQAgA0HQAGoQvoCAgAAgA0HAAGoQvoCAgAACQAJAEIyCgIAAIgQNACADQfgAahDUhICAACADQRhqIAEgAhD+hICAACADIANBIGopAwA3A6ABIAMgAykDGDcDmAEgAyAAIANB+ABqIANBmAFqEPOBgIAAIANB+ABqEL6AgIAADAELIANB+ABqENOEgIAAIANBmAFqIANB+ABqEO2EgIAAIANB4ABqENSEgIAAIANB+ABqIANBmAFqIANB4ABqEPSEgIAAIANB+ABqEJiBgIAACyADQbABaiSAgICAACAEDwsgAyADQcAAajYCkAEgAyADQdAAajYClAEgA0GYAWpBFGpBADYCACADQayDwIAANgKoASADQgE3ApwBIANBvJfAgAA2ApgBIANBOGogA0GQAWpBiYCAgAAQpoWAgAAgAykDOCEBIANBMGogA0GUAWpBiYCAgAAQpoWAgAAgAykDMCECIANBKGogA0GYAWpBiICAgAAQkoWAgAAgA0HgAGpBFGpBAzYCACADIAI3A4ABIAMgATcDeCADQgM3AmQgA0H8kcCAADYCYCADIAMpAyg3A4gBIAMgA0H4AGo2AnAgA0HgAGpB1JfAgAAQ5oWAgAAAC9YDAwJ/AX4BfyOAgICAAEHAAWsiACSAgICAABDQhICAAEEBQeSXwIAAEM6EgIAAIABB4ABqENaEgIAAIABBIGogAEHgAGpBsJnAgABBKkHcmcCAABCpgYCAACAAQQhqIABBIGoQz4WAgAAgAEHgAGogACgCCCAAKAIMELCAgIAAAkAgACgCYEEBRw0AIAAgACgCZDYCoAFB7JnAgABBJiAAQaABakHYhcCAAEHcmcCAABCqhoCAAAALIABBoAFqQQhqIABB7ABqKAIAIgE2AgAgACAAKQJkIgI3A6ABIABBEGpBCGoiAyABNgIAIAAgAjcDECAAQSBqEL6AgIAAIABB4ABqEPyBgIAAIABBIGogAEHgAGoQpoGAgAAgAEHgAGpBCGogAygCADYCACAAIAApAxA3A2AgAEGgAWogAEEgaiAAQeAAahDBgoCAACAAQeAAaiAAQaABahDpgICAACAAQbABaiAAQeAAakGSmsCAAEEwQdyZwIAAEKuBgIAAIAAgAEGwAWoQz4WAgAAgACgCACAAKAIEEOeEgIAAIABBIGoQ/YGAgAAgAEGwAWoQvoCAgAAgAEGgAWoQmIGAgAAgAEEgahCUgYCAACAAQcABaiSAgICAAAuHBAMCfwF+AX8jgICAgABB0AFrIgAkgICAgAAQ0ISAgABBAUHkl8CAABDOhICAACAAQRBqENeEgIAAAkACQCAAKQMQIABBGGopAwCEQgBSDQAgAEHwAGoQ1oSAgAAgAEEwaiAAQfAAakGwmcCAAEEqQdyZwIAAEKmBgIAAIABBCGogAEEwahDPhYCAACAAQfAAaiAAKAIIIAAoAgwQsoCAgAAgACgCcEEBRg0BIABBsAFqQQhqIABB/ABqKAIAIgE2AgAgACAAKQJ0IgI3A7ABIABBIGpBCGoiAyABNgIAIAAgAjcDICAAQTBqEL6AgIAAIABB8ABqEPyBgIAAIABBMGogAEHwAGoQpoGAgAAgAEHwAGpBCGogAygCADYCACAAIAApAyA3A3AgAEGwAWogAEEwaiAAQfAAahDCgoCAACAAQfAAaiAAQbABahDpgICAACAAQcABaiAAQfAAakGSmsCAAEEwQdyZwIAAEKuBgIAAIAAgAEHAAWoQz4WAgAAgACgCACAAKAIEEOeEgIAAIABBMGoQ/YGAgAAgAEHAAWoQvoCAgAAgAEGwAWoQmIGAgAAgAEEwahCUgYCAACAAQdABaiSAgICAAA8LQcKawIAAQR0Qz4SAgAAACyAAIAAoAnQ2ArABQeyZwIAAQSYgAEGwAWpB2IXAgABB3JnAgAAQqoaAgAAAC+oEAwF/An4CfyOAgICAAEHgAWsiACSAgICAABDQhICAAEEBQeSXwIAAEM6EgIAAIABBEGoQ14SAgAACQAJAIAApAxAgAEEYaikDAIRCAFINACAAQYABahDWhICAACAAQcABaiAAQYABakGwmcCAAEEqQdyZwIAAEKmBgIAAIABBCGogAEHAAWoQz4WAgAAgAEGAAWogACgCCCAAKAIMELSAgIAAIAAoAoABQQFGDQEgAEHQAGogAEGAAWpBFGopAgA3AwAgAEHAAGpBCGogAEGMAWopAgAiATcDACAAIAApAoQBIgI3A0AgAEEgakEIaiIDIAE+AgAgACACNwMgIABBMGpBCGoiBCAAQcAAakEUaigCADYCACAAIAApAkw3AzAgAEHAAWoQvoCAgAAgAEGAAWoQ/IGAgAAgAEHAAGogAEGAAWoQpoGAgAAgAEHQAWpBCGogAygCADYCACAAIAApAyA3A9ABIABBgAFqQQhqIAQoAgA2AgAgACAAKQMwNwOAASAAQcABaiAAQcAAaiAAQdABaiAAQYABahDDgoCAACAAQYABaiAAQcABahDpgICAACAAQdABaiAAQYABakGSmsCAAEEwQdyZwIAAEKuBgIAAIAAgAEHQAWoQz4WAgAAgACgCACAAKAIEEOeEgIAAIABBwABqEP2BgIAAIABB0AFqEL6AgIAAIABBwAFqEJiBgIAAIABBwABqEJSBgIAAIABB4AFqJICAgIAADwtBwprAgABBHRDPhICAAAALIAAgACgChAE2AkBB7JnAgABBJiAAQcAAakHYhcCAAEHcmcCAABCqhoCAAAALqwQDAX8CfgJ/I4CAgIAAQdABayIAJICAgIAAENCEgIAAQQFB5JfAgAAQzoSAgAAgAEHwAGoQ1oSAgAAgAEGwAWogAEHwAGpBsJnAgABBKkHcmcCAABCpgYCAACAAQQhqIABBsAFqEM+FgIAAIABB8ABqIAAoAgggACgCDBC4gICAAAJAIAAoAnBBAUcNACAAIAAoAnQ2AjBB7JnAgABBJiAAQTBqQdiFwIAAQdyZwIAAEKqGgIAAAAsgAEHAAGogAEHwAGpBFGopAgA3AwAgAEEwakEIaiAAQfwAaikCACIBNwMAIAAgACkCdCICNwMwIABBEGpBCGoiAyABPgIAIAAgAjcDECAAQSBqQQhqIgQgAEEwakEUaigCADYCACAAIAApAjw3AyAgAEGwAWoQvoCAgAAgAEHwAGoQ/IGAgAAgAEEwaiAAQfAAahCmgYCAACAAQcABakEIaiADKAIANgIAIAAgACkDEDcDwAEgAEHwAGpBCGogBCgCADYCACAAIAApAyA3A3AgAEGwAWogACAAQcABaiAAQfAAahDEgoCAACAAQfAAaiAAQbABahDpgICAACAAQcABaiAAQfAAakGSmsCAAEEwQdyZwIAAEKuBgIAAIAAgAEHAAWoQz4WAgAAgACgCACAAKAIEEOeEgIAAIABBMGoQ/YGAgAAgAEHAAWoQvoCAgAAgAEGwAWoQmIGAgAAgAEEwahCUgYCAACAAQdABaiSAgICAAAubBAUCfwJ+AX8BfgF/I4CAgIAAQdABayIAJICAgIAAENCEgIAAQQFB5JfAgAAQzoSAgAAgAEEYahDXhICAAAJAAkAgACkDGCAAQSBqKQMAhEIAUg0AIABB+ABqENaEgIAAIABBOGogAEH4AGpBsJnAgABBKkHcmcCAABCpgYCAACAAQRBqIABBOGoQz4WAgAAgAEH4AGogACgCECAAKAIUELaAgIAAIAAoAnhBAUYNASAAQcABakEIaiAAQZgBaigCACIBNgIAIAAgAEGQAWopAwAiAjcDwAEgAEGIAWopAwAhAyAAQfgAakEIaiIEKQMAIQUgAEEoakEIaiIGIAE2AgAgACACNwMoIABBOGoQvoCAgAAgAEH4AGoQ/IGAgAAgAEE4aiAAQfgAahCmgYCAACAEIAYoAgA2AgAgACAAKQMoNwN4IAAgACAAQfgAaiAFIAMQxYKAgAA6AL8BIABB+ABqIABBvwFqEOyAgIAAIABBwAFqIABB+ABqQZKawIAAQTBB3JnAgAAQq4GAgAAgAEEIaiAAQcABahDPhYCAACAAKAIIIAAoAgwQ54SAgAAgAEE4ahD9gYCAACAAQcABahC+gICAACAAQThqEJSBgIAAIABB0AFqJICAgIAADwtBwprAgABBHRDPhICAAAALIAAgACgCfDYCwAFB7JnAgABBJiAAQcABakHYhcCAAEHcmcCAABCqhoCAAAALuwMCAX8CfiOAgICAAEHAAWsiACSAgICAABDQhICAAEEBQeSXwIAAEM6EgIAAIABBGGoQ14SAgAACQAJAIAApAxggAEEgaikDAIRCAFINACAAQegAahDWhICAACAAQShqIABB6ABqQbCZwIAAQSpB3JnAgAAQqYGAgAAgAEEQaiAAQShqEM+FgIAAIABB6ABqIAAoAhAgACgCFBCugICAACAAKAJoQQFGDQEgAEH4AGopAwAhASAAQfAAaikDACECIABBKGoQvoCAgAAgAEHoAGoQ/IGAgAAgAEEoaiAAQegAahCmgYCAACAAIABBKGogAiABEMaCgIAAOgCvASAAQegAaiAAQa8BahDsgICAACAAQbABaiAAQegAakGSmsCAAEEwQdyZwIAAEKuBgIAAIABBCGogAEGwAWoQz4WAgAAgACgCCCAAKAIMEOeEgIAAIABBKGoQ/YGAgAAgAEGwAWoQvoCAgAAgAEEoahCUgYCAACAAQcABaiSAgICAAA8LQcKawIAAQR0Qz4SAgAAACyAAIAAoAmw2ArABQeyZwIAAQSYgAEGwAWpB2IXAgABB3JnAgAAQqoaAgAAACxMBAX8gACABEI2CgIAAIQIgAg8LDwAgACABIAIQjoKAgAAPCxcBAX8gACABIAIgAxCPgoCAACEEIAQPCxMBAX8gACABEJCCgIAAIQIgAg8LTQEBfyOAgICAAEEQayIEJICAgIAAIARBCGogARCrhYCAACAEIAQoAgggBCgCDCACIAMQ0oKAgAAgACAEKQMANwIAIARBEGokgICAgAALygEBAX8jgICAgABBIGsiBSSAgICAACAFIAI2AgQgBSABNgIAIAUgAzYCCCAFIAQ2AgwCQCAEIANJDQACQCADRQ0AIAIgA0YNACACIANNDQEgASADaiwAAEG/f0wNAQsCQCAERQ0AIAIgBEYNACACIARNDQEgASAEaiwAAEG/f0wNAQsgACAEIANrNgIEIAAgASADajYCACAFQSBqJICAgIAADwsgBSAFQQxqNgIYIAUgBUEIajYCFCAFIAU2AhAgBUEQahD6goCAAAALZQECfyOAgICAAEEQayIDJICAgIAAIAAgAiABayICENSCgIAAIAAgACgCCCIEIAJqNgIIIANBCGogABDVgoCAACAEIAMoAghqIAMoAgwgBGsgASACENaCgIAAIANBEGokgICAgAALEQAgACAAKAIIIAEQlYOAgAALGQAgACABEKqFgIAANgIAIAAgASgCCDYCBAuTAgEBfyOAgICAAEHgAGsiBCSAgICAACAEIAE2AgggBCADNgIMAkAgASADRw0AIAAgAiABEPOGgIAAGiAEQeAAaiSAgICAAA8LIARBKGpBFGpBiICAgAA2AgAgBEE0akHVgICAADYCACAEQRBqQRRqQQM2AgAgBCAEQQhqNgJAIAQgBEEMajYCRCAEQcgAakEUakEANgIAIARCAzcCFCAEQbygwIAANgIQIARB1YCAgAA2AiwgBEGcnMCAADYCWCAEQgE3AkwgBEGIocCAADYCSCAEIARBKGo2AiAgBCAEQcgAajYCOCAEIARBxABqNgIwIAQgBEHAAGo2AiggBEEQakHwn8CAABCxhoCAABCUhoCAAAALSwEBfyOAgICAAEEQayIDJICAgIAAIANBCGogARCrhYCAACADIAMoAgggAygCDCACENiCgIAAIAAgAykDADcCACADQRBqJICAgIAAC54BAQF/I4CAgIAAQSBrIgQkgICAgAAgBCACNgIEIAQgATYCACAEIAM2AgggBCACNgIMAkAgA0UNACACIANGDQACQCACIANNDQAgASADaiwAAEG/f0oNAQsgBCAEQQxqNgIYIAQgBEEIajYCFCAEIAQ2AhAgBEEQahD8goCAAAALIAAgAiADazYCBCAAIAEgA2o2AgAgBEEgaiSAgICAAAtEAQF/I4CAgIAAQRBrIgQkgICAgAAgBEEIaiACIAMQ3IKAgAAgACABIAQoAghB9JvAgAAQ8YWAgAAgBEEQaiSAgICAAAtgAQN/AkAgAC0AAEECSQ0AIABBBGooAgAiASgCACABKAIEKAIAEYGAgIAAAAJAIAEoAgQiAigCBCIDRQ0AIAEoAgAgAyACKAIIEM6CgIAACyAAKAIEQQxBBBDOgoCAAAsLQAACQAJAIAIgAUkNACAEIAJPDQEgAiAEEI+GgIAAAAsgASACEJCGgIAAAAsgACACIAFrNgIEIAAgAyABajYCAAtiAQF/I4CAgIAAQRBrIgMkgICAgAAgAyABIAIQjoOAgABBDEEEEN2CgIAAIgFBCGogA0EIaigCADYCACABIAMpAwA3AgAgAEH0m8CAADYCBCAAIAE2AgAgA0EQaiSAgICAAAsuAQF/AkACQCAARQ0AIAAgARDNgoCAACICDQEgACABEIWGgIAAAAsgASECCyACCwkAIABBADYCAAsJACAAQQA2AgALDQBCz7iP7NPB+cS/fwsEAEEACw8AIAAoAgAgARDjgoCAAAvZAwACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIADhYBAgMEBQYHCAkKCwwNDg8QERITFBUAAQsgAUHlt8CAAEEYEMiGgIAADwsgASAAKAIEIABBCGooAgAQyIaAgAAPCyAAQQRqIAEQ54WAgAAPCyABQZW7wIAAQRgQyIaAgAAPCyABQfq6wIAAQRsQyIaAgAAPCyABQeC6wIAAQRoQyIaAgAAPCyABQce6wIAAQRkQyIaAgAAPCyABQbu6wIAAQQwQyIaAgAAPCyABQai6wIAAQRMQyIaAgAAPCyABQZW6wIAAQRMQyIaAgAAPCyABQYe6wIAAQQ4QyIaAgAAPCyABQfm5wIAAQQ4QyIaAgAAPCyABQeu5wIAAQQ4QyIaAgAAPCyABQd25wIAAQQ4QyIaAgAAPCyABQcq5wIAAQRMQyIaAgAAPCyABQbC5wIAAQRoQyIaAgAAPCyABQfK4wIAAQT4QyIaAgAAPCyABQd64wIAAQRQQyIaAgAAPCyABQbq4wIAAQSQQyIaAgAAPCyABQay4wIAAQQ4QyIaAgAAPCyABQZm4wIAAQRMQyIaAgAAPCyABQf23wIAAQRwQyIaAgAALFAAgACgCACAAKAIEIAEQr4WAgAALDwAgACgCACABEMSGgIAACxwAIAAoAgAgACgCBCABKAIAIAEoAgQQ54KAgAALMAEBf0EAIQQCQAJAIAEgA0cNACAAIAJHDQFBASEECyAEDwsgACACIAEQ9IaAgABFCxAAIAAgAjYCBCAAIAE2AgALEAAgACACNgIEIAAgATYCAAtxAQF/I4CAgIAAQSBrIgIkgICAgAAgAiAANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBhJ7AgAAgAkEIahCfhoCAACEBIAJBIGokgICAgAAgAQs1AQF/I4CAgIAAQRBrIgEkgICAgAAgAUEIaiAAENWCgIAAIAAQ8oKAgAAgAUEQaiSAgICAAAsCAAtIAQF/AkAgACgCACIBQQFLDQACQAJAIAEOAgABAAsgAEEIaigCACIBRQ0BIAAoAgQgAUEBEM6CgIAADwsgAEEEahDagoCAAAsLAgALGAAgACgCABDtgoCAACAAKAIAEPCCgIAACw4AIABBFEEEEM6CgIAACxgAAkAgAC0AAEUNACAAQQRqEO+CgIAACwsgAQF/AkAgACgCBCIBRQ0AIAAoAgAgAUEBEM6CgIAACwsYAAJAIAAvAQBFDQAgAEEEahDvgoCAAAsLFQACQCAAKAIARQ0AIAAQ74KAgAALCwIACwoAIAAQ64KAgAALGAACQCAALQAARQ0AIABBBGoQ74KAgAALC98BAAJAAkACQAJAIAFBgAFJDQAgAUGAEEkNASABQYCABE8NAiACIAFBP3FBgAFyOgACIAIgAUEGdkE/cUGAAXI6AAEgAiABQQx2QQ9xQeABcjoAAEEDIQEMAwsgAiABOgAAQQEhAQwCCyACIAFBP3FBgAFyOgABIAIgAUEGdkEfcUHAAXI6AABBAiEBDAELIAIgAUE/cUGAAXI6AAMgAiABQRJ2QfABcjoAACACIAFBBnZBP3FBgAFyOgACIAIgAUEMdkE/cUGAAXI6AAFBBCEBCyAAIAE2AgQgACACNgIAC3IBAX8jgICAgABBIGsiBCSAgICAACAEIAM2AhQgBCACNgIQAkACQCABIANJDQAgBEEIaiAAIAEgAxCAg4CAACAEIAQpAwg3AxhBASEDIARBEGogBEEYahDmgoCAAA0BC0EAIQMLIARBIGokgICAgAAgAwsqAQF/IAAoAgAiASgCACABKAIEIAAoAgQoAgAgACgCCCgCABCShoCAAAALGgAgACgCACAAKAIEQQAgASgCABCShoCAAAALKgEBfyAAKAIAIgEoAgAgASgCBCAAKAIEKAIAIAAoAggoAgAQkoaAgAAAC1EBBH9BACECAkACQCABKAIAIgMgASgCBEkNAAwBCyADQQEQtoOAgABqIgQgA0kNACABKAIAIQUgASAENgIAQQEhAgsgACAFNgIEIAAgAjYCAAupBAEJfyOAgICAAEEQayIHJICAgIAAIAIgBWshCCABKAIYIQkCQAJAA0ACQCAJIAVrIgogA0kNAEEAIQsgAUEANgIYDAMLAkBCASACIApqMQAAQj+DhiABKQMAg1BFDQAgASAKNgIYIAohCSAGDQEgASAFNgIgIAohCQwBCyABKAIMIgwhCwJAIAYNACABKAIgIgsgDCAMIAtLGyELCyAIIAlqIQ0gC0F/aiELAkADQAJAIAtBf0cNACAHIAUgASgCICAGGzYCDCAHIAw2AggCQAJAA0AgByAHQQhqEP2CgIAAAkAgBygCAA0AIAEgASgCGCILIAVrIg42AhgCQCAGDQAgASAFNgIgCyAAIA42AgQgAEEIaiALNgIAQQEhCwwJCyAHKAIEIgsgBU8NASABKAIYIg8gBWsgC2oiDiADTw0CIAQgC2otAAAgAiAOai0AAEYNAAsgASAPIAEoAhAiC2siCTYCGCAGDQUgASALNgIgDAULQdCfwIAAIAsgBRCMhoCAAAALQeCfwIAAIA4gAxCMhoCAAAALIAsgBU8NASAKIAtqIANPDQMgDSALaiEOIAQgC2ohDyALQX9qIQsgDy0AACAOLQAARg0ACyABIAkgDGsgC2pBAWoiCTYCGCAGDQEgASAFNgIgDAELC0Gwn8CAACALIAUQjIaAgAAAC0HAn8CAACAJIAVrIAtqIAMQjIaAgAAACyAAIAs2AgAgB0EQaiSAgICAAAsXACAAQQA2AgggACACNgIEIAAgATYCAAs9AQF/I4CAgIAAQRBrIgQkgICAgAAgBEEIakEAIAMgASACENuCgIAAIAAgBCkDCDcCACAEQRBqJICAgIAACxAAIAAgASACIAMQ54KAgAALEwAgACABIAJqNgIEIAAgATYCAAtMAQF/IAEoAgQhAgJAIAEoAgBBAUcNACAAQQE2AgAgACACNgIEDwsgAEKAgICAEDcCACAAQQhqIAI2AgAgAEEMaiABQQhqKAIANgIAC0gBAX8gASgCBCECAkAgASgCAEEBRw0AIABBATYCACAAIAI2AgQPCyAAQgA3AgAgAEEIaiACNgIAIABBDGogAUEIaigCADYCAAtIAQF/I4CAgIAAQRBrIgEkgICAgAACQCAARQ0AQaScwIAAQTcgAUEIakGQocCAAEGkncCAABCqhoCAAAALIAFBEGokgICAgAALVwEBfyOAgICAAEEQayIDJICAgIAAAkAgAkUNACAAIAI2AgQgACABNgIAIANBEGokgICAgAAPC0GgocCAAEErIANBCGpBzKHAgABBhKPAgAAQqoaAgAAACywBAX4gACkCACEBQRRBBBDdgoCAACIAQgA3AgwgACABNwIEIABBATYCACAAC2sBAX8jgICAgABBEGsiAiSAgICAACAAKAIAIQACQAJAIAFBgAFJDQAgAkEANgIMIAIgASACQQxqEPiCgIAAIAAgAigCACACKAIEEImDgIAADAELIAAgARCKg4CAAAsgAkEQaiSAgICAAEEACxEAIAAgASABIAJqENOCgIAACzwAAkAgACgCCCAAKAIERw0AIABBARDUgoCAAAsgABCqhYCAACAAKAIIaiABOgAAIAAgACgCCEEBajYCCAtoAQF/I4CAgIAAQSBrIgIkgICAgAAgACgCACEAIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAAgAkEIahDqgoCAACEBIAJBIGokgICAgAAgAQsTACAAKAIAIAEgAhCJg4CAAEEACwQAIAALZgEBfyOAgICAAEEgayIDJICAgIAAIANBCGogAkEAEJaDgIAAIANBADYCGCADIAMpAwg3AxAgA0EQaiABIAIQiYOAgAAgAEEIaiADKAIYNgIAIAAgAykDEDcCACADQSBqJICAgIAAC0MBAX8jgICAgABBEGsiAiSAgICAACACQQhqIAAQq4WAgAAgAigCCCACKAIMIAEQ0IaAgAAhASACQRBqJICAgIAAIAELIAEBfwJAIAAoAgQgACgCCCIBRg0AIAAgARCRg4CAAAsLgwEBAX8CQAJAAkACQCAAKAIEIgIgAUkNAAJAIAFFDQAgAiABRg0EIAAoAgAgAkEBIAEQz4KAgAAiAg0CIAFBARCFhoCAAAALIAAQ8oKAgAAgAEEBNgIAQQAhAQwCC0G8o8CAAEEkQayiwIAAEI2GgIAAAAsgACACNgIACyAAIAE2AgQLC0cBAX8jgICAgABBEGsiAiSAgICAACABEJCDgIAAIAJBCGogASgCACABKAIEEJODgIAAIAAgAikDCDcCACACQRBqJICAgIAAC0gBAX8jgICAgABBEGsiAySAgICAACADIAI2AgwgAyABNgIIIAAgA0EIahCqhYCAADYCACAAIAMoAgw2AgQgA0EQaiSAgICAAAtBAQF/I4CAgIAAQRBrIgIkgICAgAACQCAAKAIIIAFJDQAgAkEIaiAAENWCgIAAIAAgATYCCAsgAkEQaiSAgICAAAtqAQF/I4CAgIAAQRBrIgMkgICAgAAgAyAAIAEgAkEBQQEQmIOAgAACQAJAIAMoAgBBAUcNACADQQhqKAIARQ0BQZSjwIAAQShBrKLAgAAQjYaAgAAACyADQRBqJICAgIAADwsQhoaAgAAAC6QBAQN/I4CAgIAAQRBrIgMkgICAgAACQAJAIAFBf0wNAAJAAkAgAQ0AQQEhAgwBCyADQQhqIAFBARCGg4CAACADKAIMIQQgAygCCCEFAkACQCACDQAgBSAEEM2CgIAAIQIMAQsgBSAEENCCgIAAIQILIAJFDQILIAAgATYCBCAAIAI2AgAgA0EQaiSAgICAAA8LEJeDgIAAAAsgBSAEEIWGgIAAAAsJABCGhoCAAAALhQIBAn9BACEGAkAgASgCBCIHIAJrIANPDQAgAiADaiIDIAJJIQICQAJAAkACQCAFRQ0AIAJFDQEgACADNgIEIABBCGpBADYCAAwDCyACRQ0BIAAgAzYCBCAAQQhqQQA2AgAMAgsgB0EBdCICIAMgAiADSxshAwsCQCADQX9KDQAgAEEIakEANgIADAELAkACQCAHDQAgA0EBEM2CgIAAIQIMAQsgASgCACAHQQEgAxDPgoCAACECCwJAAkAgAg0AIARFDQEgA0EBEIWGgIAAAAsgASADNgIEIAEgAjYCAAwCCyAAIAM2AgRBASEGIABBCGpBATYCAAwBC0EBIQYLIAAgBjYCAAtNAQF/I4CAgIAAQRBrIgMkgICAgAAgAyABIAIQjoOAgAAgAEEANgIAIABBDGogA0EIaigCADYCACAAIAMpAwA3AgQgA0EQaiSAgICAAAscACAAIAEpAgA3AgAgAEEIaiABQQhqKAIANgIACy0BAX8jgICAgABBEGsiASSAgICAACABQQhqIAAQ1YKAgAAgAUEQaiSAgICAAAsKACAAEPKCgIAACzUAIABBgAE6ABggAEIBNwIMIAAgASkCADcCACAAQRRqQQA2AgAgAEEIaiABQQhqKAIANgIAC4wBAgF/AX4jgICAgABBEGsiAySAgICAAAJAAkACQAJAIAAoAgAOAwABAgALIAApAwghBCADQQM6AAAgAyAENwMIDAILIAApAwghBCADQQE6AAAgAyAENwMIDAELIAApAwghBCADQQI6AAAgAyAENwMICyADIAEgAhCfg4CAACEAIANBEGokgICAgAAgAAvhAQEBfyOAgICAAEEwayIDJICAgIAAIAMgAjYCBCADIAE2AgACQAJAIAAtAABBB0YNACADQSxqQYWAgIAANgIAIANBHGpBAjYCACADQgI3AgwgA0GUvMCAADYCCCADQdaAgIAANgIkIAMgADYCICADIANBIGo2AhggAyADNgIoIANBCGoQpYOAgAAhAAwBCyADQRxqQQE2AgAgA0IBNwIMIANBxLzAgAA2AgggA0GFgICAADYCJCADIANBIGo2AhggAyADNgIgIANBCGoQpYOAgAAhAAsgA0EwaiSAgICAACAAC00BA39BACECAkAgASgCCCIDIAEoAgRPDQAgASgCACADai0AACEEQQEhAiABIANBAWo2AggLIAAgAjoAASAAQQA6AAAgAEECaiAEOgAACzwBAX8jgICAgABBEGsiAiSAgICAACACQQhqIAEgASgCCBCog4CAACAAIAIpAwg3AgAgAkEQaiSAgICAAAs4AQF/QRRBBBDdgoCAACIDIAI2AhAgAyABNgIMIANBCGogAEEIaigCADYCACADIAApAgA3AgAgAwtwAQN/I4CAgIAAQRBrIgIkgICAgAACQCABKAIIIgNBAWoiBCADSQ0AIAJBCGogASAEIAEoAgQiAyADIARLGxCog4CAACAAIAIpAwg3AgAgAkEQaiSAgICAAA8LQcCdwIAAQRxB2MDAgAAQjYaAgAAAC88CAgF/AX4jgICAgABB0ABrIgIkgICAgAAgAiAAKAIAIgA2AjQgAkEANgIYIAJCATcDECACQThqQRRqQQE2AgAgAkIBNwI8IAJBnJzAgAA2AjggAkHXgICAADYCLCACIAJBKGo2AkggAiACQTRqNgIoIAJBEGogAkE4ahDqgoCAABCFg4CAACACQRBqEJCDgIAAIAJBMGogAigCGDYCACACIAIpAxA3AyggAkEIaiAAQQxqQYSAgIAAELiDgIAAIAIpAwghAyACIABBEGpBhICAgAAQuIOAgAAgAkEQakEUakEDNgIAIAIgAzcDQCACQdiAgIAANgI8IAJCBDcCFCACQdi7wIAANgIQIAIgAikDADcDSCACIAJBKGo2AjggAiACQThqNgIgIAEgAkEQahDJhoCAACEAIAJBKGoQ64KAgAAgAkHQAGokgICAgAAgAAu5AQEBfyOAgICAAEHAAGsiASSAgICAACABIAA2AgwgAUEANgIYIAFCATcDECABQTRqQQE2AgAgAUIBNwIkIAFBnJzAgAA2AiAgAUHZgICAADYCPCABIAFBOGo2AjAgASABQQxqNgI4IAFBEGogAUEgahDqgoCAABCFg4CAACABQRBqEJCDgIAAIAFBKGogASgCGDYCACABIAEpAxA3AyAgAUEgahCmg4CAACEAIAFBwABqJICAgIAAIAALgwsCCn8BfiOAgICAAEGgAWsiASSAgICAACABQcAAaiAAEKuFgIAAIAFB2ABqIAEoAkAgASgCREGtu8CAAEEJENWGgIAAAkACQAJAAkACQCABKAJYQQFGDQAgAUHlAGotAAAhAiABQeAAaigCACEDIAFBjAFqKAIAIQQgASgCiAEhBQJAA0AgASAENgJMIAEgBTYCSCABIAM2ApwBAkAgA0UNACAEIANGDQAgBCADTQ0FIAUgA2osAABBv39MDQULIAJB/wFxIQYCQAJAIANFDQAgBSADaiIHQX9qIgItAAAiCEEYdEEYdSIJQQBODQECQAJAIAUgAkcNAEEAIQgMAQsCQCAHQX5qIgItAAAiCEHAAXFBgAFGDQAgCEEfcSEIDAELAkACQCAFIAJHDQBBACECDAELAkAgB0F9aiIKLQAAIgJBwAFxQYABRg0AIAJBD3EhAgwBCwJAAkAgBSAKRw0AQQAhBwwBCyAHQXxqLQAAQQdxQQZ0IQcLIAcgAkE/cXIhAgsgAkEGdCAIQT9xciEICyAIQQZ0IAlBP3FyIQgMAQsQqYWAgABBgIDEACEICyAGRSECAkAgBg0AIAhBgIDEAEYNAkEBIQYCQCAIQYABSQ0AQQIhBiAIQYAQSQ0AQQNBBCAIQYCABEkbIQYLIAMgBmshAwwBCwsgAUHQAGogAzYCACABIAM2AmAgASACOgBlIAEgAzYCTCABQQE2AkgMAgsgASADNgJgIAEgAjoAZSABQQA2AkgMAQsgAUHgAGohAyABQZQBaigCACEIIAFBjAFqKAIAIQYgASgCkAEhBSABKAKIASEEAkAgAUH8AGooAgBBf0cNACABQcgAaiADIAQgBiAFIAhBARD+goCAAAwBCyABQcgAaiADIAQgBiAFIAhBABD+goCAAAtBACEGQQAhCCABKAJIRQ0CAkACQAJAAkAgASgCTCIFQQlqIgIgBUkNACAFQRFqIQMCQANAIAFBOGogACADQXhqIggQ14KAgAACQAJAIAEoAjxFDQAgASgCOC0AAEFQakH/AXFBCkkNAQsgAUEwaiAAIANBeGoiBBDXgoCAAEEAIQZBACEIIAEoAjAgASgCNEG2u8CAAEEIEPmCgIAADQIMCQsgCEEBaiAISQ0DIANBAWohAwwACwsgBEEIaiIJIARJDQICQANAIAFBKGogACADENeCgIAAAkACQCABKAIsRQ0AIAEoAigtAABBUGpB/wFxQQpJDQELQQAhBkEAIQggAyAAKAIISQ0JIAFBIGogACACIAQQ0YKAgAAgAUHYAGogASgCICABKAIkEN2GgIAAIAEtAFhBAUcNAgwICyADQQFqIgggA0kNBSAIIQMMAAsLIAEoAlwhCCABQRhqIAAgCSADENGCgIAAIAFB2ABqIAEoAhggASgCHBDdhoCAACABLQBYQQFGDQUgASgCXCEGIAAoAgggBUkNBiABQRBqIAAQq4WAgAACQAJAIAVFDQAgASgCFCIDIAVGDQAgAyAFTQ0BIAEoAhAgBWosAABBQEgNAQsgACAFEJSDgIAADAcLQdyhwIAAQTBBrKLAgAAQjYaAgAAAC0HAncCAAEEcQcy8wIAAEI2GgIAAAAtBwJ3AgABBHEHcvMCAABCNhoCAAAALQcCdwIAAQRxB7LzAgAAQjYaAgAAAC0HAncCAAEEcQfy8wIAAEI2GgIAAAAsgAUHIAGogAUGcAWoQ+4KAgAAAC0EAIQZBACEICyABQdgAakEIaiAAQQhqKAIANgIAIAEgACkCADcDWCABQQhqIAFB2ABqEJKDgIAAIAEpAwghC0EUQQQQ3YKAgAAiAyAGNgIQIAMgCDYCDCADIAs3AgQgA0EANgIAIAFBoAFqJICAgIAAIAMLFwAgAEEANgIIIAAgAjYCBCAAIAE2AgAL4AEBBX8jgICAgABBEGsiAySAgICAACADQQhqIAEoAgAgASgCBCACEICDgIAAIAMoAggiBCADKAIMaiEFQQEhBgJAAkADQCAFIARrIQdBACEBAkADQCAHIAFGDQQCQCAEIAFqIgItAABBCkYNACABQQFqIgIgAUkNAiACIQEMAQsLIAZBAWoiASAGSQ0CIAJBAWohBCABIQYMAQsLQcCdwIAAQRxB+L/AgAAQjYaAgAAAC0HAncCAAEEcQYjAwIAAEI2GgIAAAAsgACABNgIEIAAgBjYCACADQRBqJICAgIAAC/AEAQZ/I4CAgIAAQTBrIgMkgICAgAADQCABKAIEIQQgASgCCCIFIQYDQAJAAkACQCAGIARJDQBBACEHDAELIAEoAgAgBmotAABBqMHAgABqLQAARQ0BQQEhBwsCQAJAIAQgBkYNAAJAAkACQAJAIAdFDQACQAJAAkAgASgCACIHIAZqLQAAIghB3ABGDQAgCEEiRw0BIAIoAgghCCADQRBqIAUgBiAHIAQQ24KAgAAgAygCFCEGIAMoAhAhBAJAIAhFDQAgAiAEIAYQiYOAgAAgASgCCCIGQQFqIgQgBkkNByABIAQ2AgggA0EIaiACEKuFgIAAIANBIGogASADKAIIIAMoAgwQqoOAgAAgACADQSBqEIODgIAADAkLIAEoAggiAkEBaiIHIAJJDQUgASAHNgIIIANBIGogASAEIAYQqoOAgAAgACADQSBqEISDgIAADAgLIANBGGogBSAGIAcgBBDbgoCAACACIAMoAhggAygCHBCJg4CAACABKAIIIgZBAWoiBCAGSQ0DIAEgBDYCCCADIAEgAhCrg4CAACIGNgIgIAYNASADQSBqEPSCgIAADAoLIAEgBkEBajYCCCADQQ82AiAgACABIANBIGoQrIOAgAAMBgsgAEEBNgIAIAAgBjYCBAwFC0GYwMCAACAGIAQQjIaAgAAAC0HAncCAAEEcQajAwIAAEI2GgIAAAAtBwJ3AgABBHEG4wMCAABCNhoCAAAALQcCdwIAAQRxByMDAgAAQjYaAgAAACyADQQQ2AiAgACABIANBIGoQrIOAgAALIANBMGokgICAgAAPCyABIAZBAWoiBjYCCAwACwsLZQEBfyOAgICAAEEgayIEJICAgIAAIAQgAiADENeGgIAAAkACQCAEKAIAQQFGDQAgACAEKQIENwIEIABBADYCAAwBCyAEQQ42AhAgACABIARBEGoQtYOAgAALIARBIGokgICAgAALxAcBA38jgICAgABBIGsiAiSAgICAACACQRBqIAAQs4OAgAACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAItABBBAUYNACACLQARIQMgAkEQahD3goCAACADQZJ/aiIEQQdNDQQgA0Gef2oiBEEETQ0FIANBIkYNASADQS9GDQMgA0HcAEYNAgwMCyACKAIUIQAMDAsgAUEiEIqDgIAAQQAhAAwLCyABQdwAEIqDgIAAQQAhAAwKCyABQS8QioOAgABBACEADAkLIAQOCAQHBwcDBwIBBAsgBA4FBQYGBgQFCyACQQhqIAAQsIOAgAACQAJAAkACQAJAAkACQAJAIAIvAQhBAUYNAAJAAkACQCACLwEKIgRBgPgDcSIDQYCwA0YNACADQYC4A0cNASACQRE2AhAgACACQRBqEK+DgIAAIQAMCgsgAkEQaiAAELODgIAAIAItABBBAUYNByACLQARIQMgAkEQahD3goCAACADQdwARw0DIAJBEGogABCzg4CAACACLQAQQQFGDQcgAi0AESEDIAJBEGoQ94KAgAAgA0H1AEcNBiACQRBqIAAQsIOAgAAgAi8BEEEBRg0HIAIvARIhAyACQRBqEPOCgIAAIANBgPgDcUGAuANHDQggBEGA0HxqIgRB//8DcSAERw0EIANBgMh8aiIDQf//A3EgA0cNBQJAIARB//8DcUEKdCADQf//A3FyQYCABGoiBEH//8MASw0AIARBgPD/P3FBgLADRw0CCyACQQ42AhAgACACQRBqEK+DgIAAIQAMCQsgBEGA8ANxQYCwA0cNACACQQ42AhAgACACQRBqEK+DgIAAIQAMCAsgAkEIahDzgoCAAEEAIQAgAkEANgIQIAIgBCACQRBqEPiCgIAAIAEgAigCACACKAIEEImDgIAADA4LIAIoAgwhAAwNCyACQRQ2AhAgACACQRBqEK+DgIAAIQAMBQtB4J3AgABBIUGow8CAABCNhoCAAAALQeCdwIAAQSFBuMPAgAAQjYaAgAAACyACQRQ2AhAgACACQRBqEK+DgIAAIQAMAgsgAigCFCEADAELIAJBETYCECAAIAJBEGoQr4OAgAAhAAsgAkEIahDzgoCAAAwGCyABQQkQioOAgABBACEADAULIAFBDRCKg4CAAEEAIQAMBAsgAUEKEIqDgIAAQQAhAAwDCyABQQwQioOAgABBACEADAILIAFBCBCKg4CAAEEAIQAMAQsgAkELNgIQIAAgAkEQahCvg4CAACEACyACQSBqJICAgIAAIAALdwECfyOAgICAAEEgayIDJICAgIAAIANBCGogARChg4CAACADKAIMIQEgAygCCCEEIANBEGpBCGogAkEIaigCADYCACADIAIpAgA3AxAgA0EQaiAEIAEQooOAgAAhAiAAQQE2AgAgACACNgIEIANBIGokgICAgAALkwIBBH8jgICAgABBEGsiASSAgICAAAJAA0AgACgCBCECIAAoAgghAwJAA0ACQAJAAkAgAyACSQ0AQQAhBAwBCyAAKAIAIANqLQAAQajBwIAAai0AAEUNAUEBIQQLAkACQCACIANGDQAgBA0BQejAwIAAIAMgAhCMhoCAAAALIAFBBDYCAAwDCwJAIAAoAgAgA2otAAAiAkHcAEYNAAJAIAJBIkYNACABQQ82AgAMBAsgACADQQFqNgIIQQAhAwwFCyAAIANBAWo2AgggASAAEK6DgIAAIgM2AgAgAw0EIAEQ9IKAgAAMAwsgACADQQFqIgM2AggMAAsLCyAAIAEQr4OAgAAhAwsgAUEQaiSAgICAACADC9oFAQR/I4CAgIAAQSBrIgEkgICAgAAgAUEQaiAAELODgIAAAkACQAJAAkACQAJAIAEtABBBAUYNACABLQARIQIgAUEQahD3goCAAEEAIQMgAkGSf2oiBEEHTQ0BIAJBnn9qIgRBBE0NAiACQSJGDQUgAkEvRg0FIAJB3ABGDQUMBAsgASgCFCEDDAQLIAQOCAMCAgIDAgMBAwsgBA4FAgEBAQICCyABQQhqIAAQsIOAgAACQAJAAkACQAJAAkACQAJAIAEvAQhBAUYNAAJAAkAgAS8BCiIEQYD4A3EiAkGAsANGDQAgAkGAuANHDQEgAUERNgIQIAAgAUEQahCvg4CAACEDDAkLIAFBEGogABCzg4CAACABLQAQQQFGDQYgAS0AESECIAFBEGoQ94KAgAAgAkHcAEcNAiABQRBqIAAQs4OAgAAgAS0AEEEBRg0GIAEtABEhAiABQRBqEPeCgIAAIAJB9QBHDQUgAUEQaiAAELCDgIAAIAEvARBBAUYNBiABLwESIQIgAUEQahDzgoCAACACQYD4A3FBgLgDRw0HIARBgNB8aiIEQf//A3EgBEcNAyACQYDIfGoiAkH//wNxIAJHDQQgBEH//wNxQQp0IAJB//8DcXJBgIAEaiEECyABQQhqEPOCgIAAAkAgBEH//8MASw0AIARBgPD/P3FBgLADRw0KCyABQQ42AhAgACABQRBqEK+DgIAAIQMMCQsgASgCDCEDDAgLIAFBFDYCECAAIAFBEGoQr4OAgAAhAwwFC0HgncCAAEEhQcjDwIAAEI2GgIAAAAtB4J3AgABBIUHYw8CAABCNhoCAAAALIAFBFDYCECAAIAFBEGoQr4OAgAAhAwwCCyABKAIUIQMMAQsgAUERNgIQIAAgAUEQahCvg4CAACEDCyABQQhqEPOCgIAADAELIAFBCzYCECAAIAFBEGoQr4OAgAAhAwsgAUEgaiSAgICAACADC2sBAn8jgICAgABBIGsiAiSAgICAACACQQhqIAAQoYOAgAAgAigCDCEAIAIoAgghAyACQRBqQQhqIAFBCGooAgA2AgAgAiABKQIANwMQIAJBEGogAyAAEKKDgIAAIQEgAkEgaiSAgICAACABC8wCAQh/I4CAgIAAQSBrIgIkgICAgAACQAJAAkACQAJAIAEoAggiA0EEaiIEIANJDQAgBCABKAIEIgVLDQNBACEEQQAhBgNAAkAgBEEERw0AIABBADsBACAAIAY7AQIMBgsgAyAEaiIHIAVPDQIgAkEIaiABKAIAIANqIARqLQAAELGDgIAAIAIvAQohCCACLwEIIQkgASAHQQFqNgIIAkAgCQ0AIAJBCzYCECAAIAEgAkEQahCyg4CAAAwGCyAGQQR0QfD/A3EgCEH//wNxaiIGQf//A3EgBkcNAyAEQQFqIQQMAAsLQcCdwIAAQRxB+MDAgAAQjYaAgAAAC0GIwcCAACAHIAUQjIaAgAAAC0HAncCAAEEcQZjBwIAAEI2GgIAAAAsgASAFNgIIIAJBBDYCECAAIAEgAkEQahCyg4CAAAsgAkEgaiSAgICAAAskACAAIAFB/wFxQejDwIAAai0AACIBOwECIAAgAUH/AUc7AQALegECfyOAgICAAEEgayIDJICAgIAAIANBCGogARChg4CAACADKAIMIQEgAygCCCEEIANBEGpBCGogAkEIaigCADYCACADIAIpAgA3AxAgA0EQaiAEIAEQooOAgAAhAiAAQQE7AQAgAEEEaiACNgIAIANBIGokgICAgAALlwEBAX8jgICAgABBIGsiAiSAgICAACACQQhqIAEQoIOAgAACQAJAIAItAAhBAUYNAAJAAkAgAi0ACUEBRg0AIAJBBDYCECAAIAEgAkEQahC0g4CAAAwBCyAAIAItAAo6AAEgAEEAOgAACyACQQhqEPGCgIAADAELIABBAToAACAAQQRqIAIoAgw2AgALIAJBIGokgICAgAALegECfyOAgICAAEEgayIDJICAgIAAIANBCGogARChg4CAACADKAIMIQEgAygCCCEEIANBEGpBCGogAkEIaigCADYCACADIAIpAgA3AxAgA0EQaiAEIAEQooOAgAAhAiAAQQE6AAAgAEEEaiACNgIAIANBIGokgICAgAALdwECfyOAgICAAEEgayIDJICAgIAAIANBCGogARChg4CAACADKAIMIQEgAygCCCEEIANBEGpBCGogAkEIaigCADYCACADIAIpAgA3AxAgA0EQaiAEIAEQooOAgAAhAiAAQQE2AgAgACACNgIEIANBIGokgICAgAALBAAgAAtDACAAKAIAIQACQCABEMqGgIAADQACQCABEMuGgIAADQAgACABEJyGgIAADwsgACABEOGGgIAADwsgACABENuGgIAACxAAIAAgAjYCBCAAIAE2AgALUgEBfwJAAkAgACABaiICIABJDQAgAkF/aiIAIAJLDQEgACABbg8LQeDGwIAAQRxBxMbAgAAQjYaAgAAAC0GQx8CAAEEhQfzGwIAAEI2GgIAAAAsOACAAQYCABBC5g4CAAAsMACAAQQQQuYOAgAALAgALKwACQCAAQf//A3EgAEYNAEHQx8CAAEEhQbTHwIAAEI2GgIAAAAsgAEEQdAstAAJAIABB/////wNxIABGDQBB0MfAgABBIUG0x8CAABCNhoCAAAALIABBAnQLJQACQCAAQXhqIABLDQAPC0GQx8CAAEEhQZTIwIAAEI2GgIAAAAt5AQJ/AkAgACgCACIBQXxxIgJFDQAgAUECcQ0AIAIgAigCBEEDcSAAKAIEQXxxcjYCBAsCQCAAKAIEIgJBfHEiAUUNACABIAEoAgBBA3EgACgCAEF8cXI2AgAgACgCBCECCyAAIAJBA3E2AgQgACAAKAIAQQNxNgIACzYBAX8CQCAAKAIAQXxxIgEgAGtBeGoiACABSw0AIAAPC0GQx8CAAEEhQYDJwIAAEI2GgIAAAAuSAgEBfyACEL6DgIAAIQQCQAJAAkACQCADQcAAaiICIANJDQAgAkH/////AXEgAkcNASAEIAJBA3QiAiAEIAJLGyICQQhqIgMgAkkNAgJAAkAgAxC6g4CAACIDQAAiAkF/Rw0AQQEhAwwBCyACQf//A3EgAkcNBCADEL2DgIAAIgQQv4OAgAAgAkEQdCICQgA3AwBBACEDIAJBADYCCCACIAIgBGpBAnI2AgALIAAgAjYCBCAAIAM2AgAPC0HgxsCAAEEcQfTHwIAAEI2GgIAAAAtB0MfAgABBIUGEyMCAABCNhoCAAAALQeDGwIAAQRxB9MfAgAAQjYaAgAAAC0HQx8CAAEEhQdjKwIAAEI2GgIAAAAsEAEEQCwQAQQELpgUBCX8gAUF/aiIFQX9zIQYgBSABSyEHIAIoAgAhAUEAIQgCQANAIAFFDQECQANAAkAgASgCCCIJQQFxDQAgAUEIaiEJIAAQvoOAgAAhCiABEMGDgIAAIApJDQICQAJAAkACQAJAAkAgASgCAEF8cSILIAprIgwgC0sNACAHDQEgAyAAIAQoAhARgICAgAAAEL6DgIAAIQ0gCUEIaiIKIAlJDQIgCiANaiINIApJDQMCQAJAIA0gDCAGcSIKTQ0AIAUgCXENCiACIAEoAghBfHE2AgAgASEJDAELIApBeGoiCSAKSw0FIAsgCWsiAiALSw0GIAIQv4OAgAAgCUEANgIIIAlCADcCACAJIAEoAgBBfHE2AgACQCABKAIAIgpBfHEiAkUNACAKQQJxDQAgAiACKAIEQQNxIAlyNgIECyAJIAkoAgRBA3EgAXI2AgQgASABKAIIQX5xNgIIIAEgASgCAEEDcSAJciICNgIAIAJBAnFFDQAgASACQX1xNgIAIAkgCSgCAEECcjYCAAsgCSAJKAIAQQFyNgIAIAlBCGohCAwKC0GQx8CAAEEhQZDJwIAAEI2GgIAAAAtBkMfAgABBIUGgycCAABCNhoCAAAALQeDGwIAAQRxBsMnAgAAQjYaAgAAAC0HgxsCAAEEcQbDJwIAAEI2GgIAAAAtBkMfAgABBIUHAycCAABCNhoCAAAALQZDHwIAAQSFB0MnAgAAQjYaAgAAACyABIAlBfnE2AggCQAJAIAEoAgRBfHEiCQ0AQQAhCQwBC0EAIAkgCS0AAEEBcRshCQsgARDAg4CAAAJAIAEtAABBAnFFDQAgCSAJKAIAQQJyNgIACyACIAk2AgAgCRDBg4CAABogCSEBDAALCyACIAkoAgAiATYCAAwACwsgCAvMAQECfyOAgICAAEEQayIDJICAgIAAIAJBASACGyECAkACQCABRQ0AIAEQu4OAgAAhBCADIAAoAgA2AgwCQCAEIAIgA0EMakHgycCAAEHgycCAABDFg4CAACIBDQAgA0HgycCAACAEIAIQwoOAgABBACEBIAMoAgANACADKAIEIgEgAygCDDYCCCADIAE2AgwgBCACIANBDGpB4MnAgABB4MnAgAAQxYOAgAAhAQsgACADKAIMNgIADAELIAIhAQsgA0EQaiSAgICAACABC+cBAQN/AkAgAUUNACACRQ0AIAIQu4OAgAAaIAAoAgAhBCABQXhqIgIgAigCAEF+cTYCACACEMGDgIAAGiABQQA2AgACQAJAAkACQCABQXxqKAIAQXxxIgVFDQAgBS0AAEEBcUUNAQsgAigCACIFQXxxIgZFDQEgBUECcQ0BIAYtAABBAXENASABIAYoAghBfHE2AgAgBiACQQFyNgIIIAQhAgwCCyACEMCDgIAAAkAgAi0AAEECcUUNACAFIAUoAgBBAnI2AgALIAUQwYOAgAAaIAQhAgwBCyABIAQ2AgALIAAgAjYCAAsLUwECfyAAIAEoAgwgASgCCCICayIDEI2FgIAAIAAoAgggABCbhYCAAGogAiADEPOGgIAAGiABIAEoAgw2AgggACAAKAIIIANqNgIIIAEQyYOAgAALbgEBfyOAgICAAEEgayIBJICAgIAAIAEgADYCGANAIAFBEGogAUEYahDMhICAACABLQAQQQFxDQALIAFBCGogACgCACAAKAIEEMuFgIAAIAEgASkDCDcDGCABQRhqENCFgIAAIAFBIGokgICAgAALOgEBf0EMQQQQy4OAgAAiAkEIaiABQQhqKAIANgIAIAIgASkCADcCACAAQejKwIAANgIEIAAgAjYCAAsuAQF/AkACQCAARQ0AIAAgARDNgoCAACICDQEgACABEIWGgIAAAAsgASECCyACCxQAIABBKDYCBCAAQZDLwIAANgIACwkAIABBADYCAAsJACAAQQA2AgALDQBCor2vyfH32uXXAAsEAEEAC0oBAX8jgICAgABBEGsiAiSAgICAACACQQhqIAFBCGooAgA2AgAgAiABKQIANwMAIAAgAhDSg4CAABDTg4CAACACQRBqJICAgIAAC9gBAwF/AX4DfyOAgICAAEEQayICJICAgIAAAkACQCAAKAIAEYKAgIAAACIADQAQwYWAgABBASEBDAELIAE1AgQhAyABKAIIIQQgASgCACEFIAIgABCLhICAACACIAIoAgQ2AgwgAiACKAIAIgA2AghBACEBIABBACAAKAIAG0HY18CAABCMhICAACIAKAIEIQYgACgCACAFKQMAIAMgBCgCACIANQIIIAAQm4WAgACtIAYoApQBEYOAgIAAACACQQhqQQRyEI2EgIAACyACQRBqJICAgIAAIAELSQEBfyOAgICAAEEQayIBJICAgIAAAkAgAEUNAEG4y8CAAEHGACABQQhqQbzRwIAAQcjMwIAAEKqGgIAAAAsgAUEQaiSAgICAAAtMAQF/I4CAgIAAQRBrIgIkgICAgAACQCAApw0AIAJBEGokgICAgAAgAQ8LQbjLwIAAQcYAIAJBCGpBvNHAgABByMzAgAAQqoaAgAAACxAAIAAQ1oOAgAAQ04OAgAALogEBAn8jgICAgABBEGsiASSAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAAwBCyABIAAQi4SAgAAgASABKAIENgIMIAEgASgCACICNgIIQQAhACACQQAgAigCABtBiNbAgAAQjISAgAAiAigCAEIAIAIoAgQoAiQRhICAgAAAIAFBCGpBBHIQjYSAgAALIAFBEGokgICAgAAgAAsSACAAIAEQ2IOAgAAQ04OAgAALpgEBAn8jgICAgABBEGsiAiSAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAAwBCyACIAAQi4SAgAAgAiACKAIENgIMIAIgAigCACIDNgIIQQAhACADQQAgAygCABtBmNfAgAAQjISAgAAiAygCACABKQMAIAMoAgQoAoQBEYSAgIAAACACQQhqQQRyEI2EgIAACyACQRBqJICAgIAAIAALQgIBfwF+I4CAgIAAQRBrIgIkgICAgAAgAiAAIAEQ2oOAgAAgAikDACACKQMIENSDgIAAIQMgAkEQaiSAgICAACADC70BAgF/An4jgICAgABBEGsiAySAgICAAAJAAkAgASgCABGCgICAAAAiAQ0AEMGFgIAAQgEhBAwBCyADIAEQi4SAgAAgAyADKAIENgIMIAMgAygCACIBNgIIQgAhBCABQQAgASgCABtBsNrAgAAQjISAgAAiASgCACACNQIEIAI1AgBCACABKAIEKAK4ARGFgICAAAAhBSADQQhqQQRyEI2EgIAACyAAIAU3AwggACAENwMAIANBEGokgICAgAALYwEBfyOAgICAAEEgayICJICAgIAAIAJBCGpBEGogAUEQaigCADYCACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAAgAkEIahDcg4CAABDTg4CAACACQSBqJICAgIAAC+gBAwJ/AX4DfyOAgICAAEEQayICJICAgIAAAkACQCAAKAIAEYKAgIAAACIDDQAQwYWAgABBASEBDAELIAE1AgwhBCABKAIAIQUgASgCBCEAIAEoAgghBiABKAIQIQcgAiADEIuEgIAAIAIgAigCBDYCDCACIAIoAgAiAzYCCEEAIQEgA0EAIAMoAgAbQbjXwIAAEIyEgIAAIgMoAgAgBSkDACAANQIEIAA1AgAgBjUCBCAGNQIAIAQgBykDACADKAIEKAKMARGGgICAAAAgAkEIakEEchCNhICAAAsgAkEQaiSAgICAACABCxQAIAAgASACEN6DgIAAENODgIAAC6kBAQJ/I4CAgIAAQRBrIgMkgICAgAACQAJAIAAoAgARgoCAgAAAIgANABDBhYCAAEEBIQAMAQsgAyAAEIuEgIAAIAMgAygCBDYCDCADIAMoAgAiBDYCCEEAIQAgBEEAIAQoAgAbQcjXwIAAEIyEgIAAIgQoAgAgASkDACACrSAEKAIEKAKQARGHgICAAAAgA0EIakEEchCNhICAAAsgA0EQaiSAgICAACAACxQAIAAgASACEOCDgIAAENODgIAAC6gBAQF/I4CAgIAAQSBrIgMkgICAgAAgAyACNgIUIAMgATYCEAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAIANBEGoQkISAgABBASEBDAELIANBCGogABCOhICAACADIAMoAgw2AhwgAyADKAIIIgA2AhggABCRhICAACAAIAI2AgQgACABNgIAIANBGGpBBHIQj4SAgABBACEBCyADQSBqJICAgIAAIAELYwEBfyOAgICAAEEgayICJICAgIAAIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAAgAkEIahDig4CAABDTg4CAACACQSBqJICAgIAAC7UCBAJ/AX4FfwR+I4CAgIAAQSBrIgIkgICAgAACQAJAIAAoAgARgoCAgAAAIgMNABDBhYCAAEEBIQEMAQsgATUCDCEEIAEoAhQhACABKAIQIQUgASgCCCEGIAEoAgQhByABKAIAIQggAkEQaiADEIuEgIAAIAIgAigCFDYCHCACIAIoAhAiAzYCGEEAIQEgA0EAIAMoAgAbQfjXwIAAEIyEgIAAIgMoAgAhCSADKAIEIQMgCCkDACEKIAcoAgAiBzUCCCELIAcQm4WAgAAhByAGKQMAIQwgBSgCACIFNQIIIQ0gAkEIaiAFEM+FgIAAIAkgCiALIAetIAwgBCANIAI1AgggADUCBCAANQIAIAMoApwBEYiAgIAAACACQRhqQQRyEI2EgIAACyACQSBqJICAgIAAIAELSgEBfyOAgICAAEEQayICJICAgIAAIAJBCGogAUEIaigCADYCACACIAEpAgA3AwAgACACEOSDgIAAENODgIAAIAJBEGokgICAgAAL1wEBBX8jgICAgABBEGsiAiSAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAQwBCyABKAIIIQMgASgCBCEEIAEoAgAhBSACIAAQi4SAgAAgAiACKAIENgIMIAIgAigCACIANgIIQQAhASAAQQAgACgCABtB6NfAgAAQjISAgAAiACgCBCEGIAAoAgAgBSkDACAEKAIAIgA1AgggABCbhYCAAK0gAykDACAGKAKYARGDgICAAAAgAkEIakEEchCNhICAAAsgAkEQaiSAgICAACABCxAAIAAQ5oOAgAAQ04OAgAALogEBAn8jgICAgABBEGsiASSAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAAwBCyABIAAQi4SAgAAgASABKAIENgIMIAEgASgCACICNgIIQQAhACACQQAgAigCABtB6NXAgAAQjISAgAAiAigCAEIAIAIoAgQoAiARhICAgAAAIAFBCGpBBHIQjYSAgAALIAFBEGokgICAgAAgAAtEAgF/AX4jgICAgABBEGsiAySAgICAACADIAAgASACEOiDgIAAIAMpAwAgAykDCBDUg4CAACEEIANBEGokgICAgAAgBAvHAQIBfwJ+I4CAgIAAQRBrIgQkgICAgAACQAJAIAEoAgARgoCAgAAAIgENABDBhYCAAEIBIQUMAQsgBCABEIuEgIAAIAQgBCgCBDYCDCAEIAQoAgAiATYCCCABQQAgASgCABtBgNrAgAAQjISAgAAiASgCACACNQIEIAI1AgAgAzUCBCADNQIAQn4gASgCBCgCtAERiYCAgAAAIQYgBEEIakEEchCNhICAAEIAIQULIAAgBjcDCCAAIAU3AwAgBEEQaiSAgICAAAtAAgF/AX4jgICAgABBEGsiASSAgICAACABIAAQ6oOAgAAgASkDACABKQMIENSDgIAAIQIgAUEQaiSAgICAACACC7EBAgF/An4jgICAgABBEGsiAiSAgICAAAJAAkAgASgCABGCgICAAAAiAQ0AEMGFgIAAQgEhAwwBCyACIAEQi4SAgAAgAiACKAIENgIMIAIgAigCACIBNgIIIAFBACABKAIAG0Go2MCAABCMhICAACIBKAIAIAEoAgQoAqgBEYqAgIAAACEEIAJBCGpBBHIQjYSAgABCACEDCyAAIAQ3AwggACADNwMAIAJBEGokgICAgAALEgAgACABEOyDgIAAENODgIAAC6MBAQJ/I4CAgIAAQRBrIgIkgICAgAACQAJAIAAoAgARgoCAgAAAIgANABDBhYCAAEEBIQAMAQsgAiAAEIuEgIAAIAIgAigCBDYCDCACIAIoAgAiAzYCCEEAIQAgA0EAIAMoAgAbQZjWwIAAEIyEgIAAIgMoAgAgAa0gAygCBCgCQBGEgICAAAAgAkEIakEEchCNhICAAAsgAkEQaiSAgICAACAAC0ICAX8BfiOAgICAAEEQayICJICAgIAAIAIgACABEO6DgIAAIAIpAwAgAikDCBDUg4CAACEDIAJBEGokgICAgAAgAwu9AQIBfwJ+I4CAgIAAQRBrIgMkgICAgAACQAJAIAEoAgARgoCAgAAAIgENABDBhYCAAEIBIQQMAQsgAyABEIuEgIAAIAMgAygCBDYCDCADIAMoAgAiATYCCCABQQAgASgCABtB0NrAgAAQjISAgAAiASgCACACNQIEIAI1AgBCfiABKAIEKAK8ARGFgICAAAAhBSADQQhqQQRyEI2EgIAAQgAhBAsgACAFNwMIIAAgBDcDACADQRBqJICAgIAACxQAIAAgASACEPCDgIAAENODgIAAC9cBAgN/An4jgICAgABBIGsiAySAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAAwBCyADQRBqIAAQi4SAgAAgAyADKAIUNgIcIAMgAygCECIENgIYQQAhACAEQQAgBCgCABtBmNjAgAAQjISAgAAiBCgCACEFIAQoAgQhBCABKQMAIQYgAigCACIBNQIIIQcgA0EIaiABEM+FgIAAIAUgBiAHIAM1AgggBCgCpAERi4CAgAAAIANBGGpBBHIQjYSAgAALIANBIGokgICAgAAgAAtCAgF/AX4jgICAgABBEGsiAiSAgICAACACIAAgARDyg4CAACACKQMAIAIpAwgQ1IOAgAAhAyACQRBqJICAgIAAIAMLuAECAX8CfiOAgICAAEEQayIDJICAgIAAAkACQCABKAIAEYKAgIAAACIBDQAQwYWAgABCASEEDAELIAMgARCLhICAACADIAMoAgQ2AgwgAyADKAIAIgE2AghCACEEIAFBACABKAIAG0Gg2cCAABCMhICAACIBKAIAIAIpAwBCACABKAIEKAKsARGMgICAAAAhBSADQQhqQQRyEI2EgIAACyAAIAU3AwggACAENwMAIANBEGokgICAgAALEAAgABD0g4CAABDTg4CAAAuiAQECfyOAgICAAEEQayIBJICAgIAAAkACQCAAKAIAEYKAgIAAACIADQAQwYWAgABBASEADAELIAEgABCLhICAACABIAEoAgQ2AgwgASABKAIAIgI2AghBACEAIAJBACACKAIAG0G41cCAABCMhICAACICKAIAQgAgAigCBCgCFBGEgICAAAAgAUEIakEEchCNhICAAAsgAUEQaiSAgICAACAACxAAIAAQ9oOAgAAQ04OAgAALogEBAn8jgICAgABBEGsiASSAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAAwBCyABIAAQi4SAgAAgASABKAIENgIMIAEgASgCACICNgIIQQAhACACQQAgAigCABtB2NXAgAAQjISAgAAiAigCAEIAIAIoAgQoAhwRhICAgAAAIAFBCGpBBHIQjYSAgAALIAFBEGokgICAgAAgAAtEAgF/AX4jgICAgABBEGsiAySAgICAACADIAAgASACEPiDgIAAIAMpAwAgAykDCBDUg4CAACEEIANBEGokgICAgAAgBAvGAQQBfwF+AX8BfiOAgICAAEEQayIEJICAgIAAAkACQCABKAIAEYKAgIAAACIBDQAQwYWAgABCASEFDAELIAQgARCLhICAACAEIAQoAgQ2AgwgBCAEKAIAIgE2AgggAUEAIAEoAgAbQejWwIAAEIyEgIAAIgEoAgQhBiABKAIAIAIQm4WAgACtIAM1AgQgBigCeBGMgICAAAAhByAEQQhqQQRyEI2EgIAAQgAhBQsgACAHNwMIIAAgBTcDACAEQRBqJICAgIAACxQAIAAgASACEPqDgIAAENODgIAAC7IBAQN/I4CAgIAAQRBrIgMkgICAgAACQAJAIAAoAgARgoCAgAAAIgANABDBhYCAAEEBIQAMAQsgAyAAEIuEgIAAIAMgAygCBDYCDCADIAMoAgAiBDYCCEEAIQAgBEEAIAQoAgAbQdTUwIAAEIyEgIAAIgQoAgQhBSAEKAIAIAEpAwAgAhCbhYCAAK0gBSgCDBGHgICAAAAgA0EIakEEchCNhICAAAsgA0EQaiSAgICAACAACxIAIAAgARD8g4CAABDTg4CAAAuqAQECfyOAgICAAEEQayICJICAgIAAAkACQCAAKAIAEYKAgIAAACIADQAQwYWAgABBASEADAELIAIgABCLhICAACACIAIoAgQ2AgwgAiACKAIAIgM2AghBACEAIANBACADKAIAG0HA2cCAABCMhICAACIDKAIAIAE1AgQgATUCACADKAIEKAJcEYeAgIAAACACQQhqQQRyEI2EgIAACyACQRBqJICAgIAAIAALEgAgACABEP6DgIAAENODgIAAC6oBAQJ/I4CAgIAAQRBrIgIkgICAgAACQAJAIAAoAgARgoCAgAAAIgANABDBhYCAAEEBIQAMAQsgAiAAEIuEgIAAIAIgAigCBDYCDCACIAIoAgAiAzYCCEEAIQAgA0EAIAMoAgAbQeDZwIAAEIyEgIAAIgMoAgAgATUCBCABNQIAIAMoAgQoAmQRh4CAgAAAIAJBCGpBBHIQjYSAgAALIAJBEGokgICAgAAgAAtCAgF/AX4jgICAgABBEGsiAiSAgICAACACIAAgARCAhICAACACKQMAIAIpAwgQ1IOAgAAhAyACQRBqJICAgIAAIAML3wEEAX8BfgF/AX4jgICAgABBIGsiAySAgICAAAJAAkAgASgCABGCgICAAAAiAQ0AEMGFgIAAQgEhBAwBCyADQRBqIAEQi4SAgAAgAyADKAIUNgIcIAMgAygCECIBNgIYIAFBACABKAIAG0H41sCAABCMhICAACIBKAIAIQUgASgCBCEBIAIoAgAiAjUCCCEEIANBCGogAhDPhYCAACAFIAQgAzUCCCABKAJ8EYyAgIAAACEGIANBGGpBBHIQjYSAgABCACEECyAAIAY3AwggACAENwMAIANBIGokgICAgAALEgAgACABEIKEgIAAENODgIAAC6YBAQJ/I4CAgIAAQRBrIgIkgICAgAACQAJAIAAoAgARgoCAgAAAIgANABDBhYCAAEEBIQAMAQsgAiAAEIuEgIAAIAIgAigCBDYCDCACIAIoAgAiAzYCCEEAIQAgA0EAIAMoAgAbQbDZwIAAEIyEgIAAIgMoAgAgASkDACADKAIEKAKwARGEgICAAAAgAkEIakEEchCNhICAAAsgAkEQaiSAgICAACAAC0QCAX8BfiOAgICAAEEQayIDJICAgIAAIAMgACABIAIQhISAgAAgAykDACADKQMIENSDgIAAIQQgA0EQaiSAgICAACAEC+kBBAF/AX4BfwF+I4CAgIAAQSBrIgQkgICAgAACQAJAIAEoAgARgoCAgAAAIgENABDBhYCAAEIBIQUMAQsgBEEQaiABEIuEgIAAIAQgBCgCFDYCHCAEIAQoAhAiATYCGCABQQAgASgCABtBiNfAgAAQjISAgAAiASgCACEGIAEoAgQhASACKQMAIQUgAygCACICNQIIIQcgBEEIaiACEM+FgIAAIAYgBSAHIAQ1AgggASgCgAERhYCAgAAAIQcgBEEYakEEchCNhICAAEIAIQULIAAgBzcDCCAAIAU3AwAgBEEgaiSAgICAAAsUACAAIAEgAhCGhICAABDTg4CAAAuwAQECfyOAgICAAEEQayIDJICAgIAAAkACQCAAKAIAEYKAgIAAACIADQAQwYWAgABBASEADAELIAMgABCLhICAACADIAMoAgQ2AgwgAyADKAIAIgQ2AghBACEAIARBACAEKAIAG0Go18CAABCMhICAACIEKAIAIAEpAwAgAjUCBCACNQIAIAQoAgQoAogBEYuAgIAAACADQQhqQQRyEI2EgIAACyADQRBqJICAgIAAIAALFAAgACABIAIQiISAgAAQ04OAgAALvQEBA38jgICAgABBEGsiAySAgICAAAJAAkAgACgCABGCgICAAAAiAA0AEMGFgIAAQQEhAAwBCyADIAAQi4SAgAAgAyADKAIENgIMIAMgAygCACIENgIIQQAhACAEQQAgBCgCABtBiNjAgAAQjISAgAAiBCgCBCEFIAQoAgAgASkDACACKAIAIgQ1AgggBBCbhYCAAK0gBSgCoAERi4CAgAAAIANBCGpBBHIQjYSAgAALIANBEGokgICAgAAgAAtCAgF/AX4jgICAgABBEGsiAiSAgICAACACIAAgARCKhICAACACKQMAIAIpAwgQ1IOAgAAhAyACQRBqJICAgIAAIAMLtQECAX8CfiOAgICAAEEQayIDJICAgIAAAkACQCABKAIAEYKAgIAAACIBDQAQwYWAgABCASEEDAELIAMgARCLhICAACADIAMoAgQ2AgwgAyADKAIAIgE2AgggAUEAIAEoAgAbQeTUwIAAEIyEgIAAIgEoAgAgAikDACABKAIEKAIQEY2AgIAAACEFIANBCGpBBHIQjYSAgABCACEECyAAIAU3AwggACAENwMAIANBEGokgICAgAALcgECfyOAgICAAEEQayICJICAgIAAIAIgARC4hICAACIDNgIEAkAgAw0AIAJBBGoQroSAgABB9NDAgABBGCACQQhqQazRwIAAQYzRwIAAEKqGgIAAAAsgACADNgIEIAAgAUEEajYCACACQRBqJICAgIAACxwAAkAgAA0AQdDTwIAAQR0gARCohoCAAAALIAALGAAgACgCACIAIAAoAgBBf2oQ/4SAgAAaC3IBAn8jgICAgABBEGsiAiSAgICAACACIAEQtoSAgAAiAzYCBAJAIAMNACACQQRqEKaEgIAAQZHQwIAAQRAgAkEIakGc0cCAAEHk0MCAABCqhoCAAAALIAAgAzYCBCAAIAFBBGo2AgAgAkEQaiSAgICAAAsYACAAKAIAIgAgACgCAEEBahD/hICAABoLPAECfyAAKAIAIAAoAgQoAgARgYCAgAAAAkAgACgCBCIBKAIEIgJFDQAgACgCACACIAEoAggQzoKAgAALCxUAAkAgACgCAEUNACAAEJCEgIAACwuhAQMBfwF+AX8jgICAgABBMGsiAiSAgICAACACIAERgYCAgAAAIAApAgQhAyAAIAIpAwA3AgQgAEEMaiIBKAIAIQQgASACQQhqKAIANgIAIAAoAgAhASAAQQE2AgAgAkEQakEMaiAENgIAIAIgATYCECACIAM3AhQgAEEEaiEAAkAgAUUNACACQRBqQQhqEJGEgIAACyACQTBqJICAgIAAIAALHwACQCAAKAIAQQFGDQAgACABEJKEgIAADwsgAEEEagsPACAAKAIAIAEQroaAgAALqgEBAX8jgICAgABBwABrIgIkgICAgAAgAiABNgIMIAJBADYCGCACQgE3AxAgAkE0akEBNgIAIAJCATcCJCACQdjMwIAANgIgIAJB7YCAgAA2AjwgAiACQThqNgIwIAIgAkEMajYCOCACQRBqIAJBIGoQtYWAgAAQloSAgAAgAkEQahDFhYCAACAAQQhqIAIoAhg2AgAgACACKQMQNwIAIAJBwABqJICAgIAAC0gBAX8jgICAgABBEGsiASSAgICAAAJAIABFDQBB4MzAgABBNyABQQhqQczRwIAAQeDNwIAAEKqGgIAAAAsgAUEQaiSAgICAAAu9AQEDfyOAgICAAEEwayIDJICAgIAAAkAgAS0ADA0AIAEoAgQhBCABKAIAIQUgAyACEJ2FgIAAIAAgBSAEIAMoAgAgAygCBCABKAIIEImFgIAAIANBMGokgICAgAAPCyADQQhqQYTPwIAAQYGAgIAAEJOFgIAAIANBJGpBATYCACADQgE3AhQgA0HUzsCAADYCECADIAMpAwg3AyggAyADQShqNgIgIANBEGpBrM/AgAAQsYaAgAAQlIaAgAAAC6QCAgR/AX4jgICAgABB0ABrIgIkgICAgABBACEDIAJBACABKAIEEIGFgIAAIAJBIGpBCGoiBCABQQhqKQIANwMAIAIgASkCADcDICACQRBqIAJBIGogAhCXhICAACACQTBqQQhqIgEgAkEIaigCADYCACACIAIpAwA3AzACQAJAIAIoAhBBAUYNACACKAIUIQUgBCABKAIANgIAIAIgAikDMDcDICACQSBqIAUQmYSAgAAgAkHAAGpBCGogBCgCACIBNgIAIAIgAikDICIGNwNAIABBDGogATYCACAAIAY3AgQMAQsgACACKQIUNwIEIABBDGogAkEQakEMaigCADYCACACQTBqEJqEgIAAQQEhAwsgACADNgIAIAJB0ABqJICAgIAAC0EBAX8jgICAgABBEGsiAiSAgICAAAJAIAAoAgggAUkNACACQQhqIAAQnYWAgAAgACABNgIICyACQRBqJICAgIAACxIAIAAQzoWAgAAgABDQhYCAAAs9AQF/I4CAgIAAQRBrIgQkgICAgAAgBEEIakEAIAMgASACELyEgIAAIAAgBCkDCDcCACAEQRBqJICAgIAACxwAIAAoAgAgACgCBCABKAIAIAEoAgQQuYWAgAALCwAgARCehICAAAALQwEBfyOAgICAAEEgayIBJICAgIAAIAFBEGogABCVhICAACABQQhqIAFBEGoQxoSAgAAgASgCCCABKAIMEM+EgIAAAAsLACABEJ6EgIAAAAsLACABEKGEgIAAAAsLACAAEJ6EgIAAAAsCAAsYACAAQRhqEKSEgIAAIABBKGoQpISAgAALLwEBfyAAEKiEgIAAGiAAQQRqIQECQCAAKAIADQAgARCphICAAA8LIAEQqoSAgAALFwACQCAAKAIADQAgAEEEahCahICAAAsLFQACQCAAKAIARQ0AIAAQj4SAgAALCwIAC84IBAZ/AX4EfwJ+I4CAgIAAQdAAayIBJICAgIAAAkACQAJAAkACQCAAKAIAQQFGDQAgAUEoaiAAKAIEIgJBCGoQt4SAgAAgASABKAIsNgJEIAEgASgCKCIDNgJAIAFBwABqQQRyIQQgAykDAEIBUQ0CIAEgAkE8ahC4hICAACIFNgJMIAVFDQEgASAFNgI0IAEgAkHAAGoiBTYCMCABQTBqQQRyIQYCQAJAIAUoAgBBAkcNACACQSBqENmEgIAAIQcMAQsgBRCohICAACACQSBqENqEgIAAIQcLIAYQjYSAgAAgASACQSxqELiEgIAAIgU2AkwgBUUNAyABIAU2AjQgAkE4aigCAEE4bCEGIAEgAkEwaiICNgIwIAIoAgAhCCABQTBqQQRyIQlBACECA0ACQCAGIAJHDQAgAyAHNwMIIANCATcDACAJEI2EgIAADAYLAkACQAJAAkACQAJAAkACQAJAAkAgCCACaiIFKAIADgkAAQIDBAUGBwgACyAHENuEgIAADAgLIAFBCGogBUEEahDPhYCAACAHIAEoAgggASgCDBDchICAAAwHCyABQRhqIAVBBGoQz4WAgAAgASgCHCEKIAEoAhghCyABQRBqIAVBEGoQz4WAgAAgByALIAogASgCECABKAIUIAVBIGopAwAgBUEoaikDACAFQTBqKQMAEN2EgIAADAYLIAcgBUEIaikDACAFQRBqKQMAEN6EgIAADAULIAcgBUEQaikDACAFQRhqKQMAIAVBBGoQ34SAgAAMBAsgByAFQQRqQgAQ4ISAgAAMAwsgBUEwaikDACEMIAVBKGopAwAhDSABQSBqIAVBHGoQz4WAgAAgByAFQQRqQgAgDSAMIAVBEGogASgCICABKAIkEOGEgIAADAILIAcgBUEEahDihICAAAwBCyAHIAVBBGoQ44SAgAALIAJBOGohAgwACwsgASAAKAIEIgVBCGoQt4SAgAAgASABKAIENgJEIAEgASgCACICNgJAIAFBwABqQQRyIQQCQCACKQMAQgFRDQAgBUEgahCohICAACEHIAEgBUEwahCohICAADcDOCABIAc3AzAgAiABQTBqQQIQ2ISAgAAiBzcDCCACQgE3AwAMBAsgAikDCCEHDAMLIAFBzABqEK6EgIAAQfTQwIAAQRggAUEwakGs0cCAAEGM0cCAABCqhoCAAAALIAMpAwghBwwBCyABQcwAahCuhICAAEH00MCAAEEYIAFBMGpBrNHAgABBjNHAgAAQqoaAgAAACyAEEI+EgIAAIAEgAEEIahC4hICAACICNgJAAkAgAkUNACABIAI2AjQgASAAQQxqIgI2AjAgAi0AACECIAFBMGpBBHIQjYSAgAACQCACRQ0AIAcQ5oSAgAALIAFB0ABqJICAgIAAIAcPCyABQcAAahCuhICAAEH00MCAAEEYIAFBMGpBrNHAgABBjNHAgAAQqoaAgAAAC3wBAn8gACgCACIBIAEoAgBBf2o2AgACQCAAKAIAIgEoAgANACABQSBqEJqEgIAAIAFBMGoiAhDLhICAACACEMiEgIAAIAFBwABqEKuEgIAAIAAoAgAiASABKAIEQX9qNgIEIAAoAgAiACgCBA0AIABB0ABBCBDOgoCAAAsLWwEBfyAAKAIAIgEgASgCAEF/ajYCAAJAIAAoAgAiASgCAA0AIAFBCGoQo4SAgAAgACgCACIBIAEoAgRBf2o2AgQgACgCACIAKAIEDQAgAEHAAEEIEM6CgIAACwsXAAJAIAAoAgBBAkYNACAAEKSEgIAACwsCAAsCAAsVAAJAIAAoAgBFDQAgABCNhICAAAsLGAACQCAAKAIARQ0AIABBBGoQsISAgAALCzwBAn8gACgCACAAKAIEKAIAEYGAgIAAAAJAIAAoAgQiASgCBCICRQ0AIAAoAgAgAiABKAIIEM6CgIAACwsKACAAEJqEgIAACwIACwIACyoBAX8gACgCACIBKAIAIAEoAgQgACgCBCgCACAAKAIIKAIAEJKGgIAAAAtNAQF/I4CAgIAAQRBrIgQkgICAgAAgBCABNgIEIAQgADYCACAEIAM2AgwgBCACNgIIIAQgBEEIahCchICAACECIARBEGokgICAgAAgAgsjAQF/QQAhAQJAIAAoAgANACAAQX8Q/4SAgAAaIAAhAQsgAQtyAQJ/I4CAgIAAQRBrIgIkgICAgAAgAiABELaEgIAAIgM2AgQCQCADDQAgAkEEahCmhICAAEGR0MCAAEEQIAJBCGpBnNHAgABB5NDAgAAQqoaAgAAACyAAIAM2AgQgACABQQhqNgIAIAJBEGokgICAgAALJgEBfwJAIAAoAgBBAWoiAUEBTg0AQQAPCyAAIAEQ/4SAgAAaIAALUQEEf0EAIQICQAJAIAEoAgAiAyABKAIESQ0ADAELIANBARCWhYCAAGoiBCADSQ0AIAEoAgAhBSABIAQ2AgBBASECCyAAIAU2AgQgACACNgIAC1gCAn8BfkEAIQMCQAJAIAEoAgAgASgCBCIBakF/akEAIAFrcSIErSACrX4iBUIgiKdFDQAQlIWAgAAMAQsgACAENgIIIAAgBac2AgAgASEDCyAAIAM2AgQLQAACQAJAIAIgAUkNACAEIAJPDQEgAiAEEI+GgIAAAAsgASACEJCGgIAAAAsgACACIAFrNgIEIAAgAyABajYCAAtAAAJAAkAgAiABSQ0AIAQgAk8NASACIAQQj4aAgAAACyABIAIQkIaAgAAACyAAIAIgAWs2AgQgACADIAFqNgIACzMAAkAgASgCAA0AIAIgAyAEEKiGgIAAAAsgACABKQIANwIAIABBCGogAUEIaigCADYCAAuNAQEBfyOAgICAAEEgayIDJICAgIAAAkAgASgCAEEBRw0AIANBGGogAUEUaigCADYCACADQRBqIAFBDGopAgA3AwAgAyABKQIENwMIQdzRwIAAQSsgA0EIakGI0sCAACACEKqGgIAAAAsgACABKQIENwIAIABBCGogAUEMaigCADYCACADQSBqJICAgIAACzcBAX9BASEDAkAgAkEQRw0AIAAgASkAADcAASAAQQlqIAFBCGopAAA3AABBACEDCyAAIAM6AAALLQEBf0HQAEEIEMuDgIAAIgFCgYCAgBA3AwAgAUEIaiAAQcgAEPOGgIAAGiABC0YCAX8BfiOAgICAAEEQayICJICAgIAAIAJBCGogAUEAEMeFgIAAIAIpAwghAyAAQQA2AgggACADNwIAIAJBEGokgICAgAALPAACQCAAKAIIIAAoAgRHDQAgAEEBEI2FgIAACyAAEJuFgIAAIAAoAghqIAE6AAAgACAAKAIIQQFqNgIICxEAIAAgACgCCCABEMSEgIAAC2oBAX8jgICAgABBEGsiAySAgICAACADIAAgASACQQFBARDJhICAAAJAAkAgAygCAEEBRw0AIANBCGooAgBFDQFBmNLAgABBKEG8z8CAABCNhoCAAAALIANBEGokgICAgAAPCxCGhoCAAAALUAEBfyOAgICAAEEQayIDJICAgIAAIAMgAhDBhICAACADIAEgAhCYhYCAACAAQQhqIANBCGooAgA2AgAgACADKQMANwIAIANBEGokgICAgAALNwEBfyOAgICAAEEQayICJICAgIAAIAJBCGogARDPhYCAACAAIAIpAwg3AgAgAkEQaiSAgICAAAukAQECfyOAgICAAEEgayICJICAgIAAIAJBCGogARDPhYCAACACQRBqIAIoAgggAigCDBDXhoCAAEEBIQMCQAJAIAIoAhBBAUYNACAAIAEpAgA3AgQgAEEMaiABQQhqKAIANgIAQQAhAwwBCyAAIAEpAgA3AgQgAEEQaiACKQIUNwIAIABBDGogAUEIaigCADYCAAsgACADNgIAIAJBIGokgICAgAALIwEBfwJAIAAoAgQiAUUNACAAKAIAIAFBOGxBCBDOgoCAAAsLoAMBAn8jgICAgABBwABrIgYkgICAgABBACEHAkAgASgCBCACayADTw0AAkACQAJAIAVFDQAgBkEoaiABIAIgAxDKhICAAEEBIQcgBigCLCEDIAYoAihBAUcNASAGQQhqIAMgBkEwaigCABCVhYCAACAAIAYpAwg3AgQMAwsgAiADaiIDIAJPDQAgBkEgaiADQQAQlYWAgAAgACAGKQMgNwIEDAELIAZCuICAgIABNwM4IAZBKGogBkE4aiADELqEgIAAIAYoAighAgJAIAYoAiwiBw0AIAZBEGogAkEAEJWFgIAAIAAgBikDEDcCBAwBCwJAIAJBf0oNACAGQRhqIAZBABCVhYCAACAAIAYpAxg3AgQMAQsCQAJAIAEoAgQiBQ0AIAIgBxDNgoCAACEFDAELIAEoAgAgBUE4bEEIIAIQz4KAgAAhBQsCQAJAIAUNACAERQ0BIAIgBxCFhoCAAAALIAEgAzYCBCABIAU2AgBBACEHDAILIAAgAjYCBCAAQQhqIAc2AgALQQEhBwsgACAHNgIAIAZBwABqJICAgIAAC4EBAQF/I4CAgIAAQRBrIgQkgICAgAACQAJAIAIgA2oiAyACTw0AIARBCGogA0EAEJWFgIAAIAQoAgghAiAAQQhqIAQoAgw2AgBBASEDDAELIAEoAgRBAXQiAiADIAIgA0sbIQJBACEDCyAAIAM2AgAgACACNgIEIARBEGokgICAgAAL2wEBAn8gACgCCEE4bCEBIAAoAgAhAAJAA0AgAUUNAQJAAkAgACgCACICQQdLDQACQAJAAkACQAJAAkAgAg4IBwABBwIDBAUHCyAAQQRqEJqEgIAADAYLIABBBGoQmoSAgAAgAEEQahCahICAAAwFCyAAQQRqEJqEgIAADAQLIABBBGoQmoSAgAAMAwsgAEEEahCahICAACAAQRBqEJqEgIAAIABBHGoQmoSAgAAMAgsgAEEEahCahICAAAwBCyAAQQRqEJqEgIAACyAAQThqIQAgAUFIaiEBDAALCwtHAQJ/AkACQCABKAIAIgEoAggiAiABKAIMRw0AQQAhAwwBC0EBIQMgASACQQFqNgIIIAItAAAhAQsgACABOgABIAAgAzoAAAtxAgF/An4jgICAgABBEGsiASSAgICAAAJAQQApA4D6woAAIgJCAXwiAyACVA0AQQAgAzcDgPrCgAAgASACNwMIIAAgAUEIakEIEMWEgIAAIAFBEGokgICAgAAPC0HQz8CAAEEcQaTTwIAAEI2GgIAAAAsSAEG008CAACAAIAEQ34OAgAALSQEBfyOAgICAAEEQayICJICAgIAAIAIgATYCDCACIAA2AghBtNPAgAAgAkEIahD9g4CAAEGY0sCAAEEoQdDZwIAAEKGFgIAAAAsQAEEBQbjTwIAAEPWFgIAAC5cBAQF/I4CAgIAAQTBrIgIkgICAgAAgAiABNwMYIAJBCGogARDShICAAAJAAkAgAikDCKcNABCehYCAACAAQQA2AgAMAQsgAkEgakEAIAIpAxCnEIGFgIAAQbTTwIAAIAJBGGogAkEgahD5g4CAACAAQQhqIAJBIGpBCGooAgA2AgAgACACKQMgNwIACyACQTBqJICAgIAAC0oBAX8jgICAgABBEGsiAiSAgICAACACIAE3AwggAEG008CAACACQQhqEImEgIAAIgE3AwggACABQn9SrTcDACACQRBqJICAgIAAC3sBAX8jgICAgABBMGsiASSAgICAAEG008CAABDzg4CAACABQQhqQgAQ0YSAgAAgAUEgaiABQQhqQfTUwIAAQcQAQbjVwIAAEL2EgIAAIAFBCGogAUEgahDHhICAACAAIAFBCGpByNXAgAAQvoSAgAAgAUEwaiSAgICAAAtPAQF/I4CAgIAAQRBrIgEkgICAgABBtNPAgAAQ9YOAgAAgAUIAENGEgIAAIAAgAUH01MCAAEHEAEHY1cCAABC9hICAACABQRBqJICAgIAAC3sBAX8jgICAgABBMGsiASSAgICAAEG008CAABDlg4CAACABQQhqQgAQ0YSAgAAgAUEgaiABQQhqQfTUwIAAQcQAQejVwIAAEL2EgIAAIAFBCGogAUEgahDHhICAACAAIAFBCGpB+NXAgAAQvoSAgAAgAUEwaiSAgICAAAsYAEG008CAABDVg4CAACAAQgAQ0YSAgAALWwICfwF+I4CAgIAAQRBrIgEkgICAgAAgAUEIaiICQgA3AwAgAUIANwMAQbTTwIAAIAEQ64OAgAAgASkDACEDIAAgAikDADcDCCAAIAM3AwAgAUEQaiSAgICAAAuYAwIEfwF+I4CAgIAAQdAAayICJICAgIAAIAIgATYCLCACIAA2AigCQAJAAkACQAJAIAFB/////wFxIAFHDQAgAkEwakEAIAFBA3QQgYWAgAAgAkEgakEAIAEQnIWAgAAgAiACKQMgNwNAA0AgAkEYaiACQcAAahC5hICAACACKAIYRQ0FIAIoAhwiA0H/////AXEgA0cNAiADQQFqIgRB/////wFxIARHDQMgAkEQaiACQTBqEJ2FgIAAIAJBCGogA0EDdCIFIARBA3QgAigCECACKAIUELyEgIAAIAMgAU8NBCACKAIMIQMgAigCCCEEIAIgACAFaikDADcDSCAEIAMgAkHIAGpBCBCOhYCAAAwACwtB8M/AgABBIUGo1sCAABCNhoCAAAALQfDPwIAAQSFBuNbAgAAQjYaAgAAAC0Hwz8CAAEEhQcjWwIAAEI2GgIAAAAtB2NbAgAAgAyABEIyGgIAAAAtBtNPAgAAgAkEwaiACQShqEPeDgIAAIQYgAkEwahCahICAACACQdAAaiSAgICAACAGCz4CAX8BfiOAgICAAEEQayIBJICAgIAAIAEgADYCDEG008CAACABQQxqEP+DgIAAIQIgAUEQaiSAgICAACACC0UBAX8jgICAgABBEGsiAiSAgICAACACIAE2AgwgAiAANwMAQbTTwIAAIAIgAkEMahCDhICAACEAIAJBEGokgICAgAAgAAs4AQF/I4CAgIAAQRBrIgEkgICAgAAgASAANwMIQbTTwIAAIAFBCGoQ14OAgAAgAUEQaiSAgICAAAtIAQF/I4CAgIAAQRBrIgMkgICAgAAgAyACNgIMIAMgATYCCCADIAA3AwBBtNPAgAAgAyADQQhqEIWEgIAAIANBEGokgICAgAALnQEBAX8jgICAgABB0ABrIggkgICAgAAgCCAGNwMoIAggBTcDICAIIAI2AhQgCCABNgIQIAggADcDCCAIIAQ2AhwgCCADNgIYIAggBzcDMCAIIAhBMGo2AkggCCAIQSBqNgJEIAggCEEYajYCQCAIIAhBEGo2AjwgCCAIQQhqNgI4QbTTwIAAIAhBOGoQ24OAgAAgCEHQAGokgICAgAALSwEBfyOAgICAAEEgayIDJICAgIAAIAMgAjcDGCADIAE3AxAgAyAANwMIQbTTwIAAIANBCGogA0EQahDdg4CAACADQSBqJICAgIAAC2gBAX8jgICAgABBMGsiBCSAgICAACAEIAI3AxAgBCABNwMIIAQgADcDACAEIAM2AhwgBCAEQRxqNgIoIAQgBEEIajYCJCAEIAQ2AiBBtNPAgAAgBEEgahDRg4CAACAEQTBqJICAgIAAC2QBAX8jgICAgABBMGsiAySAgICAACADIAE2AhQgAyAANwMIIAMgAjcDGCADIANBGGo2AiggAyADQRRqNgIkIAMgA0EIajYCIEG008CAACADQSBqEOODgIAAIANBMGokgICAgAALpAEBAX8jgICAgABB0ABrIggkgICAgAAgCCAENwMgIAggAzcDGCAIIAE2AgwgCCAANwMAIAggAjcDECAIIAU2AiwgCCAHNgI0IAggBjYCMCAIIAhBMGo2AkwgCCAIQSxqNgJIIAggCEEYajYCRCAIIAhBEGo2AkAgCCAIQQxqNgI8IAggCDYCOEG008CAACAIQThqEOGDgIAAIAhB0ABqJICAgIAAC0EBAX8jgICAgABBEGsiAiSAgICAACACIAE2AgwgAiAANwMAQbTTwIAAIAIgAkEMahCHhICAACACQRBqJICAgIAAC0EBAX8jgICAgABBEGsiAiSAgICAACACIAE2AgwgAiAANwMAQbTTwIAAIAIgAkEMahDvg4CAACACQRBqJICAgIAACw4AQbTTwIAAEOmDgIAAC8oBAQF/I4CAgIAAQTBrIgIkgICAgAAgAiABNwMIAkACQAJAAkACQEG008CAACACQQhqEPGDgIAAIgFCAlYNACABpw4DAQIDAQtBuNjAgABBF0HQ2MCAABChhYCAAAALIABBADYCAAwCCyACQSBqQgAQ0YSAgAAgAkEQaiACQSBqQeDYwIAAQTBBkNnAgAAQvYSAgAAgAEEMaiACQRhqKAIANgIAIAAgAikDEDcCBCAAQQE2AgAMAQsgAEECNgIACyACQTBqJICAgIAACzgBAX8jgICAgABBEGsiASSAgICAACABIAA3AwhBtNPAgAAgAUEIahCBhICAACABQRBqJICAgIAACz8BAX8jgICAgABBEGsiAiSAgICAACACIAE2AgwgAiAANgIIQbTTwIAAIAJBCGoQ+4OAgAAgAkEQaiSAgICAAAuIAQIBfwF+I4CAgIAAQRBrIgQkgICAgAAgBCABNgIEIAQgADYCACAEIAM2AgwgBCACNgIIAkACQAJAQbTTwIAAIAQgBEEIahDng4CAACIFQgFWDQBBACECIAWnDgICAQILQbjYwIAAQRdB8NnAgAAQoYWAgAAAC0EBIQILIARBEGokgICAgAAgAgujAQIBfwF+I4CAgIAAQSBrIgMkgICAgAAgAyACNgIMIAMgATYCCAJAAkACQAJAQbTTwIAAIANBCGoQ2YOAgAAiBEIBVg0AIASnDgIBAgELQbjYwIAAQRdBkNrAgAAQoYWAgAAACyAAQQA2AgAMAQsgA0EQakIAENGEgIAAIAAgA0EQakH01MCAAEHEAEGg2sCAABC9hICAAAsgA0EgaiSAgICAAAt4AgF/AX4jgICAgABBEGsiAiSAgICAACACIAE2AgwgAiAANgIIAkACQAJAQbTTwIAAIAJBCGoQ7YOAgAAiA0IBVg0AQQAhACADpw4CAgECC0G42MCAAEEXQcDawIAAEKGFgIAAAAtBASEACyACQRBqJICAgIAAIAALDAAgAEJ+ENGEgIAAC80BAQV/I4CAgIAAQRBrIgIkgICAgABBACEDAkAgAUF+akE+Sw0AIAJBCGogACABEJ+FgIAAIAIoAgwhBCACKAIIIQBBASEBA0AgASEFAkAgBCAARw0AIAVBAXMhAwwCC0EAIQNBACEBAkAgAC0AACIGQZ9/akH/AXFBGkkNAEEAIQEgBkFQakH/AXFBCkkNAEEBIQEgBkFTakECSQ0AIAZB3wBGDQBBACEDDAILIABBAWohACAFIAFxRQ0ACwsgAkEQaiSAgICAACADQQFxC5cBAQF/I4CAgIAAQdAAayICJICAgIAAIAJBNGpCADcCACACQShqIAFBCGooAgA2AgAgAkKAgICAIDcCPCACQoCAgICAATcCLCACQgA3AxAgAkEANgIIIAIgASkCADcDICACQQhqEMCEgIAAIQEgAEEMakEAOgAAIABBADYCCCAAIAE2AgQgAEEANgIAIAJB0ABqJICAgIAAC6oCAQR/I4CAgIAAQcAAayIDJICAgIAAAkACQCABKAIAQQFGDQAgAyABKAIEIgRBLGoQtoSAgAAiBTYCCCAFRQ0BIAMgBTYCBCADIARBMGoiBjYCACADQQhqIAJBOBDzhoCAABogA0EEciECAkAgBEE4aigCACIFIARBNGooAgBHDQAgBkEBEMOEgIAAIAQoAjghBQsgBCgCMCAFQThsaiADQQhqQTgQ84aAgAAaIAQgBCgCOEEBajYCOCACEI+EgIAAIABBCGogAUEIaikCADcCACAAIAEpAgA3AgAgA0HAAGokgICAgAAPC0Hg2sCAAEElQeTbwIAAEKGFgIAAAAsgA0EIahCmhICAAEGR0MCAAEEQIANBCGpBnNHAgABB5NDAgAAQqoaAgAAAC1gBAX8jgICAgABB0ABrIgIkgICAgAAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQA2AhggACACQQhqIAJBGGoQ7oSAgAAgAkHQAGokgICAgAALpQEBAX8jgICAgABB0ABrIgckgICAgAAgB0EIakEIaiABQQhqKQIANwMAIAcgASkCADcDCCAHQcAAaiAFNwMAIAdBJGogAkEIaigCADYCACAHQTBqIANBCGooAgA2AgAgByAENwM4IAcgBjcDSCAHQQI2AhggByACKQIANwIcIAcgAykCADcDKCAAIAdBCGogB0EYahDuhICAACAHQdAAaiSAgICAAAtvAQF/I4CAgIAAQdAAayIEJICAgIAAIARBCGpBCGogAUEIaikCADcDACAEIAEpAgA3AwggBEEoaiADNwMAIARBGGpBCGogAjcDACAEQQM2AhggACAEQQhqIARBGGoQ7oSAgAAgBEHQAGokgICAgAALcgEBfyOAgICAAEHQAGsiAySAgICAACADQQhqQQhqIAFBCGopAgA3AwAgAyABKQIANwMIIANBJGogAkEIaigCADYCACADQQU2AhggAyACKQIANwIcIAAgA0EIaiADQRhqEO6EgIAAIANB0ABqJICAgIAAC7gBAQF/I4CAgIAAQdAAayIHJICAgIAAIAdBCGpBCGogAUEIaikCADcDACAHIAEpAgA3AwggB0HIAGogBDcDACAHQSRqIAJBCGooAgA2AgAgB0EwaiAFQQhqKAIANgIAIAdBPGogBkEIaigCADYCACAHIAM3A0AgB0EGNgIYIAcgAikCADcCHCAHIAUpAgA3AyggByAGKQIANwI0IAAgB0EIaiAHQRhqEO6EgIAAIAdB0ABqJICAgIAAC3IBAX8jgICAgABB0ABrIgMkgICAgAAgA0EIakEIaiABQQhqKQIANwMAIAMgASkCADcDCCADQSRqIAJBCGooAgA2AgAgA0EHNgIYIAMgAikCADcCHCAAIANBCGogA0EYahDuhICAACADQdAAaiSAgICAAAuFAgEDfyOAgICAAEEgayIDJICAgIAAAkACQCACKAIAQQFGDQAgA0EIaiIEIAFBCGopAgA3AwAgAyABKQIANwMAIAMgAigCBCIBQTxqELaEgIAAIgU2AhwgBUUNASADIAU2AhQgAyABQcAAaiIFNgIQIAUQq4SAgAAgAUHIAGogBCkDADcCACAFIAMpAwA3AgAgA0EQakEEchCPhICAACAAQQhqIAJBCGopAgA3AgAgACACKQIANwIAIANBIGokgICAgAAPC0H028CAAEEeQZTcwIAAEKGFgIAAAAsgA0EcahCmhICAAEGR0MCAAEEQIANBEGpBnNHAgABB5NDAgAAQqoaAgAAACwsAIAAQqISAgAAaC+cBAQN/I4CAgIAAQTBrIgIkgICAgAAgAkEYaiABEM+FgIAAIAJBIGogAigCGCACKAIcEIiGgIAAIAJBEGogAkEgahDPhYCAAAJAAkAgAigCECIDIAIoAhQiBEGk3MCAAEEHELWEgIAARQ0AQQAhAyAAQQA6AAEMAQsCQCADIARBq9zAgABBCRC1hICAAEUNACAAQQE6AAFBACEDDAELIAJBCGpBtNzAgABBEhDuhYCAACAAQQRqIAIpAwg3AgBBASEDCyAAIAM6AAAgAkEgahCahICAACABEJqEgIAAIAJBMGokgICAgAALHAAgACABKQIANwIAIABBCGogAUEIaigCADYCAAtHAQF/I4CAgIAAQRBrIgIkgICAgAAgAkEIaiABEM+FgIAAIAAgAigCCCACKAIMEPqEgIAAIAEQmoSAgAAgAkEQaiSAgICAAAu6CQIFfwF+I4CAgIAAQYABayIDJICAgIAAIANBOjYCSCADQSBqIANByABqQQRBARCbhICAACADQTo2AmggAyACNgJkIANBADYCYCADIAI2AlwgAyABNgJYIAMgAygCSDYCcCADIAMoAiQ2AmwgA0HwAGohBCACIQVBACEGIAIhBwJAAkACQAJAAkADQAJAAkACQAJAIAUgBkkNACAHIAVPDQELEJ6FgIAADAELIANBGGogAygCbCADQdgAampBF2otAAAgAygCWCAGaiAFIAZrELOGgIAAIAMoAhhBAUYNASADIAMoAmQ2AmALQQAhBkEAIQdBACEFDAILIAMgAygCHCADKAJgakEBaiIGNgJgAkACQCAGIAMoAmwiBUkNACADKAJcIAZJDQAgAygCWCEHIAMgBTYCLCADIAcgBiAFayIGajYCKCADQRBqQQAgBSAEQQQQu4SAgAAgAyADKQMQNwNIIANBKGogA0HIAGoQnISAgAANASADKAJgIQYLIAMoAmQhBSADKAJcIQcMAQsLAkAgBkUNACAGIAJGDQAgBiACTw0CIAEgBmosAABBv39MDQILIANB2ABqIAEgBhDKhYCAACADQcgAaiADQdgAahD3hICAAEEBIQUCQCADLQBIQQFGDQAgAy0ASSEHIAMgAiAGayIFNgIsIAMgASAGaiIGNgIoIANBATYCeCADIAU2AnwCQAJAIAVBAUsNACAFDgIFAQULIAYsAAFBv39MDQQLIAZBAWohASAFQX9qIQJBACEGQQAhBQwBCyADKAJMIgdBgH5xIQYgA0HQAGooAgAhAQsgA0HEAGogAjYCACADQThqQQhqIAE2AgAgAyAFNgI4IAMgBiAHQf8BcXIiBjYCPAJAAkAgBQ0AIANBOGoQr4SAgAAgA0EAOwFkIANB8M3AgAA2AmAgAyACNgJcIAMgATYCWCADQThqIANB2ABqEJiEgIAAIANBOGpBBHIhBiADKAI4QQFHDQEgA0HYAGpBCGogBkEIaigCADYCACADIAYpAgA3A1ggAyADQdgAahDKg4CAACADKAIAIQYgAEEIakHoysCAADYCACAAIAY2AgQgAEEBNgIAIANBOGoQpYSAgAAMBQsgACAGNgIEIABBATYCACAAQQhqIAE2AgAMBAsgA0EoakEIaiICIAZBCGooAgAiBTYCACADIAYpAgA3AyggBUHAAEEgIAdBAXEiBhsiB0cNAiADQcgAaiAHQQFyEMGEgIAAIANByABqIAYQwoSAgAAgA0E4akEIaiIFIAIoAgA2AgAgAyADKQMoNwM4IAMgA0E4ahCbhYCAACIGNgJgIAMgBjYCWCADIAMoAjw2AlwgAyAGIAUoAgBqNgJkIANByABqIANB2ABqEMiDgIAAIABBDGogA0HIAGpBCGooAgA2AgAgACADKQNINwIEIABBADYCAAwDCyABIAJBACAGEJKGgIAAAAsgAyADQfwAajYCYCADIANB+ABqNgJcIAMgA0EoajYCWCADQdgAahC0hICAAAALIANBCGpBxtzAgABBIBDuhYCAACADKQMIIQggAEEBNgIAIAAgCDcCBCADQShqEJqEgIAACyADQYABaiSAgICAAAsJACAAQgA3AgALFABBiPrCgABB7oCAgAAQk4SAgAALEAAgACACNwMIIAAgATcDAAsQACAAIAI3AwggACABNwMACxQBAX8gACgCACECIAAgATYCACACC0YCAX8BfiOAgICAAEEQayICJICAgIAAIAJBCGogAUEAEMeFgIAAIAIpAwghAyAAQQA2AgggACADNwIAIAJBEGokgICAgAALjgECAX8BfiOAgICAAEEgayIDJICAgIAAAkACQCABQf8BcUUNACADQRBqIAIQgIWAgAAgA0EQahCbhYCAACABIAIQ8oaAgAAaIABBCGogAjYCACAAIAMpAxA3AgAMAQsgA0EIaiACQQEQx4WAgAAgAykDCCEEIAAgAjYCCCAAIAQ3AgALIANBIGokgICAgAALQwAgACgCACEAAkAgARDKhoCAAA0AAkAgARDLhoCAAA0AIAAgARCchoCAAA8LIAAgARDhhoCAAA8LIAAgARDbhoCAAAsPACAAKAIAIAEQ04aAgAALAgALAgALPQEBfyOAgICAAEEQayIEJICAgIAAIARBCGpBACADIAEgAhCHhYCAACAAIAQpAwg3AgAgBEEQaiSAgICAAAtAAAJAAkAgAiABSQ0AIAQgAk8NASACIAQQj4aAgAAACyABIAIQkIaAgAAACyAAIAIgAWs2AgQgACADIAFqNgIAC2cBA38CQAJAIAEoAgAiAiABKAIERw0AQQAhAgwBCyABIAJBAWo2AgACQCABKAIIIgNBAWoiBCADTw0AQcDdwIAAQRxBsN3AgAAQjYaAgAAACyABIAQ2AggLIAAgAjYCBCAAIAM2AgALwAUBBX8jgICAgABBoAJrIgYkgICAgAAgBS0AACEHIAZBoAFqQf8BQYABEPKGgIAAGiAGQQA2AiggBiAFQTpqNgIkIAYgBTYCIANAIAZBGGogBkEgahCIhYCAAAJAIAYoAhwiBQ0AIAZBIGogBkGgAWpBgAEQ84aAgAAaIAZBADYCqAEgBiABNgKgASAGIAEgAmo2AqQBQQAhCAJAAkADQCAGQRBqIAZBoAFqEIiFgIAAAkAgBigCFCIFDQAgB0H/AXEhBQNAAkACQCACRQ0AIAEtAAAgBUYNAQsgBiADIAQgCBCGhYCAACAGKAIEIglBAXYhCiAJIAYoAgAiBWpBf2ohCQJAA0AgCkUNASAFLQAAIQQgBSAJLQAAOgAAIAkgBDoAACAKQX9qIQogBUEBaiEFIAlBf2ohCQwACwsgAEEANgIAIAAgCDYCBAwFCwJAIAggBEkNACAAQgE3AgAMBQsgAUEBaiEBIAMgCGpBADoAACACQX9qIQIgCEEBaiEIDAALCyAGKAIQIQkCQAJAAkAgBSwAACIFQQBIDQAgBkEgaiAFQf8BcSIKai0AACIFQf8BRg0BIAZBCGogAyAEIAgQhoWAgAAgBigCCCEJIAYoAgwhCgNAIApFDQMgCSAJLQAAQTpsIAVqIgU6AAAgCkF/aiEKIAlBAWohCSAFQQh2IQUMAAsLIABBAjYCBCAAQQhqIAk2AgAMAwsgAEEBNgIEIABBDGogCTYCACAAQQhqIAo2AgAMAgsDQCAFRQ0BAkAgCCAETw0AIAMgCGogBToAACAFQQh2IQUgCEEBaiEIDAELCwsgAEEANgIECyAAQQE2AgALIAZBoAJqJICAgIAADwsgBSwAACIFQf8BcSEJAkAgBUEASA0AIAZBoAFqIAlqIAYoAhg6AAAMAQsLQbTewIAAIAlBgAEQjIaAgAAAC9sCAQF/I4CAgIAAQcAAayICJICAgIAAAkACQAJAAkACQCAAKAIADgQBAgMAAQtBxN7AgABBKEHs3sCAABCNhoCAAAALIAJBPGpBADYCACACQdjgwIAANgI4IAJCATcCLCACQdDgwIAANgIoIAEgAkEoahDJhoCAACEADAILIAIgACgCBDYCDCACIABBCGooAgA2AiQgAkE8akECNgIAIAJBHGpBhICAgAA2AgAgAkICNwIsIAJB/N/AgAA2AiggAkGEgYCAADYCFCACIAJBEGo2AjggAiACQSRqNgIYIAIgAkEMajYCECABIAJBKGoQyYaAgAAhAAwBCyACIAAoAgQ2AiQgAkE8akEBNgIAIAJCATcCLCACQbzfwIAANgIoIAJBhICAgAA2AhQgAiACQRBqNgI4IAIgAkEkajYCECABIAJBKGoQyYaAgAAhAAsgAkHAAGokgICAgAAgAAvGAgEBfyOAgICAAEEgayICJICAgIAAAkACQAJAAkACQCAAKAIADgQBAgMAAQsgAkEQaiABQdjgwIAAQQ8QzYaAgAAgAkEQahC2hoCAACEADAMLIAJBEGogAUG84cCAAEEOEM2GgIAAIAJBEGoQtoaAgAAhAAwCCyACQRBqIAFBkOHAgABBEBDMhoCAACACIABBBGo2AgwgAkEQakGg4cCAAEEJIAJBDGpBrOHAgAAQp4aAgAAaIAIgAEEIajYCDCACQRBqQfjgwIAAQQUgAkEMakGA4cCAABCnhoCAABogAkEQahC0hoCAACEADAELIAJBEGogAUHn4MCAAEEREMyGgIAAIAIgAEEEajYCDCACQRBqQfjgwIAAQQUgAkEMakGA4cCAABCnhoCAABogAkEQahC0hoCAACEACyACQSBqJICAgIAAIAALNgEBfyAAIAIgAWsiAhCNhYCAACAAIAAoAggiAyACajYCCCADIAAoAgBqIAIgASACEI6FgIAACxEAIAAgACgCCCABEJmFgIAAC5MCAQF/I4CAgIAAQeAAayIEJICAgIAAIAQgATYCCCAEIAM2AgwCQCABIANHDQAgACACIAEQ84aAgAAaIARB4ABqJICAgIAADwsgBEEoakEUakGIgICAADYCACAEQTRqQYmBgIAANgIAIARBEGpBFGpBAzYCACAEIARBCGo2AkAgBCAEQQxqNgJEIARByABqQRRqQQA2AgAgBEIDNwIUIARBiOLAgAA2AhAgBEGJgYCAADYCLCAEQdziwIAANgJYIARCATcCTCAEQdTiwIAANgJIIAQgBEEoajYCICAEIARByABqNgI4IAQgBEHEAGo2AjAgBCAEQcAAajYCKCAEQRBqQajjwIAAELGGgIAAEJSGgIAAAAtDACAAKAIAIQACQCABEMqGgIAADQACQCABEMuGgIAADQAgACABEJyGgIAADwsgACABEOGGgIAADwsgACABENuGgIAACxQAIAAoAgAgACgCBCABENKGgIAACxAAIAAgAjYCBCAAIAE2AgALEAAgACACNgIEIAAgATYCAAsQACAAIAI2AgQgACABNgIACwIACxAAIAAgAjYCBCAAIAE2AgALBAAgAAsnAQF/QQEhAwJAIAJBCEcNACAAIAEpAAA3AAFBACEDCyAAIAM6AAALEQAgACABIAEgAmoQjIWAgAALagEBfyOAgICAAEEQayIDJICAgIAAIAMgACABIAJBAUEBEJqFgIAAAkACQCADKAIAQQFHDQAgA0EIaigCAEUNAUG448CAAEEoQYDkwIAAEI2GgIAAAAsgA0EQaiSAgICAAA8LEIaGgIAAAAuFAgECf0EAIQYCQCABKAIEIgcgAmsgA08NACACIANqIgMgAkkhAgJAAkACQAJAIAVFDQAgAkUNASAAIAM2AgQgAEEIakEANgIADAMLIAJFDQEgACADNgIEIABBCGpBADYCAAwCCyAHQQF0IgIgAyACIANLGyEDCwJAIANBf0oNACAAQQhqQQA2AgAMAQsCQAJAIAcNACADQQEQzYKAgAAhAgwBCyABKAIAIAdBASADEM+CgIAAIQILAkACQCACDQAgBEUNASADQQEQhYaAgAAACyABIAM2AgQgASACNgIADAILIAAgAzYCBEEBIQYgAEEIakEBNgIADAELQQEhBgsgACAGNgIACwcAIAAoAgALEAAgACACNgIEIAAgATYCAAsWACAAIAEoAgg2AgQgACABKAIANgIACwIACxMAIAAgASACajYCBCAAIAE2AgALDABC5K7ChZebpYgRCz8BAX8jgICAgABBEGsiAySAgICAACADIAE2AgwgAyAANgIIIANBCGpBkOTAgABBACACELGGgIAAEPeFgIAAAAsCAAsCAAslAAJAIAEoAgANABDyhYCAAAALIABBpOTAgAA2AgQgACABNgIAC2YBAn8gASgCACECIAFBADYCAAJAAkAgAkUNACABKAIEIQNBCEEEEM2CgIAAIgFFDQEgASADNgIEIAEgAjYCACAAQaTkwIAANgIEIAAgATYCAA8LEPKFgIAAAAtBCEEEEIWGgIAAAAsQACAAIAI2AgQgACABNgIACxQAIAAoAgAgACgCBCABENCGgIAAC0MAIAAoAgAhAAJAIAEQyoaAgAANAAJAIAEQy4aAgAANACAAIAEQ2oaAgAAPCyAAIAEQ44aAgAAPCyAAIAEQ4oaAgAALAgALBwAgACgCAAsWACAAIAEoAgg2AgQgACABKAIANgIACxIAIAFBtOTAgABBCBDIhoCAAAuKCgEBfyOAgICAAEEwayICJICAgIAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAC0AAA4SAAECAwQFBgcICQoLDA0ODxARAAsgAiAALQABOgAIIAJBLGpBATYCACACQgI3AhwgAkGE58CAADYCGCACQY+BgIAANgIUIAIgAkEQajYCKCACIAJBCGo2AhAgASACQRhqEMmGgIAAIQAMEQsgAiAAQQhqKQMANwMIIAJBLGpBATYCACACQgI3AhwgAkHo5sCAADYCGCACQZCBgIAANgIUIAIgAkEQajYCKCACIAJBCGo2AhAgASACQRhqEMmGgIAAIQAMEAsgAiAAQQhqKQMANwMIIAJBLGpBATYCACACQgI3AhwgAkHo5sCAADYCGCACQZGBgIAANgIUIAIgAkEQajYCKCACIAJBCGo2AhAgASACQRhqEMmGgIAAIQAMDwsgAiAAQQhqKQMANwMIIAJBLGpBATYCACACQgI3AhwgAkHM5sCAADYCGCACQZKBgIAANgIUIAIgAkEQajYCKCACIAJBCGo2AhAgASACQRhqEMmGgIAAIQAMDgsgAiAAQQRqKAIANgIIIAJBLGpBATYCACACQgI3AhwgAkGs5sCAADYCGCACQZOBgIAANgIUIAIgAkEQajYCKCACIAJBCGo2AhAgASACQRhqEMmGgIAAIQAMDQsgAiAAQQRqKQIANwMIIAJBLGpBATYCACACQgE3AhwgAkGY5sCAADYCGCACQZSBgIAANgIUIAIgAkEQajYCKCACIAJBCGo2AhAgASACQRhqEMmGgIAAIQAMDAsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBiObAgAA2AhggASACQRhqEMmGgIAAIQAMCwsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBgObAgAA2AhggASACQRhqEMmGgIAAIQAMCgsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJB7OXAgAA2AhggASACQRhqEMmGgIAAIQAMCQsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJB2OXAgAA2AhggASACQRhqEMmGgIAAIQAMCAsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBwOXAgAA2AhggASACQRhqEMmGgIAAIQAMBwsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBsOXAgAA2AhggASACQRhqEMmGgIAAIQAMBgsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBpOXAgAA2AhggASACQRhqEMmGgIAAIQAMBQsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBmOXAgAA2AhggASACQRhqEMmGgIAAIQAMBAsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJBhOXAgAA2AhggASACQRhqEMmGgIAAIQAMAwsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJB7OTAgAA2AhggASACQRhqEMmGgIAAIQAMAgsgAkEsakEANgIAIAJBtOTAgAA2AiggAkIBNwIcIAJB1OTAgAA2AhggASACQRhqEMmGgIAAIQAMAQsgASAAQQRqKAIAIABBCGooAgAQyIaAgAAhAAsgAkEwaiSAgICAACAACxQAIAEgACgCACAAKAIEEMiGgIAACxIAIAAgAiABKAIMEYCAgIAAAAs2AQF/IAAgAiABayICELGFgIAAIAAgACgCCCIDIAJqNgIIIAMgACgCAGogAiABIAIQsoWAgAALEQAgACAAKAIIIAEQyYWAgAALkwIBAX8jgICAgABB4ABrIgQkgICAgAAgBCABNgIIIAQgAzYCDAJAIAEgA0cNACAAIAIgARDzhoCAABogBEHgAGokgICAgAAPCyAEQShqQRRqQYiAgIAANgIAIARBNGpBlYGAgAA2AgAgBEEQakEUakEDNgIAIAQgBEEIajYCQCAEIARBDGo2AkQgBEHIAGpBFGpBADYCACAEQgM3AhQgBEHE6MCAADYCECAEQZWBgIAANgIsIARBlOfAgAA2AlggBEIBNwJMIARBkOnAgAA2AkggBCAEQShqNgIgIAQgBEHIAGo2AjggBCAEQcQAajYCMCAEIARBwABqNgIoIARBEGpB+OfAgAAQsYaAgAAQlIaAgAAACxkAIAAoAgAiACgCACAAKAIIIAEQ0IaAgAALQwAgACgCACEAAkAgARDKhoCAAA0AAkAgARDLhoCAAA0AIAAgARCchoCAAA8LIAAgARDhhoCAAA8LIAAgARDbhoCAAAtxAQF/I4CAgIAAQSBrIgIkgICAgAAgAiAANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBlOfAgAAgAkEIahCfhoCAACEBIAJBIGokgICAgAAgAQsgAQF/AkAgACgCBCIBRQ0AIAAoAgAgAUEBEM6CgIAACwsCAAsCAAsQACAAIAEgAiADELqFgIAACzABAX9BACEEAkACQCABIANHDQAgACACRw0BQQEhBAsgBA8LIAAgAiABEPSGgIAARQtXAQF/I4CAgIAAQRBrIgMkgICAgAACQCACRQ0AIAAgAjYCBCAAIAE2AgAgA0EQaiSAgICAAA8LQZjpwIAAQSsgA0EIakHE6cCAAEHM6sCAABCqhoCAAAALzgIBAn8jgICAgABBEGsiAiSAgICAACAAKAIAIQACQAJAAkACQCABQYABSQ0AIAJBADYCDCABQYAQSQ0BIAJBDGohAwJAIAFBgIAETw0AIAIgAUE/cUGAAXI6AA4gAiABQQZ2QT9xQYABcjoADSACIAFBDHZBD3FB4AFyOgAMQQMhAQwDCyACIAFBP3FBgAFyOgAPIAIgAUESdkHwAXI6AAwgAiABQQZ2QT9xQYABcjoADiACIAFBDHZBP3FBgAFyOgANQQQhAQwCCwJAIAAoAggiAyAAKAIERw0AIABBARCxhYCAACAAKAIIIQMLIAAoAgAgA2ogAToAACAAIAAoAghBAWo2AggMAgsgAiABQT9xQYABcjoADSACIAFBBnZBH3FBwAFyOgAMIAJBDGohA0ECIQELIAAgAyABEL2FgIAACyACQRBqJICAgIAAQQALEQAgACABIAEgAmoQsIWAgAALaAEBfyOAgICAAEEgayICJICAgIAAIAAoAgAhACACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCAAIAJBCGoQtYWAgAAhASACQSBqJICAgIAAIAELEwAgACgCACABIAIQvYWAgABBAAsMACAAIAEpAgA3AgALAgALUAEBfyOAgICAAEEQayIDJICAgIAAIAMgAhDEhYCAACADIAEgAhC9hYCAACAAQQhqIANBCGooAgA2AgAgACADKQMANwIAIANBEGokgICAgAALJwEBf0EBIQMCQCACQQRHDQAgACABKAAANgABQQAhAwsgACADOgAAC0YCAX8BfiOAgICAAEEQayICJICAgIAAIAJBCGogAUEAEMeFgIAAIAIpAwghAyAAQQA2AgggACADNwIAIAJBEGokgICAgAALIAEBfwJAIAAoAgQgACgCCCIBRg0AIAAgARDGhYCAAAsLgwEBAX8CQAJAAkACQCAAKAIEIgIgAUkNAAJAIAFFDQAgAiABRg0EIAAoAgAgAkEBIAEQz4KAgAAiAg0CIAFBARCFhoCAAAALIAAQtoWAgAAgAEEBNgIAQQAhAQwCC0Hc6sCAAEEkQfTpwIAAEI2GgIAAAAsgACACNgIACyAAIAE2AgQLC6QBAQN/I4CAgIAAQRBrIgMkgICAgAACQAJAIAFBf0wNAAJAAkAgAQ0AQQEhAgwBCyADQQhqIAFBARC7hYCAACADKAIMIQQgAygCCCEFAkACQCACDQAgBSAEEM2CgIAAIQIMAQsgBSAEENCCgIAAIQILIAJFDQILIAAgATYCBCAAIAI2AgAgA0EQaiSAgICAAA8LEMyFgIAAAAsgBSAEEIWGgIAAAAsXACAAIAI2AgggACADNgIEIAAgATYCAAtqAQF/I4CAgIAAQRBrIgMkgICAgAAgAyAAIAEgAkEBQQEQzYWAgAACQAJAIAMoAgBBAUcNACADQQhqKAIARQ0BQYDrwIAAQShB9OnAgAAQjYaAgAAACyADQRBqJICAgIAADwsQhoaAgAAACw4AIAAgASACEMKFgIAACxAAIAAgAjYCBCAAIAE2AgALCQAQhoaAgAAAC4UCAQJ/QQAhBgJAIAEoAgQiByACayADTw0AIAIgA2oiAyACSSECAkACQAJAAkAgBUUNACACRQ0BIAAgAzYCBCAAQQhqQQA2AgAMAwsgAkUNASAAIAM2AgQgAEEIakEANgIADAILIAdBAXQiAiADIAIgA0sbIQMLAkAgA0F/Sg0AIABBCGpBADYCAAwBCwJAAkAgBw0AIANBARDNgoCAACECDAELIAEoAgAgB0EBIAMQz4KAgAAhAgsCQAJAIAINACAERQ0BIANBARCFhoCAAAALIAEgAzYCBCABIAI2AgAMAgsgACADNgIEQQEhBiAAQQhqQQE2AgAMAQtBASEGCyAAIAY2AgALAgALFgAgACABKAIINgIEIAAgASgCADYCAAsKACAAELaFgIAACwwAQuSuwoWXm6WIEQsMAEKY3Iamxs6tyQgLDQBCmfbtpaTq/5TFAAsdACAAKAIAIgAoAgAgASAAKAIEKAIkEYCAgIAAAAsPACAAKAIAIAEQ1oWAgAAL5QMBAX8jgICAgABBEGsiAiSAgICAAAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAAtAAAOEgECAwQFBgcICQoLDA0ODxARAAELIAIgAUHA8cCAAEENEM2GgIAADBELIAIgAUGM88CAAEEIEM2GgIAADBALIAIgAUH88sCAAEEQEM2GgIAADA8LIAIgAUHr8sCAAEEREM2GgIAADA4LIAIgAUHc8sCAAEEPEM2GgIAADA0LIAIgAUHL8sCAAEEREM2GgIAADAwLIAIgAUG/8sCAAEEMEM2GgIAADAsLIAIgAUG28sCAAEEJEM2GgIAADAoLIAIgAUGm8sCAAEEQEM2GgIAADAkLIAIgAUGc8sCAAEEKEM2GgIAADAgLIAIgAUGP8sCAAEENEM2GgIAADAcLIAIgAUGF8sCAAEEKEM2GgIAADAYLIAIgAUH58cCAAEEMEM2GgIAADAULIAIgAUHu8cCAAEELEM2GgIAADAQLIAIgAUHm8cCAAEEIEM2GgIAADAMLIAIgAUHd8cCAAEEJEM2GgIAADAILIAIgAUHS8cCAAEELEM2GgIAADAELIAIgAUHN8cCAAEEFEM2GgIAACyACELaGgIAAIQEgAkEQaiSAgICAACABCxQAIAAoAgAgACgCBCABENKGgIAACzwAAkAgARDKhoCAAA0AAkAgARDLhoCAAA0AIAAgARDkhoCAAA8LIAAgARDhhoCAAA8LIAAgARDbhoCAAAs/AQF/I4CAgIAAQRBrIgMkgICAgAAgAyABNgIMIAMgADYCCCADQQhqQfDwwIAAQQAgAhCxhoCAABD3hYCAAAALAgALKgEBfwJAIAAoAgQiAUUNACAAQQhqKAIAIgBFDQAgASAAQQEQzoKAgAALCyMBAX8CQCAAQQRqKAIAIgFFDQAgACgCACABQQEQzoKAgAALCxwAAkAgAA0AQdDrwIAAQSsgARCNhoCAAAALIAALIAACQCAADQBB0OvAgABBK0G88MCAABCNhoCAAAALIAALgwUBBX8jgICAgABBEGsiAiSAgICAACAAKAIAIQACQAJAAkACQAJAAkACQCABQYABSQ0AIAJBADYCDCABQYAQSQ0BIAJBDGohAwJAIAFBgIAETw0AIAIgAUE/cUGAAXI6AA4gAiABQQZ2QT9xQYABcjoADSACIAFBDHZBD3FB4AFyOgAMQQMhAQwECyACIAFBP3FBgAFyOgAPIAIgAUESdkHwAXI6AAwgAiABQQZ2QT9xQYABcjoADiACIAFBDHZBP3FBgAFyOgANQQQhAQwDCwJAAkAgACgCCCIEIABBBGooAgBGDQAgACgCACEFDAELIARBAWoiBSAESQ0GIARBAXQiAyAFIAMgBUsbIgNBAEgNBgJAAkAgBA0AIANBARDNgoCAACEFDAELIAAoAgAgBEEBIAMQz4KAgAAhBQsgBUUNAiAAIAU2AgAgAEEEaiADNgIAIAAoAgghBAsgBSAEaiABOgAAIAAgACgCCEEBajYCCAwDCyACIAFBP3FBgAFyOgANIAIgAUEGdkEfcUHAAXI6AAwgAkEMaiEDQQIhAQwBCyADQQEQhYaAgAAACwJAAkAgAEEEaigCACIFIABBCGooAgAiBGsgAUkNACAAKAIAIQUMAQsgBCABaiIGIARJDQMgBUEBdCIEIAYgBCAGSxsiBEEASA0DAkACQCAFDQAgBEEBEM2CgIAAIQUMAQsgACgCACAFQQEgBBDPgoCAACEFCyAFRQ0CIAAgBTYCACAAQQRqIAQ2AgAgAEEIaigCACEECyAAQQhqIAQgAWo2AgAgBSAEaiADIAEQ84aAgAAaCyACQRBqJICAgIAAQQAPCyAEQQEQhYaAgAAACxCGhoCAAAALdAEBfyOAgICAAEEgayICJICAgIAAIAIgACgCADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQajrwIAAIAJBCGoQn4aAgAAhASACQSBqJICAgIAAIAEL1wEBA38CQAJAAkACQCAAKAIAIgBBBGooAgAiAyAAQQhqKAIAIgRrIAJJDQAgACgCACEDDAELIAQgAmoiBSAESQ0CIANBAXQiBCAFIAQgBUsbIgRBAEgNAgJAAkAgAw0AIARBARDNgoCAACEDDAELIAAoAgAgA0EBIAQQz4KAgAAhAwsgA0UNASAAIAM2AgAgAEEEaiAENgIAIABBCGooAgAhBAsgAEEIaiAEIAJqNgIAIAMgBGogASACEPOGgIAAGkEADwsgBEEBEIWGgIAAAAsQhoaAgAAACxQAIAAoAgAgACgCCCABENCGgIAACwkAEIaGgIAAAAsUACAAKAIAIAAoAgggARDShoCAAAtEAQF/I4CAgIAAQRBrIgIkgICAgAAgAkEIaiABQfvrwIAAQQsQzIaAgAAgAkEIahC0hoCAACEBIAJBEGokgICAgAAgAQtIAQF/I4CAgIAAQRBrIgIkgICAgAAgAiABELGGgIAANgIMIAIgADYCCCACQcDrwIAANgIEIAJBwOvAgAA2AgAgAhD2hYCAAAAL6gUBA38jgICAgABBwABrIgIkgICAgAACQAJAAkACQAJAIAAtAAAOAwACAQALIAIgAEEEaigCADYCBEEUQQEQzYKAgAAiAEUNAyAAQRBqQQAoAKTzwIAANgAAIABBCGpBACkAnPPAgAA3AAAgAEEAKQCU88CAADcAACACQpSAgIDAAjcCDCACIAA2AgggAkEoakEUakECNgIAIAJBJGpBm4GAgAA2AgAgAkIDNwIsIAJBuO/AgAA2AiggAkGcgYCAADYCHCACIAJBGGo2AjggAiACQQRqNgIgIAIgAkEIajYCGCABIAJBKGoQyYaAgAAhACACKAIMIgFFDQIgAigCCCABQQEQzoKAgAAMAgsgAEEEaigCACIAKAIAIAEgACgCBCgCIBGAgICAAAAhAAwBC0Gw7MCAACEDQRYhBAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAAtAAEOEgABAgMEBQYHCAkKCwwNDg8QEgALQcnuwIAAIQNBECEEDBELQbjuwIAAIQNBESEEDBALQabuwIAAIQNBEiEEDA8LQZbuwIAAIQNBECEEDA4LQYTuwIAAIQNBEiEEDA0LQfftwIAAIQNBDSEEDAwLQentwIAAIQMMCgtB1O3AgAAhA0EVIQQMCgtBye3AgAAhA0ELIQQMCQtBtO3AgAAhA0EVIQQMCAtBn+3AgAAhA0EVIQQMBwtBiO3AgAAhA0EXIQQMBgtB/OzAgAAhA0EMIQQMBQtB8+zAgAAhA0EJIQQMBAtB6ezAgAAhA0EKIQQMAwtB1OzAgAAhA0EVIQQMAgtBxuzAgAAhAwtBDiEECyACQTxqQQE2AgAgAiAENgIcIAIgAzYCGCACQZ2BgIAANgIMIAJCATcCLCACQaTvwIAANgIoIAIgAkEYajYCCCACIAJBCGo2AjggASACQShqEMmGgIAAIQALIAJBwABqJICAgIAAIAAPC0EUQQEQhYaAgAAACw0AQs+4j+zTwfnEv38LBABBAAsJACAAQQA2AgALFgAgACABKAIINgIEIAAgASgCADYCAAsUACAAKAIAIAAoAgggARDShoCAAAsUACAAKAIAIAAoAgggARDQhoCAAAuVAQEBfwJAAkACQCACQX9MDQACQAJAIAINAEEBIQMMAQsgAkEBEM2CgIAAIgNFDQILIAMgASACEPOGgIAAIQNBDEEEEM2CgIAAIgFFDQIgASACNgIIIAEgAjYCBCABIAM2AgAgAEGI7MCAADYCBCAAIAE2AgAPCxDjhYCAAAALIAJBARCFhoCAAAALQQxBBBCFhoCAAAALDAAgACABEPCFgIAAC/gDAQF/I4CAgIAAQTBrIgIkgICAgAACQAJAAkACQAJAIAAtAAAOAwACAQALIAIgAEEEaigCADYCDCACQRBqIAFB8O7AgABBAhDMhoCAACACQRBqQfLuwIAAQQQgAkEMakH47sCAABCnhoCAACEAIAJBEDoAHyAAQYjvwIAAQQQgAkEfakHg7sCAABCnhoCAACEBQRRBARDNgoCAACIARQ0DIABBEGpBACgApPPAgAA2AAAgAEEIakEAKQCc88CAADcAACAAQQApAJTzwIAANwAAIAJClICAgMACNwIkIAIgADYCICABQYzvwIAAQQcgAkEgakGU78CAABCnhoCAABC0hoCAACEAIAIoAiQiAUUNAiACKAIgIAFBARDOgoCAAAwCCyAAQQRqKAIAIQAgAkEgaiABQZnxwIAAQQYQzIaAgAAgAiAAQQhqNgIQIAJBIGpBiO/AgABBBCACQRBqQaDxwIAAEKeGgIAAGiACIAA2AhAgAkEgakGU8cCAAEEFIAJBEGpBsPHAgAAQp4aAgAAaIAJBIGoQtIaAgAAhAAwBCyACIAAtAAE6ABAgAkEgaiABQdnuwIAAQQQQzYaAgAAgAkEgaiACQRBqQeDuwIAAELWGgIAAELaGgIAAIQALIAJBMGokgICAgAAgAA8LQRRBARCFhoCAAAALnAEBAn8jgICAgABBEGsiBCSAgICAAAJAQQxBBBDNgoCAACIFDQBBDEEEEIWGgIAAAAsgBSABOgAIIAUgAzYCBCAFIAI2AgAgBSAELwANOwAJIAVBC2ogBEENakECai0AADoAACAAQQI6AAAgACAELwAKOwABIABBA2ogBEEKakECai0AADoAACAAQQRqIAU2AgAgBEEQaiSAgICAAAsEAAAACwIACyUBAX8gACABQQAoApj6woAAIgJBnoGAgAAgAhsRjoCAgAAAAAALvwEBAX8CQAJAAkBBACgCqPrCgABBAUYNAEEAQgE3A6j6woAADAELQQAoAqz6woAADQELAkBBACgCnPrCgAANAEEAKAKk+sKAACECQQAgATYCpPrCgABBACgCoPrCgAAhAUEAIAA2AqD6woAAQQBBADYCnPrCgAACQCACRQ0AIAEgAigCABGBgICAAAAgAigCBCIARQ0AIAEgACACKAIIEM6CgIAACw8LAAALQdDvwIAAQTRBnPDAgAAQ2YWAgAAAC2IBA38jgICAgABBEGsiASSAgICAACAAEK2GgIAAQazwwIAAEN2FgIAAIQIgABCshoCAABDehYCAACEDIAFBADYCBCABIAM2AgAgAUHM8MCAACAAEKyGgIAAIAIQ94WAgAAAC58CAQJ/I4CAgIAAQSBrIgQkgICAgABBASEFAkACQAJAAkBBACgCqPrCgABBAUYNAEEAQoGAgIAQNwOo+sKAAAwBC0EAQQAoAqz6woAAQQFqIgU2Aqz6woAAIAVBAksNAQsgBCADNgIcIAQgAjYCGCAEQcDrwIAANgIUIARBwOvAgAA2AhBBACgCnPrCgAAiAkF/TA0AQQAgAkEBaiICNgKc+sKAAAJAQQAoAqT6woAAIgNFDQBBACgCoPrCgAAhAiAEQQhqIAAgASgCEBGOgICAAAAgBCAEKQMINwMQIAIgBEEQaiADKAIMEY6AgIAAAEEAKAKc+sKAACECC0EAIAJBf2o2Apz6woAAIAVBAU0NAQsAAAsgACABEPyFgIAAAAviAgEFfyOAgICAAEHAAGsiAiSAgICAAAJAIAEoAgQiAw0AIAFBBGohAyABKAIAIQQgAkEANgIgIAJCATcDGCACIAJBGGo2AiQgAkEoakEQaiAEQRBqKQIANwMAIAJBKGpBCGogBEEIaikCADcDACACIAQpAgA3AyggAkEkakGo68CAACACQShqEJ+GgIAAGiACQQhqQQhqIgQgAigCIDYCACACIAIpAxg3AwgCQCABKAIEIgVFDQAgAUEIaigCACIGRQ0AIAUgBkEBEM6CgIAACyADIAIpAwg3AgAgA0EIaiAEKAIANgIAIAMoAgAhAwsgAUEBNgIEIAFBDGooAgAhBCABQQhqIgEoAgAhBSABQgA3AgACQEEMQQQQzYKAgAAiAQ0AQQxBBBCFhoCAAAALIAEgBDYCCCABIAU2AgQgASADNgIAIABB4PDAgAA2AgQgACABNgIAIAJBwABqJICAgIAAC4QCAQR/I4CAgIAAQcAAayICJICAgIAAIAFBBGohAwJAIAEoAgQNACABKAIAIQQgAkEANgIgIAJCATcDGCACIAJBGGo2AiQgAkEoakEQaiAEQRBqKQIANwMAIAJBKGpBCGogBEEIaikCADcDACACIAQpAgA3AyggAkEkakGo68CAACACQShqEJ+GgIAAGiACQQhqQQhqIgQgAigCIDYCACACIAIpAxg3AwgCQCABKAIEIgVFDQAgAUEIaigCACIBRQ0AIAUgAUEBEM6CgIAACyADIAIpAwg3AgAgA0EIaiAEKAIANgIACyAAQeDwwIAANgIEIAAgAzYCACACQcAAaiSAgICAAAthAQJ/IAEoAgAhAiABQQA2AgACQAJAIAJFDQAgASgCBCEDQQhBBBDNgoCAACIBRQ0BIAEgAzYCBCABIAI2AgAgAEGE8cCAADYCBCAAIAE2AgAPCwAAC0EIQQQQhYaAgAAACyAAAkAgASgCAA0AAAALIABBhPHAgAA2AgQgACABNgIACzEBAX8jgICAgABBEGsiAiSAgICAACACIAE2AgwgAiAANgIIIAJBCGoQ/YWAgAAaAAALBAAAAAtDACAAKAIAIQACQCABEMqGgIAADQACQCABEMuGgIAADQAgACABENmGgIAADwsgACABEOCGgIAADwsgACABENaGgIAACw8AIAAoAgAgARDthoCAAAt/AQJ/I4CAgIAAQRBrIgIkgICAgAAgACgCACIAKAIIIQMgACgCACEAIAIgARDOhoCAAAJAIANFDQADQCACIAA2AgwgAiACQQxqQajzwIAAELiGgIAAGiAAQQFqIQAgA0F/aiIDDQALCyACELmGgIAAIQAgAkEQaiSAgICAACAACwIACxcAIAAoAgAgACgCBEEAIAEQkoaAgAAACyoBAX8gACgCACIBKAIAIAEoAgQgACgCBCgCACAAKAIIKAIAEJKGgIAAAAv6BAEFfyOAgICAAEEQayICJICAgIAAAkACQAJAAkACQAJAAkAgAUGAAUkNACACQQA2AgwgAUGAEEkNASACQQxqIQMCQCABQYCABE8NACACIAFBP3FBgAFyOgAOIAIgAUEGdkE/cUGAAXI6AA0gAiABQQx2QQ9xQeABcjoADEEDIQEMBAsgAiABQT9xQYABcjoADyACIAFBEnZB8AFyOgAMIAIgAUEGdkE/cUGAAXI6AA4gAiABQQx2QT9xQYABcjoADUEEIQEMAwsCQAJAIAAoAggiBCAAQQRqKAIARg0AIAAoAgAhBQwBCyAEQQFqIgUgBEkNBiAEQQF0IgMgBSADIAVLGyIDQQBIDQYCQAJAIAQNACADQQEQzYKAgAAhBQwBCyAAKAIAIARBASADEM+CgIAAIQULIAVFDQIgACAFNgIAIABBBGogAzYCACAAKAIIIQQLIAUgBGogAToAACAAIAAoAghBAWo2AggMAwsgAiABQT9xQYABcjoADSACIAFBBnZBH3FBwAFyOgAMIAJBDGohA0ECIQEMAQsgA0EBEIWGgIAAAAsCQAJAIABBBGooAgAiBSAAQQhqKAIAIgRrIAFJDQAgACgCACEFDAELIAQgAWoiBiAESQ0DIAVBAXQiBCAGIAQgBksbIgRBAEgNAwJAAkAgBQ0AIARBARDNgoCAACEFDAELIAAoAgAgBUEBIAQQz4KAgAAhBQsgBUUNAiAAIAU2AgAgAEEEaiAENgIAIABBCGooAgAhBAsgAEEIaiAEIAFqNgIAIAUgBGogAyABEPOGgIAAGgsgAkEQaiSAgICAAA8LIARBARCFhoCAAAALEIaGgIAAAAsNACAAIAEQ9IWAgAAACxcAQc/zwIAAQRFB4PPAgAAQjYaAgAAACwkAEIaGgIAAAAvADQELfyOAgICAAEEwayIDJICAgIAAAkACQAJAAkACQAJAAkAgAkF/TA0AAkACQCACDQBBASEEDAELIAJBARDNgoCAACIERQ0CCyADQQA2AgggAyACNgIEIAMgBDYCAAJAIAJFDQAgASACaiEFIAEhBkEAIQcDQCAGIQggBkEBaiEJAkACQAJAIAYsAAAiBEF/Sg0AAkACQCAJIAVHDQBBACEKIAUhCwwBCyAGLQABQT9xIQogBkECaiIJIQsLIARBH3EhDCAEQf8BcSIEQd8BSw0BIAogDEEGdHIhBCAJIQYMAgsgBEH/AXEhBCAJIQYMAQsCQAJAIAsgBUcNAEEAIQYgBSELDAELIAstAABBP3EhBiALQQFqIgkhCwsgBiAKQQZ0ciEKAkAgBEHwAU8NACAKIAxBDHRyIQQgCSEGDAELAkACQCALIAVHDQBBACEEIAkhBgwBCyALQQFqIQYgCy0AAEE/cSEECyAKQQZ0IAxBEnRBgIDwAHFyIARyIgRBgIDEAEYNAgsCQAJAAkACQCAEQaMHRg0AIARBgIDEAEYNBSADQSBqIAQQ8IaAgAAgAygCJCIJRQ0BIAMoAighBCADIAMoAiAQhIaAgAAgAyAJEISGgIAAIAQNAgwDCyADIAI2AiQgAyABNgIgAkAgB0UNACAHIAJGDQAgByACTw0IIAEgB2osAABBv39MDQgLIAEgB2ohBAJAAkADQCAEIAFGDQECQAJAIARBf2oiCy0AACIJQRh0QRh1IgpBAEgNACALIQQMAQsCQAJAIAsgAUcNAEEAIQkgASEEDAELAkAgBEF+aiILLQAAIglBwAFxQYABRg0AIAlBH3EhCSALIQQMAQsCQAJAIAsgAUcNAEEAIQsgASEEDAELAkAgBEF9aiIMLQAAIgtBwAFxQYABRg0AIAtBD3EhCyAMIQQMAQsCQAJAIAwgAUcNAEEAIQwgASEEDAELIARBfGoiBC0AAEEHcUEGdCEMCyAMIAtBP3FyIQsLIAtBBnQgCUE/cXIhCQsgCUEGdCAKQT9xciIJQYCAxABGDQILIAkQ7oaAgAANAAsgCRDvhoCAAEUNACADIAI2AhQgAyABNgIQIAMgB0ECaiIENgIYIAMgAjYCHAJAIARFDQAgBCACRg0AIAQgAk8NCyABIARqLAAAQb9/TA0LCyABIARqIQRBACELA0AgBCAFRg0CIARBAWohCgJAAkAgBCwAACIJQX9MDQAgCUH/AXEhCSAKIQQMAQsCQAJAIAogBUcNAEEAIQwgBSEKDAELIARBAmohCiAELQABQT9xIQwLIAlBH3EhDQJAIAlB/wFxIglB3wFLDQAgDCANQQZ0ciEJIAohBAwBCwJAAkAgCiAFRw0AQQAhCiAFIQQMAQsgCkEBaiEEIAotAABBP3EhCgsgCiAMQQZ0ciEKAkAgCUHwAU8NACAKIA1BDHRyIQkMAQsCQAJAIAQgBUcNAEEAIQkgBSEEDAELIAQtAABBP3EhCSAEQQFqIQQLIApBBnQgDUESdEGAgPAAcXIgCXIiCUGAgMQARg0DCwJAAkAgC0H/AXENACAJEO6GgIAARQ0AQYCAxAAhCUEAIQsMAQtBASELCyAJQYCAxABGDQALIAkQ74aAgABFDQELAkACQCADKAIEIgkgAygCCCIEa0ECSQ0AIAMoAgAhCQwBCyAEQQJqIgsgBEkNDSAJQQF0IgogCyAKIAtLGyILQQBIDQ0CQAJAIAkNACALQQEQzYKAgAAhCQwBCyADKAIAIAlBASALEM+CgIAAIQkLIAlFDQsgAyALNgIEIAMgCTYCAAsgAyAEQQJqNgIIIAkgBGpBz4cCOwAADAMLAkACQCADKAIEIgkgAygCCCIEa0ECSQ0AIAMoAgAhCQwBCyAEQQJqIgsgBEkNDCAJQQF0IgogCyAKIAtLGyILQQBIDQwCQAJAIAkNACALQQEQzYKAgAAhCQwBCyADKAIAIAlBASALEM+CgIAAIQkLIAlFDQsgAyALNgIEIAMgCTYCAAsgAyAEQQJqNgIIIAkgBGpBz4UCOwAADAILIAMoAiAhBAsgAyAEEISGgIAACyAHIAhrIAZqIQcgBSAGRw0ACwsgACADKQMANwIAIABBCGogA0EIaigCADYCACADQTBqJICAgIAADwsQh4aAgAAACyACQQEQhYaAgAAACyADQSBqIAcQgoaAgAAACyADIANBHGo2AiggAyADQRhqNgIkIAMgA0EQajYCICADQSBqEIOGgIAAAAsgC0EBEIWGgIAAAAsgC0EBEIWGgIAAAAsQhoaAgAAAC3IBAn8CQAJAIAEoAggiAkF/TA0AIAEoAgAhAQJAAkAgAg0AQQEhAwwBCyACQQEQzYKAgAAiA0UNAgsgAyABIAIQ84aAgAAhASAAIAI2AgggACACNgIEIAAgATYCAA8LEIeGgIAAAAsgAkEBEIWGgIAAAAuHAQEBfyOAgICAAEEQayICJICAgIAAIAIgAUHw88CAAEENEMyGgIAAIAIgADYCDCACQf3zwIAAQQUgAkEMakGE9MCAABCnhoCAABogAiAAQQxqNgIMIAJBlPTAgABBBSACQQxqQZz0wIAAEKeGgIAAGiACELSGgIAAIQAgAkEQaiSAgICAACAACwIAC4EBAQF/I4CAgIAAQTBrIgMkgICAgAAgAyACNgIEIAMgATYCACADQRxqQQI2AgAgA0EsakGEgICAADYCACADQgI3AgwgA0Hwj8GAADYCCCADQYSAgIAANgIkIAMgA0EgajYCGCADIAM2AiggAyADQQRqNgIgIANBCGogABCUhoCAAAALVAEBfyOAgICAAEEgayIDJICAgIAAIANBFGpBADYCACADQaz0wIAANgIQIANCATcCBCADIAE2AhwgAyAANgIYIAMgA0EYajYCACADIAIQlIaAgAAAC7IEAQd/AkAgAUH/CUsNACABQQV2IQICQAJAAkACQAJAAkACQCAAKAIAIgNFDQAgA0F/aiEEIAAgA0ECdGohBSAAIAMgAmpBAnRqIQMDQCAEQSdLDQIgAiAEaiIGQSdLDQMgAyAFKAIANgIAIAVBfGohBSADQXxqIQMgBEF/aiIEQX9HDQALCwJAIAJFDQAgAEEEaiEFIAJBAnQhA0EAIQQDQCAEQaABRg0EIAUgBGpBADYCACADIARBBGoiBEcNAAsLIAAoAgAiBCACaiEFAkAgAUEfcSIGDQAgACAFNgIAIAAPCyAFQX9qIgNBJ0sNAyAFIQcCQCAAIANBAnRqQQRqKAIAIgNBACABa0EfcSIBdiIIRQ0AIAVBJ0sNBSAAIAVBAnRqQQRqIAg2AgAgBUEBaiEHCwJAIAJBAWoiCCAFTw0AIAQgAmpBAnQgAGpBfGohBANAIAVBfmpBJ0sNByAEQQRqIAMgBnQgBCgCACIDIAF2cjYCACAEQXxqIQQgCCAFQX9qIgVJDQALCyAAIAJBAnRqQQRqIgQgBCgCACAGdDYCACAAIAc2AgAgAA8LQYilwYAAIARBKBCMhoCAAAALQYilwYAAIAZBKBCMhoCAAAALQYilwYAAQShBKBCMhoCAAAALQYilwYAAIANBKBCMhoCAAAALQYilwYAAIAVBKBCMhoCAAAALQYilwYAAIAVBfmpBKBCMhoCAAAALQbKlwYAAQR1BiKXBgAAQjYaAgAAAC4UBAQF/I4CAgIAAQTBrIgIkgICAgAAgAiABNgIEIAIgADYCACACQRxqQQI2AgAgAkEsakGEgICAADYCACACQgI3AgwgAkHok8GAADYCCCACQYSAgIAANgIkIAIgAkEgajYCGCACIAJBBGo2AiggAiACNgIgIAJBCGpB+JPBgAAQlIaAgAAAC4UBAQF/I4CAgIAAQTBrIgIkgICAgAAgAiABNgIEIAIgADYCACACQRxqQQI2AgAgAkEsakGEgICAADYCACACQgI3AgwgAkGslMGAADYCCCACQYSAgIAANgIkIAIgAkEgajYCGCACIAJBBGo2AiggAiACNgIgIAJBCGpBvJTBgAAQlIaAgAAAC6sHAQx/IABBEGooAgAhAwJAAkACQAJAIABBCGooAgAiBEEBRg0AIANBAUYNASAAKAIYIAEgAiAAQRxqKAIAKAIMEY+AgIAAACEDDAMLIANBAUcNAQsCQAJAIAINAEEAIQIMAQsgASACaiEFIABBFGooAgBBAWohBkEAIQcgASEDIAEhCANAIANBAWohCQJAAkACQCADLAAAIgpBf0oNAAJAAkAgCSAFRw0AQQAhCyAFIQMMAQsgAy0AAUE/cSELIANBAmoiCSEDCyAKQR9xIQwCQCAKQf8BcSIKQd8BSw0AIAsgDEEGdHIhCgwCCwJAAkAgAyAFRw0AQQAhDSAFIQ4MAQsgAy0AAEE/cSENIANBAWoiCSEOCyANIAtBBnRyIQsCQCAKQfABTw0AIAsgDEEMdHIhCgwCCwJAAkAgDiAFRw0AQQAhCiAJIQMMAQsgDkEBaiEDIA4tAABBP3EhCgsgC0EGdCAMQRJ0QYCA8ABxciAKciIKQYCAxABHDQIMBAsgCkH/AXEhCgsgCSEDCwJAIAZBf2oiBkUNACAHIAhrIANqIQcgAyEIIAUgA0cNAQwCCwsgCkGAgMQARg0AAkACQCAHRQ0AIAcgAkYNAEEAIQMgByACTw0BIAEgB2osAABBQEgNAQsgASEDCyAHIAIgAxshAiADIAEgAxshAQsgBEEBRg0AIAAoAhggASACIABBHGooAgAoAgwRj4CAgAAADwtBACEJAkAgAkUNACACIQogASEDA0AgCSADLQAAQcABcUGAAUZqIQkgA0EBaiEDIApBf2oiCg0ACwsCQCACIAlrIAAoAgwiBkkNACAAKAIYIAEgAiAAQRxqKAIAKAIMEY+AgIAAAA8LQQAhB0EAIQkCQCACRQ0AQQAhCSACIQogASEDA0AgCSADLQAAQcABcUGAAUZqIQkgA0EBaiEDIApBf2oiCg0ACwsgCSACayAGaiIJIQoCQAJAAkBBACAALQAgIgMgA0EDRhsOBAIBAAECCyAJQQF2IQcgCUEBakEBdiEKDAELQQAhCiAJIQcLIAdBAWohAwJAA0AgA0F/aiIDRQ0BIAAoAhggACgCBCAAKAIcKAIQEYCAgIAAAEUNAAtBAQ8LIAAoAgQhCUEBIQMgACgCGCABIAIgACgCHCgCDBGPgICAAAANACAKQQFqIQMgACgCHCEKIAAoAhghAANAAkAgA0F/aiIDDQBBAA8LIAAgCSAKKAIQEYCAgIAAAEUNAAtBAQ8LIAMLpgkBBn8jgICAgABB8ABrIgQkgICAgAAgBCADNgIMIAQgAjYCCEEBIQUgASEGAkAgAUGBAkkNAEEAIAFrIQdBgAIhCANAAkAgCCABTw0AIAAgCGosAABBv39MDQBBACEFIAghBgwCCyAIQX9qIQZBACEFIAhBAUYNASAHIAhqIQkgBiEIIAlBAUcNAAsLIAQgBjYCFCAEIAA2AhAgBEEAQQUgBRs2AhwgBEGs9MCAAEGul8GAACAFGzYCGAJAAkACQAJAIAIgAUsiCA0AIAMgAUsNACACIANLDQECQAJAIAJFDQAgASACRg0AIAEgAk0NASAAIAJqLAAAQUBIDQELIAMhAgsgBCACNgIgIAJFDQIgAiABRg0CIAFBAWohCQNAAkAgAiABTw0AIAAgAmosAABBQE4NBAsgAkF/aiEIIAJBAUYNBCAJIAJGIQYgCCECIAZFDQAMBAsLIAQgAiADIAgbNgIoIARBMGpBFGpBAzYCACAEQcgAakEUakG4gYCAADYCACAEQdQAakG4gYCAADYCACAEQgM3AjQgBEHUl8GAADYCMCAEQYSAgIAANgJMIAQgBEHIAGo2AkAgBCAEQRhqNgJYIAQgBEEQajYCUCAEIARBKGo2AkggBEEwakHsl8GAABCUhoCAAAALIARB5ABqQbiBgIAANgIAIARByABqQRRqQbiBgIAANgIAIARB1ABqQYSAgIAANgIAIARBMGpBFGpBBDYCACAEQgQ3AjQgBEGgmMGAADYCMCAEQYSAgIAANgJMIAQgBEHIAGo2AkAgBCAEQRhqNgJgIAQgBEEQajYCWCAEIARBDGo2AlAgBCAEQQhqNgJIIARBMGpBwJjBgAAQlIaAgAAACyACIQgLAkAgCCABRg0AQQEhBgJAAkACQAJAIAAgCGoiCSwAACICQX9KDQBBACEFIAAgAWoiBiEBAkAgCUEBaiAGRg0AIAlBAmohASAJLQABQT9xIQULIAJBH3EhCSACQf8BcUHfAUsNASAFIAlBBnRyIQEMAgsgBCACQf8BcTYCJCAEQShqIQIMAgtBACEAIAYhBwJAIAEgBkYNACABQQFqIQcgAS0AAEE/cSEACyAAIAVBBnRyIQECQCACQf8BcUHwAU8NACABIAlBDHRyIQEMAQtBACECAkAgByAGRg0AIActAABBP3EhAgsgAUEGdCAJQRJ0QYCA8ABxciACciIBQYCAxABGDQILIAQgATYCJEEBIQYgBEEoaiECIAFBgAFJDQBBAiEGIAFBgBBJDQBBA0EEIAFBgIAESRshBgsgBCAINgIoIAQgBiAIajYCLCAEQTBqQRRqQQU2AgAgBEHsAGpBuIGAgAA2AgAgBEHkAGpBuIGAgAA2AgAgBEHIAGpBFGpBuYGAgAA2AgAgBEHUAGpBhIGAgAA2AgAgBEIFNwI0IARBlJnBgAA2AjAgBCACNgJYIARBhICAgAA2AkwgBCAEQcgAajYCQCAEIARBGGo2AmggBCAEQRBqNgJgIAQgBEEkajYCUCAEIARBIGo2AkggBEEwakG8mcGAABCUhoCAAAALQamOwYAAQStB0JjBgAAQjYaAgAAAC+ECAgJ/AX4jgICAgABBgAFrIgIkgICAgAAgACgCACEAAkACQAJAAkACQCABKAIAIgNBEHENACAAKQMAIQQgA0EgcQ0BIARBASABEMOGgIAAIQAMAgsgACkDACEEQQAhAANAIAIgAGpB/wBqIASnQQ9xIgNBMHIgA0HXAGogA0EKSRs6AAAgAEF/aiEAIARCBIgiBEIAUg0ACyAAQYABaiIDQYEBTw0CIAFBAUHBkMGAAEECIAIgAGpBgAFqQQAgAGsQxYaAgAAhAAwBC0EAIQADQCACIABqQf8AaiAEp0EPcSIDQTByIANBN2ogA0EKSRs6AAAgAEF/aiEAIARCBIgiBEIAUg0ACyAAQYABaiIDQYEBTw0CIAFBAUHBkMGAAEECIAIgAGpBgAFqQQAgAGsQxYaAgAAhAAsgAkGAAWokgICAgAAgAA8LIANBgAEQkIaAgAAACyADQYABEJCGgIAAAAtCAQF/I4CAgIAAQRBrIgIkgICAgAAgAiABNgIMIAIgADYCCCACQfCOwYAANgIEIAJBrPTAgAA2AgAgAhD2hYCAAAALnQQEAn8BfgJ/AX4CQAJAAkACQAJAIAFBB3EiAkUNACAAKAIAIgNBKU8NAQJAAkAgAw0AQQAhAwwBCyACQQJ0Qdz1wIAAajUCACEEIAAgA0ECdGpBBGohBSADQQJ0IQYgAEEEaiECQgAhBwNAIAIgAjUCACAEfiAHfCIHPgIAIAJBBGohAiAHQiCIIQcgBkF8aiIGDQALIAenIgJFDQAgA0EnSw0DIAUgAjYCACADQQFqIQMLIAAgAzYCAAsCQCABQQhxRQ0AIAAoAgAiA0EpTw0DAkACQCADDQBBACEDDAELIAAgA0ECdCIGakEEaiEFIABBBGohAkIAIQcDQCACIAI1AgBCgMLXL34gB3wiBz4CACACQQRqIQIgB0IgiCEHIAZBfGoiBg0ACyAHpyICRQ0AIANBJ0sNBSAFIAI2AgAgA0EBaiEDCyAAIAM2AgALAkAgAUEQcUUNACAAQaz2wIAAQQIQloaAgAAaCwJAIAFBIHFFDQAgAEG09sCAAEEEEJaGgIAAGgsCQCABQcAAcUUNACAAQcT2wIAAQQcQloaAgAAaCwJAIAFBgAFxRQ0AIABB4PbAgABBDhCWhoCAABoLAkAgAUGAAnFFDQAgAEGY98CAAEEbEJaGgIAAGgsgAA8LIANBKBCPhoCAAAALQYilwYAAIANBKBCMhoCAAAALIANBKBCPhoCAAAALQYilwYAAIANBKBCMhoCAAAALoAYDDH8CfgF/I4CAgIAAQaABayIDJICAgIAAIANBAEGgARDyhoCAACEEAkACQCAAKAIAIgVBKU8NACAAQQRqIQYCQCAFIAJJDQAgASACQQJ0aiEHAkACQAJAIAVFDQAgBUEBaiEIIAVBAnQhAkEAIQlBACEKA0AgBCAJQQJ0aiELA0AgCSEMIAshAyABIAdGDQcgA0EEaiELIAxBAWohCSABKAIAIQ0gAUEEaiIOIQEgDUUNAAsgDa0hD0IAIRAgAiENIAwhASAGIQsDQCABQSdLDQMgAyAQIAM1AgB8IAs1AgAgD358IhA+AgAgEEIgiCEQIANBBGohAyABQQFqIQEgC0EEaiELIA1BfGoiDQ0ACyAFIQMCQCAQpyIBRQ0AIAwgBWoiA0EnSw0EIAQgA0ECdGogATYCACAIIQMLIAMgDGoiAyAKIAogA0kbIQogDiEBDAALC0EAIQpBACEDA0AgByABRg0FIANBAWohAyABKAIAIQsgAUEEaiIJIQEgC0UNACADQX9qIgEgCiAKIAFJGyEKIAkhAQwACwtBiKXBgAAgAUEoEIyGgIAAAAtBiKXBgAAgA0EoEIyGgIAAAAsgAkECdCEIIAJBAWohESAAIAVBAnRqQQRqIQ5BACEMIAYhC0EAIQoCQANAIAQgDEECdGohCQNAIAwhDSAJIQMgCyAORg0EIANBBGohCSANQQFqIQwgCygCACEHIAtBBGoiBSELIAdFDQALIAetIQ9CACEQIAghByANIQsgASEJAkADQCALQSdLDQEgAyAQIAM1AgB8IAk1AgAgD358IhA+AgAgEEIgiCEQIANBBGohAyALQQFqIQsgCUEEaiEJIAdBfGoiBw0ACyACIQMCQCAQpyILRQ0AIA0gAmoiA0EnSw0DIAQgA0ECdGogCzYCACARIQMLIAMgDWoiAyAKIAogA0kbIQogBSELDAELC0GIpcGAACALQSgQjIaAgAAAC0GIpcGAACADQSgQjIaAgAAACyAFQSgQj4aAgAAACyAGIARBoAEQ84aAgAAaIAAgCjYCACAEQaABaiSAgICAACAAC6ojAwF/BH4ZfyOAgICAAEHQCmsiBCSAgICAAAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAEpAwAiBVANACABKQMIIgZQDQEgASkDECIHUA0CIAUgB3wiCCAFVA0DIAUgBn0gBVYNBCADQRFJDQggASwAGiEJIAEuARghCkEAIQEgBEGoCWpBAEGgARDyhoCAABogCq1CMIZCMIcgCEJ/fHl9QsKawegEfkKAoc2gtAJ8QiCIp0EQdEEQdSELIARBqAlqIQwDQCABQShGDQYgDCAFPgIAIAxBBGohDCABQQFqIQEgBUIgiCIFUEUNAAsgBCABNgIQIARBEGpBBHIgBEGoCWpBoAEQ84aAgAAhDUEAIQEgBEGoCWpBAEGgARDyhoCAABogBEGoCWohDANAIAFBKEYNByAMIAY+AgAgDEEEaiEMIAFBAWohASAGQiCIIgZQRQ0ACyAEIAE2ArgBIARBuAFqQQRyIARBqAlqQaABEPOGgIAAGkEAIQEgBEGoCWpBAEGgARDyhoCAABogBEGoCWohDANAIAFBKEYNCCAMIAc+AgAgDEEEaiEMIAFBAWohASAHQiCIIgdQRQ0ACyAEIAE2AuACIARB4AJqQQRyIARBqAlqQaABEPOGgIAAGiAEQoGAgIAQNwOIBCAEQZAEakEAQZwBEPKGgIAAGgJAAkAgCkEASA0AIARBEGogChCOhoCAABogBEG4AWogChCOhoCAABogBEHgAmogChCOhoCAABoMAQsgBEGIBGpBACAKa0EQdEEQdRCOhoCAABoLAkACQCALQX9KDQAgBEEQakEAIAtrQRB0QRB1IgEQlYaAgAAaIARBuAFqIAEQlYaAgAAaIARB4AJqIAEQlYaAgAAaDAELIARBiARqIAsQlYaAgAAaCyAEIAQoAhAiDjYCqAkgBEGoCWpBBHIgDUGgARDzhoCAABoCQAJAAkAgDiAEKALgAiIPIA4gD0sbIhBBKEsNACAQDQFBACEQDAILIBBBKBCPhoCAAAALIARBqAlqQQRyIQEgBEHgAmpBBHIhDEEAIRFBACESA0AgASABKAIAIhMgDCgCAGoiCiARQQFxaiIRNgIAIAogE0kgESAKSXIhESABQQRqIQEgDEEEaiEMIBJBAWoiEiAQSQ0ACyARRQ0AIBBBJ0sNCiAEQagJaiAQQQJ0akEEakEBNgIAIBBBAWohEAsgBCAQNgKoCSAEKAKIBCIRIBAgESAQSxsiAUEpTw0KIAFBAnQhAQJAAkACQAJAA0ACQCABDQBBf0EAIAEbIQwMAgsgAUUNAiAEQYgEaiABaiEMIARBqAlqIAFqIQogAUF8aiEBQX8gDCgCACIMIAooAgAiCkcgDCAKSRsiDEUNAAsLIAwgCUgNAQsgDkEpTw0NAkACQCAODQBBACEODAELIARBEGogDkECdCIMakEEaiEKIARBEGpBBHIhAUIAIQUDQCABIAE1AgBCCn4gBXwiBT4CACABQQRqIQEgBUIgiCEFIAxBfGoiDA0ACyAFpyIBRQ0AIA5BJ0sNDyAKIAE2AgAgDkEBaiEOCyAEIA42AhAgBCgCuAEiCkEpTw0PAkACQCAKDQBBACEKDAELIARBuAFqIApBAnQiDGpBBGohEiAEQbgBakEEciEBQgAhBQNAIAEgATUCAEIKfiAFfCIFPgIAIAFBBGohASAFQiCIIQUgDEF8aiIMDQALIAWnIgFFDQAgCkEnSw0RIBIgATYCACAKQQFqIQoLIAQgCjYCuAEgD0EpTw0RAkAgDw0AIARBADYC4AIMAgsgBEHgAmogD0ECdCIMakEEaiEKIARB4AJqQQRyIQFCACEFA0AgASABNQIAQgp+IAV8IgU+AgAgAUEEaiEBIAVCIIghBSAMQXxqIgwNAAsCQCAFpyIBRQ0AIA9BJ0sNEyAKIAE2AgAgD0EBaiEPCyAEIA82AuACDAELIAtBAWohCwsgBCARNgKwBSAEQbAFakEEciAEQYgEakEEciIUQaABEPOGgIAAIRUgBEGwBWpBARCOhoCAABogBCAEKAKIBDYC2AYgBEHYBmpBBHIgFEGgARDzhoCAACEWIARB2AZqQQIQjoaAgAAaIAQgBCgCiAQ2AoAIIARBgAhqQQRyIBRBoAEQ84aAgAAhFyAEQYAIakEDEI6GgIAAGgJAAkACQAJAIAQoAhAiECAEKAKACCIYIBAgGEsbIg5BKEsNACAEQagJakEEciEZIARB4AJqQQRyIRogBEEQakEEciEbIARBuAFqQQRyIRxBACEdA0AgHSEeIA5BAnQhAQJAAkACQANAAkAgAQ0AQX9BACABGyEMDAILIAFFDQIgBEEQaiABaiEMIARBgAhqIAFqIQogAUF8aiEBQX8gDCgCACIMIAooAgAiCkcgDCAKSRsiDEUNAAsLQQAhHyAMQf8BcUEBSw0BCwJAIA5FDQBBASERIA4hEiAbIQEgFyEMA0AgASABKAIAIhMgDCgCAEF/c2oiCiARQQFxaiIRNgIAIAogE0kgESAKSXIhESABQQRqIQEgDEEEaiEMIBJBf2oiEg0ACyARRQ0YCyAEIA42AhBBCCEfIA4hEAsgECAEKALYBiIBIBAgAUsbIg5BKU8NFyAOQQJ0IQECQAJAAkADQAJAIAENAEF/QQAgARshDAwCCyABRQ0CIARBEGogAWohDCAEQdgGaiABaiEKIAFBfGohAUF/IAwoAgAiDCAKKAIAIgpHIAwgCkkbIgxFDQALCyAMQf8BcUEBTQ0AIBAhDgwBCwJAIA5FDQBBACESQQEhESAbIQEgFiEMA0AgASABKAIAIhMgDCgCAEF/c2oiCiARQQFxaiIRNgIAIAogE0kgESAKSXIhESABQQRqIQEgDEEEaiEMIBJBAWoiEiAOSQ0ACyARRQ0aCyAEIA42AhAgH0EEciEfCyAOIAQoArAFIgEgDiABSxsiD0EpTw0ZIA9BAnQhAQJAAkACQANAAkAgAQ0AQX9BACABGyEMDAILIAFFDQIgBEEQaiABaiEMIARBsAVqIAFqIQogAUF8aiEBQX8gDCgCACIMIAooAgAiCkcgDCAKSRsiDEUNAAsLIAxB/wFxQQFNDQAgDiEPDAELAkAgD0UNAEEAIRJBASERIBshASAVIQwDQCABIAEoAgAiEyAMKAIAQX9zaiIKIBFBAXFqIhE2AgAgCiATSSARIApJciERIAFBBGohASAMQQRqIQwgEkEBaiISIA9JDQALIBFFDRwLIAQgDzYCECAfQQJqIR8LIA8gBCgCiAQiICAPICBLGyIQQSlPDRsgEEECdCEBAkACQAJAA0ACQCABDQBBf0EAIAEbIQwMAgsgAUUNAiAEQRBqIAFqIQwgBEGIBGogAWohCiABQXxqIQFBfyAMKAIAIgwgCigCACIKRyAMIApJGyIMRQ0ACwsgDEH/AXFBAU0NACAPIRAMAQsCQCAQRQ0AQQAhEkEBIREgGyEBIBQhDANAIAEgASgCACITIAwoAgBBf3NqIgogEUEBcWoiETYCACAKIBNJIBEgCklyIREgAUEEaiEBIAxBBGohDCASQQFqIhIgEEkNAAsgEUUNHgsgBCAQNgIQIB9BAWohHwsgHiADRg0DIAIgHmogH0EwajoAACAQIAQoArgBIh8gECAfSxsiAUEpTw0dIB5BAWohHSABQQJ0IQECQANAAkAgAQ0AQX9BACABGyEPDAILAkAgAQ0AQQEhDwwCCyAEQRBqIAFqIQwgBEG4AWogAWohCiABQXxqIQFBfyAMKAIAIgwgCigCACIKRyAMIApJGyIPRQ0ACwsgBCAQNgKoCSAZIA1BoAEQ84aAgAAhAQJAAkACQCAQIAQoAuACIiEgECAhSxsiDkEoSw0AIA4NAUEAIQ4MAgsgDkEoEI+GgIAAAAtBACERIAEhASAaIQxBACESA0AgASABKAIAIhMgDCgCAGoiCiARQQFxaiIRNgIAIAogE0kgESAKSXIhESABQQRqIQEgDEEEaiEMIBJBAWoiEiAOSQ0ACyARRQ0AIA5BJ0sNHyAEQagJaiAOQQJ0akEEakEBNgIAIA5BAWohDgsgBCAONgKoCSAgIA4gICAOSxsiAUEpTw0fIAFBAnQhAQJAA0ACQCABDQBBf0EAIAEbIQwMAgsCQCABDQBBASEMDAILIARBiARqIAFqIQwgBEGoCWogAWohCiABQXxqIQFBfyAMKAIAIgwgCigCACIKRyAMIApJGyIMRQ0ACwsgDyAJSA0CIAwgCUgNAiAQQSlPDSACQAJAIBANAEEAIRAMAQsgBEEQaiAQQQJ0IgxqQQRqIQpCACEFIBshAQNAIAEgATUCAEIKfiAFfCIFPgIAIAFBBGohASAFQiCIIQUgDEF8aiIMDQALIAWnIgFFDQAgEEEnSw0iIAogATYCACAQQQFqIRALIAQgEDYCECAfQSlPDSICQAJAIB8NAEEAIR8MAQsgBEG4AWogH0ECdCIMakEEaiEKQgAhBSAcIQEDQCABIAE1AgBCCn4gBXwiBT4CACABQQRqIQEgBUIgiCEFIAxBfGoiDA0ACyAFpyIBRQ0AIB9BJ0sNJCAKIAE2AgAgH0EBaiEfCyAEIB82ArgBICFBKU8NJAJAAkAgIQ0AQQAhIQwBCyAEQeACaiAhQQJ0IgxqQQRqIQpCACEFIBohAQNAIAEgATUCAEIKfiAFfCIFPgIAIAFBBGohASAFQiCIIQUgDEF8aiIMDQALIAWnIgFFDQAgIUEnSw0mIAogATYCACAhQQFqISELIAQgITYC4AIgECAYIBAgGEsbIg5BKE0NAAsLIA5BKBCPhoCAAAALAkAgDCAJTg0AAkAgDyAJTg0AIARBEGpBARCOhoCAACgCACIBIAQoAogEIgwgASAMSxsiAUEpTw0lIAFBAnQhAQJAA0ACQCABDQBBf0EAIAEbIQwMAgsgAUUNAiAEQRBqIAFqIQwgBEGIBGogAWohCiABQXxqIQFBfyAMKAIAIgwgCigCACIKRyAMIApJGyIMRQ0ACwsgDEH/AXFBAUsNAQsgBEEIaiACIAMgHRCYhoCAACAELQAIQQFxRQ0AIB0gA08NAiACIB1qIAQtAAk6AAAgC0EBaiELIB5BAmohHQsgACALOwEEIAAgHTYCACAEQdAKaiSAgICAAA8LQYj7wIAAIAMgAxCMhoCAAAALQZj7wIAAIB0gAxCMhoCAAAALQa74wIAAQRxBzPjAgAAQjYaAgAAAC0Hc+MCAAEEdQfz4wIAAEI2GgIAAAAtBjPnAgABBHEGo+cCAABCNhoCAAAALQbj5wIAAQTZB8PnAgAAQjYaAgAAAC0GA+sCAAEE3Qbj6wIAAEI2GgIAAAAtBiKXBgABBKEEoEIyGgIAAAAtBiKXBgABBKEEoEIyGgIAAAAtBiKXBgABBKEEoEIyGgIAAAAtByPrAgABBLUH4+sCAABCNhoCAAAALQYilwYAAIBBBKBCMhoCAAAALIAFBKBCPhoCAAAALIA5BKBCPhoCAAAALQYilwYAAIA5BKBCMhoCAAAALIApBKBCPhoCAAAALQYilwYAAIApBKBCMhoCAAAALIA9BKBCPhoCAAAALQYilwYAAIA9BKBCMhoCAAAALQZilwYAAQRpBiKXBgAAQjYaAgAAACyAOQSgQj4aAgAAAC0GYpcGAAEEaQYilwYAAEI2GgIAAAAsgD0EoEI+GgIAAAAtBmKXBgABBGkGIpcGAABCNhoCAAAALIBBBKBCPhoCAAAALQZilwYAAQRpBiKXBgAAQjYaAgAAACyABQSgQj4aAgAAAC0GIpcGAACAOQSgQjIaAgAAACyABQSgQj4aAgAAACyAQQSgQj4aAgAAAC0GIpcGAACAQQSgQjIaAgAAACyAfQSgQj4aAgAAAC0GIpcGAACAfQSgQjIaAgAAACyAhQSgQj4aAgAAAC0GIpcGAACAhQSgQjIaAgAAACyABQSgQj4aAgAAAC+8CAQV/AkAgAiADSQ0AIAMgAWoiBEF/aiEFQQAhBgJAAkACQAJAAkACQANAIAMgBmpFDQEgBSAGaiEHIAZBf2oiCCEGIActAABBOUYNAAsgAyAIaiIGIAJPDQIgBCAIaiIHIActAABBAWo6AABBACEFAkAgBkEBaiADSQ0ADAILIAhBAWohBgNAIAMgBmoiByACTw0EIAQgBmpBMDoAACAGQQFqIgcgBkkhCCAHIQYgCEUNAAsMAQtBASEFAkAgAw0AQTEhBwwBCyACRQ0DIAFBMToAAEEwIQcgA0ECSQ0AQQEhBgNAIAIgBkYNBUEwIQcgASAGakEwOgAAQQEhBSADIAZBAWoiBkcNAAsLIAAgBzoAASAAIAU6AAAPC0GkisGAACAGIAIQjIaAgAAAC0G0isGAACAHIAIQjIaAgAAAC0HEisGAAEEAQQAQjIaAgAAAC0HUisGAACACIAIQjIaAgAAACyADIAIQj4aAgAAAC6kcAwF/A34TfyOAgICAAEHQBmsiBSSAgICAAAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgASkDACIGUA0AIAEpAwgiB1ANASABKQMQIghQDQIgBiAIfCAGVA0DIAYgB30gBlYNBSABLgEYIQlBACEBIAVBqAVqQQBBoAEQ8oaAgAAaIAmtQjCGQjCHIAZCf3x5fULCmsHoBH5CgKHNoLQCfEIgiKdBEHRBEHUhCiAFQagFaiELA0AgAUEoRg0FIAsgBj4CACALQQRqIQsgAUEBaiEBIAZCIIgiBlBFDQALIAUgATYCCCAFQQhqQQRyIAVBqAVqQaABEPOGgIAAGiAFQoGAgIAQNwOwASAFQbgBakEAQZwBEPKGgIAAGiAFQbABaiAFQQhqIAlBAEgbIAkgCUEfdSIBaiABc0EQdEEQdRCOhoCAABogBUGwAWogBUEIaiAKQX9KGyAKIApBH3UiAWogAXNBEHRBEHUQlYaAgAAaIAUgBSgCsAEiATYCqAUgBUGoBWpBBHIgBUGwAWpBBHIiDEGgARDzhoCAABogAyEJAkAgA0EJTQ0AAkAgAUEoSw0AIAMhCQNAAkAgAUUNACABQQJ0IQFCACEGA0AgBUGoBWogAWoiCyAGQiCGIAs1AgCEIgZCgJTr3AOAIgc+AgAgBiAHQoCU69wDfn0hBiABQXxqIgENAAsLIAlBd2oiCUEKSQ0CIAUoAqgFIgFBKE0NAAsLIAFBKBCPhoCAAAALAkACQAJAIAlBAnRBhPbAgABqKAIAIgtFDQAgBSgCqAUiAUEpTw0JIAENAUEAIQEMAgtBz6XBgABBG0GIpcGAABCNhoCAAAALIAFBAnQhASALrSEGQgAhBwNAIAVBqAVqIAFqIgsgB0IghiALNQIAhCIHIAaAIgg+AgAgByAIIAZ+fSEHIAFBfGoiAQ0ACyAFKAKoBSEBCwJAAkACQCABIAUoAggiDSABIA1LGyIOQShLDQAgDg0BQQAhDgwCCyAOQSgQj4aAgAAACyAFQagFakEEciEBIAVBCGpBBHIhC0EAIQ9BACEQA0AgASABKAIAIhEgCygCAGoiCSAPQQFxaiIPNgIAIAkgEUkgDyAJSXIhDyABQQRqIQEgC0EEaiELIBBBAWoiECAOSQ0ACyAPRQ0AIA5BJ0sNCCAFQagFaiAOQQJ0akEEakEBNgIAIA5BAWohDgsgBSAONgKoBSAOIAUoArABIhIgDiASSxsiAUEpTw0IIAFBAnQhAQJAAkACQANAAkAgAQ0AQX9BACABGyELDAILIAFFDQIgBUGoBWogAWohCyAFQbABaiABaiEJIAFBfGohAUF/IAsoAgAiCyAJKAIAIglHIAsgCUkbIgtFDQALCyALQf8BcUECSQ0AIA1BKU8NCwJAIA0NACAFQQA2AggMAgsgBUEIaiANQQJ0IgtqQQRqIQkgBUEIakEEciEBQgAhBgNAIAEgATUCAEIKfiAGfCIGPgIAIAFBBGohASAGQiCIIQYgC0F8aiILDQALAkAgBqciAUUNACANQSdLDQ0gCSABNgIAIA1BAWohDQsgBSANNgIIDAELIApBAWohCgtBASEPAkACQAJAAkAgCkEQdEEQdSIBIARBEHRBEHUiC04NAEEAIRMMAQsCQCAKIARrQRB0QRB1IAMgASALayADSRsiEw0AQQAhEwwBCyAFIBI2AtgCIAVB2AJqQQRyIAxBoAEQ84aAgAAhFCAFQdgCakEBEI6GgIAAGiAFIAUoArABNgKABCAFQYAEakEEciAMQaABEPOGgIAAIRUgBUGABGpBAhCOhoCAABogBSAFKAKwATYCqAUgBUGoBWpBBHIgDEGgARDzhoCAACEWIAVBqAVqQQMQjoaAgAAaIAVBsAFqQQRyIRcgBUEIakEEciEYIAUoAgghD0EAIRkDQCAZIRogD0EpTw0PIBpBAWohGSAPQQJ0IQEgGCELA0AgAUUNHiABQXxqIQEgCygCACEJIAtBBGohCyAJRQ0ACyAPIAUoAqgFIgEgDyABSxsiDkEpTw0QIA5BAnQhAQJAAkACQANAAkAgAQ0AQX9BACABGyELDAILIAFFDQIgBUEIaiABaiELIAVBqAVqIAFqIQkgAUF8aiEBQX8gCygCACILIAkoAgAiCUcgCyAJSRsiC0UNAAsLQQAhGyALQf8BcUECTw0BCwJAIA5FDQBBACEQQQEhDyAYIQEgFiELA0AgASABKAIAIhEgCygCAEF/c2oiCSAPQQFxaiIPNgIAIAkgEUkgDyAJSXIhDyABQQRqIQEgC0EEaiELIBBBAWoiECAOSQ0ACyAPRQ0TCyAFIA42AghBCCEbIA4hDwsgDyAFKAKABCIBIA8gAUsbIg5BKU8NEiAOQQJ0IQECQAJAAkADQAJAIAENAEF/QQAgARshCwwCCyABRQ0CIAVBCGogAWohCyAFQYAEaiABaiEJIAFBfGohAUF/IAsoAgAiCyAJKAIAIglHIAsgCUkbIgtFDQALCyALQf8BcUEBTQ0AIA8hDgwBCwJAIA5FDQBBACEQQQEhDyAYIQEgFSELA0AgASABKAIAIhEgCygCAEF/c2oiCSAPQQFxaiIPNgIAIAkgEUkgDyAJSXIhDyABQQRqIQEgC0EEaiELIBBBAWoiECAOSQ0ACyAPRQ0VCyAFIA42AgggG0EEciEbCyAOIAUoAtgCIgEgDiABSxsiDUEpTw0UIA1BAnQhAQJAAkACQANAAkAgAQ0AQX9BACABGyELDAILIAFFDQIgBUEIaiABaiELIAVB2AJqIAFqIQkgAUF8aiEBQX8gCygCACILIAkoAgAiCUcgCyAJSRsiC0UNAAsLIAtB/wFxQQFNDQAgDiENDAELAkAgDUUNAEEAIRBBASEPIBghASAUIQsDQCABIAEoAgAiESALKAIAQX9zaiIJIA9BAXFqIg82AgAgCSARSSAPIAlJciEPIAFBBGohASALQQRqIQsgEEEBaiIQIA1JDQALIA9FDRcLIAUgDTYCCCAbQQJqIRsLIA0gBSgCsAEiEiANIBJLGyIPQSlPDRYgD0ECdCEBAkACQAJAA0ACQCABDQBBf0EAIAEbIQsMAgsgAUUNAiAFQQhqIAFqIQsgBUGwAWogAWohCSABQXxqIQFBfyALKAIAIgsgCSgCACIJRyALIAlJGyILRQ0ACwsgC0H/AXFBAU0NACANIQ8MAQsCQCAPRQ0AQQAhEUEBIRAgGCEBIBchCwNAIAEgASgCACIOIAsoAgBBf3NqIgkgEEEBcWoiEDYCACAJIA5JIBAgCUlyIRAgAUEEaiEBIAtBBGohCyARQQFqIhEgD0kNAAsgEEUNGQsgBSAPNgIIIBtBAWohGwsgGiADRg0CIAIgGmogG0EwajoAACAPQSlPDRgCQAJAIA8NAEEAIQ8MAQsgBUEIaiAPQQJ0IgtqQQRqIQlCACEGIBghAQNAIAEgATUCAEIKfiAGfCIGPgIAIAFBBGohASAGQiCIIQYgC0F8aiILDQALIAanIgFFDQAgD0EnSw0aIAkgATYCACAPQQFqIQ8LIAUgDzYCCCAZIBNHDQALQQAhDwsgEkEpTw0YAkACQCASDQBBACESDAELIAVBsAFqIBJBAnQiAWpBBGohC0IAIQYDQCAMIAw1AgBCBX4gBnwiBj4CACAMQQRqIQwgBkIgiCEGIAFBfGoiAQ0ACyAGpyIBRQ0AIBJBJ0sNGiALIAE2AgAgEkEBaiESCyAFIBI2ArABIAUoAggiASASIAEgEksbIgFBKU8NGiABQQJ0IQECQAJAA0AgAUUNASABRQ0CIAVBCGogAWohCyAFQbABaiABaiEJIAFBfGohAUF/IAsoAgAiCyAJKAIAIglHIAsgCUkbIgtFDQALIAtB/wFxQQFHDR4MAQsgAQ0dIA8NACATQX9qIgEgA08NAiACIAFqLQAAQQFxRQ0dCyAFIAIgAyATEJiGgIAAIAUtAABBAXFFDRwgCkEQdEGAgARqQRB1IgogBEEQdEEQdUwNHCATIANPDRwgAiATaiAFLQABOgAAIBNBAWohEwwcC0H4+8CAACADIAMQjIaAgAAAC0GI/MCAACABIAMQjIaAgAAAC0Gu+MCAAEEcQaj7wIAAEI2GgIAAAAtB3PjAgABBHUG4+8CAABCNhoCAAAALQYz5wIAAQRxByPvAgAAQjYaAgAAAC0G4+cCAAEE2Qdj7wIAAEI2GgIAAAAtBiKXBgABBKEEoEIyGgIAAAAtBgPrAgABBN0Ho+8CAABCNhoCAAAALIAFBKBCPhoCAAAALQYilwYAAIA5BKBCMhoCAAAALIAFBKBCPhoCAAAALIA1BKBCPhoCAAAALQYilwYAAIA1BKBCMhoCAAAALIA9BKBCPhoCAAAALIA5BKBCPhoCAAAALQZilwYAAQRpBiKXBgAAQjYaAgAAACyAOQSgQj4aAgAAAC0GYpcGAAEEaQYilwYAAEI2GgIAAAAsgDUEoEI+GgIAAAAtBmKXBgABBGkGIpcGAABCNhoCAAAALIA9BKBCPhoCAAAALQZilwYAAQRpBiKXBgAAQjYaAgAAACyAPQSgQj4aAgAAAC0GIpcGAACAPQSgQjIaAgAAACyASQSgQj4aAgAAAC0GIpcGAACASQSgQjIaAgAAACyABQSgQj4aAgAAACyATIBpJDQEgEyADSw0CIBMgGkYNACACIBpqQTAgEyAaaxDyhoCAABoLIAAgCjsBBCAAIBM2AgAgBUHQBmokgICAgAAPCyAaIBMQkIaAgAAACyATIAMQj4aAgAAAC/QSBgF/BH4CfxR+BX8BfiOAgICAAEHQAGsiBCSAgICAAAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgASkDACIFUA0AIAEpAwgiBlANASABKQMQIgdQDQIgBSAHfCIHIAVUDQMgBSAGfSIIIAVWDQQgA0ERSQ0FIAdC//////////8fVg0KIAQgAS8BGCIBOwEQIAQgCDcDCCABIAFBYGogASAHQoCAgIAQVCIJGyIKQXBqIAogB0IghiAHIAkbIgdCgICAgICAwABUIgkbIgpBeGogCiAHQhCGIAcgCRsiB0KAgICAgICAgAFUIgkbIgpBfGogCiAHQgiGIAcgCRsiB0KAgICAgICAgBBUIgkbIgpBfmogCiAHQgSGIAcgCRsiB0KAgICAgICAgMAAVCIJGyAHQgKGIAcgCRsiC0I/h6dBf3NqIglrQRB0QRB1IgpBAEgNBiAEQn8gCq1CP4MiDIgiByAIgzcDKCAIIAdWDQcgBCABOwEQIAQgBTcDCCAEIAcgBYM3AyggBSAHVg0IQaB/IAlrQRB0QRB1QdAAbEGwpwVqQc4QbSIBQdEATw0JIAFBBHQiAUGY/MCAAGopAwAiB0L/////D4MiBiAFIAyGIgVCIIgiDX4iDkIgiCIPIAdCIIgiECANfnwgECAFQv////8PgyIFfiIHQiCIIhF8IRIgDkL/////D4MgBiAFfkIgiHwgB0L/////D4N8QoCAgIAIfEIgiCETQgFBACAJIAFBoPzAgABqLwEAamtBP3GtIgeGIg5Cf3whFCAGIAggDIYiBUIgiCIVfiIIQv////8PgyAGIAVC/////w+DIgV+QiCIfCAQIAV+IgVC/////w+DfEKAgICACHxCIIghFiAQIBV+IQwgBUIgiCEXIAhCIIghGCABQaL8wIAAai8BACEBAkACQAJAAkAgECALIAtCf4VCP4iGIgVCIIgiGX4iGiAGIBl+IghCIIgiG3wgECAFQv////8PgyIFfiILQiCIIhx8IAhC/////w+DIAYgBX5CIIh8IAtC/////w+DfEKAgICACHxCIIgiHXxCAXwiHiAHiKciCkGQzgBJDQAgCkHAhD1JDQEgCkGAwtcvSQ0CQQhBCSAKQYCU69wDSSIJGyEfQYDC1y9BgJTr3AMgCRshCQwDCwJAIApB5ABJDQBBAkEDIApB6AdJIgkbIR9B5ABB6AcgCRshCQwDC0EBQQogCkEKSRshCSAKQQlLIR8MAgtBBEEFIApBoI0GSSIJGyEfQZDOAEGgjQYgCRshCQwBC0EGQQcgCkGAreIESSIJGyEfQcCEPUGAreIEIAkbIQkLIBIgE3whEiAeIBSDIQUgHyABa0EBaiEgIB4gDCAYfCAXfCAWfH1CAXwiCyAUgyEIQQAhAQJAAkADQCAKIAluISEgAyABRg0BIAIgAWoiIiAhQTBqIiM6AAACQAJAAkAgCyAKICEgCWxrIgqtIAeGIgwgBXwiBlYNACAfIAFHDQIgAUEBaiEBQgEhBgNAIAYhCyAIIQwgASADTw0GIAtCCn4hBiACIAFqIAVCCn4iBSAHiKdBMGoiCToAACABQQFqIQEgDEIKfiIIIAUgFIMiBVgNAAsgBiAeIBJ9fiIHIAZ8IQ0gCCAFfSAOVCIKDRQgByAGfSIUIAVWDQEMFAsgAUEBaiEKIAEgA08NECALIAZ9Ig4gCa0gB4YiB1QhASAeIBJ9IghCAXwhJCAIQn98IhQgBlgNESAOIAdUDREgHCAbfCAdfCIIIBp8IBJ9IAwgBXx9IR4gBSAPfCARfCATfCAQIA0gGX1+fCAbfSAcfSAdfSAMfCEOIAggECAZIBV9fnwgF30gGH0gFn0gBSAHfCAMfH1CAnwhEEIAIQUDQAJAIAYgB3wiCCAUVA0AIB4gBXwgByAOfFoNAEEAIQEMEwsgIiAjQX9qIiM6AAAgECAFfCIMIAdUIQEgCCAUWg0TIA4gB3whDiAFIAd9IQUgCCEGIAwgB1QNEwwACwsgAiABakF/aiEhIAxCCn4gDiAFfH0hECAOIBJCCn4gGyAcfCAdfCAafEIKfn0gC358IR4gFCAFfSEZQgAhBwNAAkAgBSAOfCIGIBRUDQAgGSAHfCAeIAV8Wg0AQQAhCgwUCyAhIAlBf2oiCToAACAQIAd8IgwgDlQhCiAGIBRaDRQgByAOfSEHIAYhBSAMIA5UDRQMAAsLIAFBAWohASAJQQpJISEgCUEKbiEJICFFDQALQaCIwYAAQRlBhIjBgAAQjYaAgAAAC0G8iMGAACADIAMQjIaAgAAAC0HMiMGAACABIAMQjIaAgAAAC0Gu+MCAAEEcQeSGwYAAEI2GgIAAAAtB3PjAgABBHUH0hsGAABCNhoCAAAALQYz5wIAAQRxBhIfBgAAQjYaAgAAAC0G4+cCAAEE2QZSHwYAAEI2GgIAAAAtBgPrAgABBN0Gkh8GAABCNhoCAAAALQcj6wIAAQS1BtIfBgAAQjYaAgAAAC0Gt9MCAAEEdQej0wIAAEI2GgIAAAAsgBEEkakG6gYCAADYCACAEQcQAakECNgIAIARCAzcCNCAEQbT1wIAANgIwIARBuoGAgAA2AhwgBCAEQShqNgJIIAQgBEEYajYCQCAEIARBzABqNgIgIAQgBEHIAGo2AhggBCAEQQhqNgJMIARBMGpBzPXAgAAQlIaAgAAACyAEQSRqQbqBgIAANgIAIARBxABqQQI2AgAgBEIDNwI0IARBtPXAgAA2AjAgBEG6gYCAADYCHCAEIARBKGo2AkggBCAEQRhqNgJAIAQgBEHMAGo2AiAgBCAEQcgAajYCGCAEIARBCGo2AkwgBEEwakHM9cCAABCUhoCAAAALQdSGwYAAIAFB0QAQjIaAgAAAC0HEh8GAAEEtQfSHwYAAEI2GgIAAAAsgCiADEI+GgIAAAAsgBiEICwJAICQgCFgNACABDQBBACEJIAggB3wiBSAkVA0DICQgCH0gBSAkfVoNAwtBACEJIAhCAlQNAiAIIAtCfHxWDQIgACAKNgIEIABBCGogIDsBAEEBIQkMAgsgBSEGCwJAIA0gBlgNACAKDQBBACEJIAYgDnwiBSANVA0BIA0gBn0gBSANfVoNAQtBACEJIAtCFH4gBlYNACAGIAtCWH4gCHxWDQAgACABNgIEIABBCGogIDsBAEEBIQkLIAAgCTYCACAEQdAAaiSAgICAAAuODQoBfwF+An8EfgN/AX4CfwF+An8BfiOAgICAAEEQayIFJICAgIAAAkACQAJAAkACQAJAAkACQAJAIAEpAwAiBlANACAGQv//////////H1YNASADRQ0DQaB/IAEvARgiAUFgaiABIAZCgICAgBBUIgcbIgFBcGogASAGQiCGIAYgBxsiBkKAgICAgIDAAFQiBxsiAUF4aiABIAZCEIYgBiAHGyIGQoCAgICAgICAAVQiBxsiAUF8aiABIAZCCIYgBiAHGyIGQoCAgICAgICAEFQiBxsiAUF+aiABIAZCBIYgBiAHGyIGQoCAgICAgICAwABUIgcbIAZCAoYgBiAHGyIGQj+Hp0F/c2oiB2tBEHRBEHVB0ABsQbCnBWpBzhBtIgFB0QBPDQIgAUEEdCIBQaL8wIAAai8BACEIAkACQAJAAkAgAUGY/MCAAGopAwAiCUL/////D4MiCiAGIAZCf4VCP4iGIgZCIIgiC34iDEIgiCAJQiCIIgkgC358IAkgBkL/////D4MiBn4iCUIgiHwgDEL/////D4MgCiAGfkIgiHwgCUL/////D4N8QoCAgIAIfEIgiHwiBkFAIAcgAUGg/MCAAGovAQBqa0EQdEEQdSINQT9xrSIMiKciDkGQzgBJDQAgDkHAhD1JDQEgDkGAwtcvSQ0CQQhBCSAOQYCU69wDSSIBGyEPQYDC1y9BgJTr3AMgARshBwwDCwJAIA5B5ABJDQBBAkEDIA5B6AdJIgEbIQ9B5ABB6AcgARshBwwDC0EBQQogDkEKSRshByAOQQlLIQ8MAgtBBEEFIA5BoI0GSSIBGyEPQZDOAEGgjQYgARshBwwBC0EGQQcgDkGAreIESSIBGyEPQcCEPUGAreIEIAEbIQcLQgEgDIYhEAJAAkACQAJAAkAgDyAIayIRQRB0QYCABGpBEHUiEiAEQRB0QRB1IgFMDQAgBiAQQn98IhODIQYgEiAEa0EQdEEQdSADIBIgAWsgA0kbIhRBf2ohFUEAIQEDQCAOIAduIQggAyABRg0EIA4gCCAHbGshDiACIAFqIAhBMGo6AAAgFSABRg0CIA8gAUYNAyABQQFqIQEgB0EKSSEIIAdBCm4hByAIRQ0AC0GgiMGAAEEZQdSJwYAAEI2GgIAAAAsgBkIKgCEGQQAhCCAHrSAMhiIJIBBYDQwgCSAQfSAQWA0MAkAgCSAGfSAGWA0AIAkgBkIBhn0gEEIBhloNCQsgBiAQWA0MIAkgBiAQfSIGfSAGVg0MQQAhAQJAIBFBEHRBgIAIakEQdSIHIARBEHRBEHVMDQAgAkExOgAAQQEhAQsgACABNgIEIABBCGogBzsBAAwLC0EAIQggB60gDIYiCSAQWA0LIAkgEH0gEFgNCwJAIAkgDq0gDIYgBnwiBn0gBlgNACAJIAZCAYZ9IBBCAYZaDQkLIAYgEFgNCyAJIAYgEH0iBn0gBlYNCyAFIAIgAyAUEJiGgIAAQQEhCAJAIAUtAABBAXFFDQAgEUEQdEGAgAhqQRB1IhIgBEEQdEEQdUwNACAUIANPDQAgAiAUaiAFLQABOgAAIBRBAWohFAsgACAUNgIEIABBCGogEjsBAAwLCyABQQFqIQEgDUF/akE/ca0hFkIBIQkDQEEAIQggCSIKIBaIQgBSDQsgASADTw0CIApCCn4hCSAGQgp+IgsgE4MhBiACIAFqIAsgDIinQTBqOgAAIBQgAUEBaiIBRw0ACyAQIAlYDQogECAJfSAJWA0KAkAgECAGfSAGWA0AIBAgBkIBhn0gCkIUfloNCQsgBiAJWA0KIBAgBiAJfSIGfSAGVg0KIAVBCGogAiADIBQQmIaAgABBASEIAkAgBS0ACEEBcUUNACARQRB0QYCACGpBEHUiEiAEQRB0QRB1TA0AIBQgA08NACACIBRqIAUtAAk6AAAgFEEBaiEUCyAAIBQ2AgQgAEEIaiASOwEADAoLQeSJwYAAIAMgAxCMhoCAAAALQfSJwYAAIAEgAxCMhoCAAAALQa74wIAAQRxBgInBgAAQjYaAgAAAC0GQicGAAEEkQbSJwYAAEI2GgIAAAAtB1IbBgAAgAUHRABCMhoCAAAALQdyIwYAAQSFBxInBgAAQjYaAgAAACyAAQQA2AgQgAEEIaiASOwEADAILIAAgFDYCBCAAQQhqIBI7AQAMAQsgACAUNgIEIABBCGogEjsBAAtBASEICyAAIAg2AgAgBUEQaiSAgICAAAsRACAANQIAQQEgARDDhoCAAAv1BQQDfwJ+AX8CfiOAgICAAEGQAWsiBCSAgICAACAEIAM2AmwCQAJAAkACQAJAAkACQAJAAkAgA0F+akEiSw0AAkAgAg0AIABBADoAAQwICwJAIAEtAABBK0cNAEEBIQUgAkF/aiICRQ0CIAFBAWohAQsgA0ELSQ0CIARB4ABqIQZCACEHQgAhCANAIAJFDQcCQCABLQAAIglBUGoiBUEKSQ0AAkAgCUGff2pBGkkNACAJQb9/akEaTw0GIAlBSWohBQwBCyAJQal/aiEFCyAFIANPDQQgBEHIAGogCEIAIAOtIgpCABD3hoCAACAEQdgAaiAHQgAgCkIAEPeGgIAAIARBOGpCAEIAIAdCABD3hoCAACAEKQNQIAQpA0CEQgBSIAYpAwAiByAEKQNIIAQpAzh8fCIKIAdUckEBRg0FIAFBAWohASACQX9qIQIgBCkDWCILIAWtfCIHIAtUIgUgCiAFrXwiCCAKVCAHIAtaG0EBRw0ADAYLCyAEQYQBakEBNgIAIARCATcCdCAEQaCMwYAANgJwIARBhICAgAA2AowBIAQgBEGIAWo2AoABIAQgBEHsAGo2AogBIARB8ABqQcCMwYAAEJSGgIAAAAsgAEEAOgABDAYLIARBMGohCUIAIQdCACEIA0AgAkUNBCABLQAAQVBqIgVBCUsNASAFIANPDQEgBEEYaiAIQgAgA60iCkIAEPeGgIAAIARBKGogB0IAIApCABD3hoCAACAEQQhqQgBCACAHQgAQ94aAgAAgBCkDICAEKQMQhEIAUiAJKQMAIgcgBCkDGCAEKQMIfHwiCiAHVHINAiABQQFqIQEgAkF/aiECIAQpAygiCyAFrXwiByALVCIFIAogBa18IgggClQgByALWhtFDQAMAwsLQQEhBSAAQQE6AAEMBAsgAEECOgABDAILIABBAjoAAQwBCyAAQRBqIAg3AwAgAEEIaiAHNwMAQQAhBQwBC0EBIQULIAAgBToAACAEQZABaiSAgICAAAtvAQF/QdCMwYAAIQICQAJAAkACQAJAIAAtAAAOBQABAgMEAAsgAUHfjcGAAEEmEJGGgIAADwsgAUHCjcGAAEEdEJGGgIAADwsgAUGcjcGAAEEmEJGGgIAADwtB9ozBgAAhAgsgASACQSYQkYaAgAAL/gYBCn8jgICAgABBMGsiAySAgICAACADQSRqIAE2AgAgA0EDOgAoIANCgICAgIAENwMIIAMgADYCIEEAIQQgA0EANgIYIANBADYCEAJAAkACQAJAAkACQCACKAIIIgUNACACKAIAIQYgAigCBCIHIAJBFGooAgAiBSAFIAdLGyIIRQ0BIAIoAhAhBUEBIQkgACAGKAIAIAYoAgQgASgCDBGPgICAAAANBSAGQQxqIQJBASEEA0ACQCAFKAIAIANBCGogBUEEaigCABGAgICAAABFDQBBASEJDAcLIAQgCE8NAiACQXxqIQAgAigCACEBIAJBCGohAiAFQQhqIQVBASEJIARBAWohBCADKAIgIAAoAgAgASADKAIkKAIMEY+AgIAAAEUNAAwGCwsgAigCACEGIAIoAgQiByACQQxqKAIAIgkgCSAHSxsiCkUNACACQRRqKAIAIQggAigCECELQQEhCSAAIAYoAgAgBigCBCABKAIMEY+AgIAAAA0EIAZBDGohAkEBIQQDQCADIAVBBGooAgA2AgwgAyAFQRxqLQAAOgAoIAMgBUEIaigCADYCCCAFQRhqKAIAIQlBACEBQQAhAAJAAkACQCAFQRRqKAIADgMBAAIBCyAJIAhPDQUgCUEDdCEMQQAhACALIAxqIgwoAgRBu4GAgABHDQEgDCgCACgCACEJC0EBIQALIAMgCTYCFCADIAA2AhAgBUEQaigCACEJAkACQAJAIAVBDGooAgAOAwEAAgELIAkgCE8NBiAJQQN0IQAgCyAAaiIAKAIEQbuBgIAARw0BIAAoAgAoAgAhCQtBASEBCyADIAk2AhwgAyABNgIYIAUoAgAiCSAITw0CAkAgCyAJQQN0aiIJKAIAIANBCGogCSgCBBGAgICAAABFDQBBASEJDAYLIAQgCk8NASACQXxqIQAgAigCACEBIAJBCGohAiAFQSBqIQVBASEJIARBAWohBCADKAIgIAAoAgAgASADKAIkKAIMEY+AgIAAAEUNAAwFCwsCQCAHIARNDQBBASEJIAMoAiAgBiAEQQN0aiIFKAIAIAUoAgQgAygCJCgCDBGPgICAAAANBAtBACEJDAMLQbySwYAAIAkgCBCMhoCAAAALQcySwYAAIAkgCBCMhoCAAAALQcySwYAAIAkgCBCMhoCAAAALIANBMGokgICAgAAgCQuXAQEDfyOAgICAAEEgayICJICAgIAAAkAgACABEKGGgIAADQAgAUEcaigCACEDIAEoAhghBCACQRxqQQA2AgAgAkGs9MCAADYCGCACQgE3AgwgAkGIjsGAADYCCCAEIAMgAkEIahCfhoCAAA0AIABBBGogARChhoCAACEBIAJBIGokgICAgAAgAQ8LIAJBIGokgICAgABBAQvRAgEDfyOAgICAAEGAAWsiAiSAgICAAAJAAkACQAJAAkAgASgCACIDQRBxDQAgACgCACEEIANBIHENASAErUEBIAEQw4aAgAAhAAwCCyAAKAIAIQRBACEAA0AgAiAAakH/AGogBEEPcSIDQTByIANB1wBqIANBCkkbOgAAIABBf2ohACAEQQR2IgQNAAsgAEGAAWoiBEGBAU8NAiABQQFBwZDBgABBAiACIABqQYABakEAIABrEMWGgIAAIQAMAQtBACEAA0AgAiAAakH/AGogBEEPcSIDQTByIANBN2ogA0EKSRs6AAAgAEF/aiEAIARBBHYiBA0ACyAAQYABaiIEQYEBTw0CIAFBAUHBkMGAAEECIAIgAGpBgAFqQQAgAGsQxYaAgAAhAAsgAkGAAWokgICAgAAgAA8LIARBgAEQkIaAgAAACyAEQYABEJCGgIAAAAsMAEKY3Iamxs6tyQgLIQAgASgCGEGQjsGAAEELIAFBHGooAgAoAgwRj4CAgAAACyEAIAEoAhhBm47BgABBDiABQRxqKAIAKAIMEY+AgIAAAAv6AgIDfwF+QQIhAwJAAkACQAJAAkAgAUF3aiIEQR5NDQAgAUHcAEcNAQwCC0H0ACEFAkACQCAEDh8FAQICAAICAgICAgICAgICAgICAgICAgICAwICAgIDBQtB8gAhBQwEC0HuACEFDAMLAkAgAkUNACABQQp2IQUCQAJAAkACQCABQYDYB0kNAEEeIQMgBUGAB0cNBAwBCyAFQcC+wYAAai0AACIDQR5LDQELIANBBHQgAUEGdkEPcXJBu7/BgABqLQAAIgVBiwFPDQFBAyEDIAVBA3RBsMPBgABqKQMAQgEgAUE/ca2Gg1ANAiABQQFyZ0ECdkEHc61CgICAgNAAhCEGDAQLQcykwYAAIANBHxCMhoCAAAALQdykwYAAIAVBiwEQjIaAgAAACwJAIAEQpoaAgABFDQBBASEDDAILIAFBAXJnQQJ2QQdzrUKAgICA0ACEIQZBAyEDDAELCyABIQULIAAgBTYCBCAAIAM2AgAgAEEIaiAGNwIAC/AGAQd/AkACQAJAAkACQAJAAkACQAJAAkAgAEGAgARJDQAgAEGAgAhJDQFBACEBIABB4ot0akHijSxJDQggAEGfqHRqQZ8YSQ0IIABB3uJ0akEOSQ0IIABB/v//AHFBnvAKRg0IIABBqbJ1akEpSQ0IIABBy5F1akELSQ0IIABBkPxHakGP/AtLDwsgAEGA/gNxQQh2IQJB/JnBgAAhA0EAIQQgAEH/AXEhBQNAIANBAmohBiAEIAMtAAEiAWohBwJAIAMtAAAiAyACRg0AIAMgAksNCCAHIQQgBiEDIAZBzprBgABHDQEMCAsgByAESQ0CIAdBpQJLDQMgBEHOmsGAAGohAwJAA0AgAUUNASABQX9qIQEgAy0AACEEIANBAWohAyAEIAVHDQALQQAhAQwJCyAHIQQgBiEDIAZBzprBgABHDQAMBwsLIABBgP4DcUEIdiECQa2fwYAAIQNBACEEIABB/wFxIQUDQCADQQJqIQYgBCADLQABIgFqIQcCQCADLQAAIgMgAkYNACADIAJLDQYgByEEIAYhAyAGQfOfwYAARw0BDAYLIAcgBEkNAyAHQaYBSw0EIARB85/BgABqIQMCQANAIAFFDQEgAUF/aiEBIAMtAAAhBCADQQFqIQMgBCAFRw0AC0EAIQEMCAsgByEEIAYhAyAGQfOfwYAARw0ADAULCyAEIAcQkIaAgAAACyAHQaUCEI+GgIAAAAsgBCAHEJCGgIAAAAsgB0GmARCPhoCAAAALIABB//8DcSEFQZmhwYAAIQNBASEBAkADQCADQQFqIQACQAJAIAMtAAAiBEEYdEEYdSIHQQBIDQAgACEDDAELIABBsaTBgABGDQIgB0H/AHFBCHQgAy0AAXIhBCADQQJqIQMLIAUgBGsiBUEASA0DIAFBAXMhASADQbGkwYAARw0ADAMLC0GpjsGAAEErQeyZwYAAEI2GgIAAAAsgAEH//wNxIQVB85zBgAAhA0EBIQEDQCADQQFqIQACQAJAIAMtAAAiBEEYdEEYdSIHQQBIDQAgACEDDAELIABBrZ/BgABGDQMgB0H/AHFBCHQgAy0AAXIhBCADQQJqIQMLIAUgBGsiBUEASA0BIAFBAXMhASADQa2fwYAARw0ACwsgAUEBcQ8LQamOwYAAQStB7JnBgAAQjYaAgAAAC/IDAgR/An4jgICAgABBwABrIgUkgICAgABBASEGAkAgAC0ABA0AIAAtAAUhBwJAIAAoAgAiCC0AAEEEcQ0AQQEhBiAIKAIYQaGQwYAAQaOQwYAAIAdB/wFxIgcbQQJBAyAHGyAIQRxqKAIAKAIMEY+AgIAAAA0BQQEhBiAAKAIAIggoAhggASACIAhBHGooAgAoAgwRj4CAgAAADQFBASEGIAAoAgAiCCgCGEHcjsGAAEECIAhBHGooAgAoAgwRj4CAgAAADQEgAyAAKAIAIAQoAgwRgICAgAAAIQYMAQsCQCAHQf8BcQ0AQQEhBiAIKAIYQZyQwYAAQQMgCEEcaigCACgCDBGPgICAAAANASAAKAIAIQgLQQEhBiAFQQE6ABcgBUE0akGAkMGAADYCACAFIAgpAhg3AwggBSAFQRdqNgIQIAgpAgghCSAIKQIQIQogBSAILQAgOgA4IAUgCjcDKCAFIAk3AyAgBSAIKQIANwMYIAUgBUEIajYCMCAFQQhqIAEgAhCyhoCAAA0AIAVBCGpB3I7BgABBAhCyhoCAAA0AIAMgBUEYaiAEKAIMEYCAgIAAAA0AIAUoAjBBn5DBgABBAiAFKAI0KAIMEY+AgIAAACEGCyAAQQE6AAUgACAGOgAEIAVBwABqJICAgIAAIAALbAEBfyOAgICAAEEwayIDJICAgIAAIAMgATYCDCADIAA2AgggA0EkakEBNgIAIANCATcCFCADQdSOwYAANgIQIANBuIGAgAA2AiwgAyADQShqNgIgIAMgA0EIajYCKCADQRBqIAIQlIaAgAAACxQAIAEgACgCACAAKAIEEJGGgIAAC5MBAQF/I4CAgIAAQcAAayIFJICAgIAAIAUgATYCDCAFIAA2AgggBSADNgIUIAUgAjYCECAFQSxqQQI2AgAgBUE8akG8gYCAADYCACAFQgI3AhwgBUHgjsGAADYCGCAFQbiBgIAANgI0IAUgBUEwajYCKCAFIAVBEGo2AjggBSAFQQhqNgIwIAVBGGogBBCUhoCAAAALGAAgACgCACABIAAoAgQoAgwRgICAgAAACwcAIAAoAggLBwAgACgCDAvhAwEEfyOAgICAAEHAAGsiAiSAgICAAEEBIQMCQCABKAIYQYCPwYAAQQwgAUEcaigCACgCDBGPgICAAAANAAJAAkAgACgCCCIDDQAgACgCACIDIAAoAgQoAgwRioCAgAAAQuSuwoWXm6WIEVINASACIAM2AgwgAkG9gYCAADYCFCACIAJBDGo2AhAgASgCGCEEIAEoAhwhBUEBIQMgAkE8akEBNgIAIAJCAjcCLCACQZCPwYAANgIoIAIgAkEQajYCOCAEIAUgAkEoahCfhoCAAA0CDAELIAIgAzYCDCACQb6BgIAANgIUIAIgAkEMajYCECABKAIYIQQgASgCHCEFQQEhAyACQTxqQQE2AgAgAkICNwIsIAJBkI/BgAA2AiggAiACQRBqNgI4IAQgBSACQShqEJ+GgIAADQELIAAoAgwhAyACQRBqQRRqQYSAgIAANgIAIAJBEGpBDGpBhICAgAA2AgAgAiADQQxqNgIgIAIgA0EIajYCGCACQbiBgIAANgIUIAIgAzYCECABKAIYIQMgASgCHCEBIAJBKGpBFGpBAzYCACACQgM3AiwgAkGkj8GAADYCKCACIAJBEGo2AjggAyABIAJBKGoQn4aAgAAhAwsgAkHAAGokgICAgAAgAwsZACABIAAoAgAiACgCACAAKAIEEJGGgIAAC3kBA38jgICAgABBIGsiAiSAgICAACABQRxqKAIAIQMgASgCGCEEIAJBCGpBEGogACgCACIBQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggBCADIAJBCGoQn4aAgAAhASACQSBqJICAgIAAIAELBAAgAAvOBAEHfyOAgICAAEEwayIDJICAgIAAAkACQCACDQBBACEEDAELIANBKGohBQJAAkACQAJAA0ACQCAAKAIILQAARQ0AIAAoAgBBmJDBgABBBCAAKAIEKAIMEY+AgIAAAA0FCyADQQo2AiggA0KKgICAEDcDICADIAI2AhwgA0EANgIYIAMgAjYCFCADIAE2AhAgA0EIakEKIAEgAhCzhoCAAAJAAkACQAJAIAMoAghBAUcNACADKAIMIQQDQCADIAQgAygCGGpBAWoiBDYCGAJAAkAgBCADKAIkIgZPDQAgAygCFCEHDAELIAMoAhQiByAESQ0AIAZBBU8NByADKAIQIAQgBmsiCGoiCSAFRg0EIAkgBSAGEPSGgIAARQ0ECyADKAIcIgkgBEkNAiAHIAlJDQIgAyAGIANBEGpqQRdqLQAAIAMoAhAgBGogCSAEaxCzhoCAACADKAIEIQQgAygCAEEBRg0ACwsgAyADKAIcNgIYCyAAKAIIQQA6AAAgAiEEDAELIAAoAghBAToAACAIQQFqIQQLIAAoAgQhCSAAKAIAIQYCQCAERSACIARGciIHDQAgAiAETQ0DIAEgBGosAABBv39MDQMLIAYgASAEIAkoAgwRj4CAgAAADQQCQCAHDQAgAiAETQ0EIAEgBGosAABBv39MDQQLIAEgBGohASACIARrIgINAAtBACEEDAQLIAZBBBCPhoCAAAALIAEgAkEAIAQQkoaAgAAACyABIAIgBCACEJKGgIAAAAtBASEECyADQTBqJICAgIAAIAQL9wIBBn9BACEEAkACQCACQQNxIgVFDQBBBCAFayIFRQ0AIAMgBSAFIANLGyEEQQAhBSABQf8BcSEGA0AgBCAFRg0BIAIgBWohByAFQQFqIQUgBy0AACIHIAZHDQALQQEhAyAHIAFB/wFxRkEBakEBcSAFakF/aiEFDAELIAFB/wFxIQYCQAJAIANBCEkNACAEIANBeGoiCEsNACAGQYGChAhsIQUCQANAIAIgBGoiB0EEaigCACAFcyIJQX9zIAlB//37d2pxIAcoAgAgBXMiB0F/cyAHQf/9+3dqcXJBgIGChHhxDQEgBEEIaiIEIAhNDQALCyAEIANLDQELIAIgBGohCSADIARrIQJBACEDQQAhBQJAA0AgAiAFRg0BIAkgBWohByAFQQFqIQUgBy0AACIHIAZHDQALQQEhAyAHIAFB/wFxRkEBakEBcSAFakF/aiEFCyAFIARqIQUMAQsgBCADEJCGgIAAAAsgACAFNgIEIAAgAzYCAAuLAQEDfyAALQAEIQECQCAALQAFRQ0AIAFB/wFxIQJBASEBAkAgAg0AIAAoAgAiAUEcaigCACgCDCECIAEoAhghAwJAIAEtAABBBHENACADQaeQwYAAQQIgAhGPgICAAAAhAQwBCyADQaaQwYAAQQEgAhGPgICAAAAhAQsgACABOgAECyABQf8BcUEARwv4AgIEfwJ+I4CAgIAAQcAAayIDJICAgIAAQQEhBAJAIAAtAAgNACAAKAIEIQUCQCAAKAIAIgYtAABBBHENAEEBIQQgBigCGEGhkMGAAEGrkMGAACAFG0ECQQEgBRsgBkEcaigCACgCDBGPgICAAAANASABIAAoAgAgAigCDBGAgICAAAAhBAwBCwJAIAUNAEEBIQQgBigCGEGpkMGAAEECIAZBHGooAgAoAgwRj4CAgAAADQEgACgCACEGC0EBIQQgA0EBOgAXIANBNGpBgJDBgAA2AgAgAyAGKQIYNwMIIAMgA0EXajYCECAGKQIIIQcgBikCECEIIAMgBi0AIDoAOCADIAg3AyggAyAHNwMgIAMgBikCADcDGCADIANBCGo2AjAgASADQRhqIAIoAgwRgICAgAAADQAgAygCMEGfkMGAAEECIAMoAjQoAgwRj4CAgAAAIQQLIAAgBDoACCAAIAAoAgRBAWo2AgQgA0HAAGokgICAgAAgAAunAQEDfyAALQAIIQECQCAAKAIEIgJFDQAgAUH/AXEhA0EBIQECQCADDQACQCACQQFHDQAgAC0ACUUNACAAKAIAIgMtAABBBHENAEEBIQEgAygCGEGskMGAAEEBIANBHGooAgAoAgwRj4CAgAAADQELIAAoAgAiASgCGEGtkMGAAEEBIAFBHGooAgAoAgwRj4CAgAAAIQELIAAgAToACAsgAUH/AXFBAEcL9gICA38CfiOAgICAAEHAAGsiAySAgICAAEEBIQQCQCAALQAEDQAgAC0ABSEEAkAgACgCACIFLQAAQQRxDQACQCAEQf8BcUUNAEEBIQQgBSgCGEGhkMGAAEECIAVBHGooAgAoAgwRj4CAgAAADQIgACgCACEFCyABIAUgAigCDBGAgICAAAAhBAwBCwJAIARB/wFxDQBBASEEIAUoAhhBrpDBgABBASAFQRxqKAIAKAIMEY+AgIAAAA0BIAAoAgAhBQtBASEEIANBAToAFyADQTRqQYCQwYAANgIAIAMgBSkCGDcDCCADIANBF2o2AhAgBSkCCCEGIAUpAhAhByADIAUtACA6ADggAyAHNwMoIAMgBjcDICADIAUpAgA3AxggAyADQQhqNgIwIAEgA0EYaiACKAIMEYCAgIAAAA0AIAMoAjBBn5DBgABBAiADKAI0KAIMEY+AgIAAACEECyAAQQE6AAUgACAEOgAEIANBwABqJICAgIAACxAAIAAgASACELeGgIAAIAALOgEBf0EBIQECQCAALQAEDQAgACgCACIAKAIYQcCQwYAAQQEgAEEcaigCACgCDBGPgICAAAAhAQsgAQuaDAUBfwJ+AX8CfgR/I4CAgIAAQfAIayIEJICAgIAAIAG9IgVC/////////weDIgZCgICAgICAgAiEIAZCAYYgBUI0iKdB/w9xIgcbIghCAYMhCSAGUCEKQQIhCwJAAkACQAJAAkAgBUKAgICAgICA+P8AgyIGUEUNAEECQQMgChsOBAQBAgMECwJAIAZCgICAgICAgPj/AFINACAKDgQEAQIDBAtCgICAgICAgCAgCEIBhiAIQoCAgICAgIAIUSIMGyEIQgJCASAMGyEGIAmnQQFzIQtBy3dBzHcgDBsgB2ohDAwDC0EDIQsMAgtBBCELDAELIAdBzXdqIQwgCadBAXMhC0IBIQYLIARBkAhqQQRqIARBEGpBBGotAAAiCjoAACAEIAQoABAiBzYCkAggBEHvCGogCjoAACAEIAw7AegIIAQgBjcD4AggBEIBNwPYCCAEIAg3A9AIIAQgBzYA6wggBCALOgDqCAJAAkACQAJAAkAgC0F+aiILQQMgC0H/AXEiDUEDSRtB/wFxIgtBAksNAAJAAkAgCw4DAQIAAQtBrPTAgAAhCkEAIQcCQCACQf8BcQ4EBQAEAwULQaeLwYAAQaz0wIAAIAVCAFMbIQogBUI/iKchBwwECyAEQQM2ApgIIARBrIvBgAA2ApQIIARBAjsBkAhBASELQQAhB0Gs9MCAACEKDAQLQaeLwYAAQaz0wIAAIAVCAFMiBxtBp4vBgABBqIvBgAAgBxsgAkH/AXEiAkECSRshCkEBIQsgByACQQFLciEHAkAgDUECTQ0AAkACQAJAAkBBdEEFIAxBEHRBEHUiC0EASBsgC2xBBHZBFWoiC0GACEsNACAEQZAIaiAEQdAIaiAEQRBqIAtBACADa0GAgH4gA0GAgAJJGyIMEJuGgIAAIAxBEHRBEHUhAgJAAkAgBCgCkAhBAUYNACAEQQhqIARB0AhqIARBEGogCyACEJmGgIAAIAQvAQwhCyAEKAIIIQwMAQsgBEGYCGovAQAhCyAEKAKUCCEMCwJAIAtBEHRBEHUgAkwNACAMQYEITw0CIAxFDQMgBC0AEEExSQ0EAkACQCALQRB0QRB1IgJBAUgNAEECIQsgBEECOwGQCCAEIARBEGo2ApQIIAwgAk0NASAEQQI7AagIIARBATYCpAggBEGmi8GAADYCoAggBEECOwGcCCAEIAI2ApgIIAQgBEEQaiACajYCrAggBCAMIAJrIg02ArAIQQMhCyANIANPDQsgBEEAOwG0CCAEIAMgDGsgAmo2ArgIQQQhCwwLCyAEQQI7AagIIARBAjYCmAggBEGki8GAADYClAggBEECOwGQCCAEQQA7AZwIIARBACACayINNgKgCCAEIAw2ArAIIAQgBEEQajYCrAhBAyELIAwgA08NCiADIAxrIgwgDU0NCiAEQQA7AbQIIAQgDCACajYCuAhBBCELDAoLIARBADsBnAggBCAMNgKYCCAEIAIgDGs2AqAIIANFDQkgBCADNgK4CCAEQQA7AbQIIARBATYCsAggBEGmi8GAADYCrAggBEECOwGoCEEEIQsMCQtBAiELIARBAjsBkAgCQCADRQ0AIAQgAzYCoAggBEEAOwGcCCAEQQI2ApgIIARBpIvBgAA2ApQIDAkLQQEhCyAEQQE2ApgIIARBrPTAgAA2ApQIDAgLQa+LwYAAQSVB1IvBgAAQjYaAgAAACyAMQYAIEI+GgIAAAAtB3IjBgABBIUHkisGAABCNhoCAAAALQfSKwYAAQR9BlIvBgAAQjYaAgAAACyAEQQM2ApgIIARBqYvBgAA2ApQIIARBAjsBkAgMAwtBp4vBgABBqIvBgAAgBUIAUxshCkEBIQcMAQtBqIvBgAAhCkEBIQcLQQIhCyAEQQI7AZAIAkAgA0UNACAEIAM2AqAIIARBADsBnAggBEECNgKYCCAEQaSLwYAANgKUCAwBC0EBIQsgBEEBNgKYCCAEQaz0wIAANgKUCAsgBEHMCGogCzYCACAEIAc2AsQIIAQgCjYCwAggBCAEQZAIajYCyAggACAEQcAIahC7hoCAACELIARB8AhqJICAgIAAIAsLkQUBCX8jgICAgABBEGsiAiSAgICAAAJAAkAgACgCCEEBRg0AIAAgARDHhoCAACEDDAELIABBDGooAgAhBCACQQxqIAFBDGooAgAiBTYCACACIAFBCGooAgAiAzYCCCACIAFBBGooAgAiBjYCBCACIAEoAgAiATYCACAALQAgIQcgACgCBCEIAkACQAJAIAAtAABBCHENACAGIQkgByEKDAELIAAoAhggASAGIABBHGooAgAoAgwRj4CAgAAADQFBASEKIABBAToAICAAQTA2AgRBACEJIAJBADYCBCACQaz0wIAANgIAQQAgBCAGayIBIAEgBEsbIQQLAkAgBUUNACADIAVBDGxqIQYDQCADIgFBDGohAwJAAkACQAJAIAEvAQAOAwABAgALIAFBBGooAgAhAQwCCwJAIAFBAmovAQAiBUHoB0kNAEEEQQUgBUGQzgBJGyEBDAILQQEhASAFQQpJDQFBAkEDIAVB5ABJGyEBDAELIAFBCGooAgAhAQsgASAJaiEJIAYgA0cNAAsLAkACQAJAIAQgCU0NAEEAIQMgBCAJayIBIQkCQAJAAkAgCkEDcQ4EAgEAAQILIAFBAXYhAyABQQFqQQF2IQkMAQtBACEJIAEhAwsgA0EBaiEDA0AgA0F/aiIDRQ0CIAAoAhggACgCBCAAKAIcKAIQEYCAgIAAAA0EDAALCyAAIAIQx4aAgAAhAwwBCyAAKAIEIQEgACACEMeGgIAADQEgCUEBaiEDIAAoAhwhCSAAKAIYIQYDQAJAIANBf2oiAw0AQQAhAwwCCyAGIAEgCSgCEBGAgICAAABFDQALQQEhAwsgACAHOgAgIAAgCDYCBAwBC0EBIQMLIAJBEGokgICAgAAgAwuYCgUBfwJ+AX8CfgR/I4CAgIAAQYABayIEJICAgIAAIAG9IgVC/////////weDIgZCgICAgICAgAiEIAZCAYYgBUI0iKdB/w9xIgcbIghCAYMhCSAGUCEKQQIhCwJAAkACQAJAAkAgBUKAgICAgICA+P8AgyIGUEUNAEECQQMgChsOBAQBAgMECwJAIAZCgICAgICAgPj/AFINACAKDgQEAQIDBAtCgICAgICAgCAgCEIBhiAIQoCAgICAgIAIUSIMGyEIQgJCASAMGyEGIAmnQQFzIQtBy3dBzHcgDBsgB2ohDAwDC0EDIQsMAgtBBCELDAELIAdBzXdqIQwgCadBAXMhC0IBIQYLIARBCGpBBGogBEEgakEEai0AACIKOgAAIAQgBCgAICIHNgIIIARB/wBqIAo6AAAgBCAMOwF4IAQgBjcDcCAEQgE3A2ggBCAINwNgIAQgBzYAeyAEIAs6AHoCQAJAAkACQAJAIAtBfmoiC0EDIAtB/wFxIgdBA0kbQf8BcSILQQJLDQACQAJAIAsOAwECAAELQaz0wIAAIQxBACEKAkAgAkH/AXEOBAUABAMFC0Gni8GAAEGs9MCAACAFQgBTGyEMIAVCP4inIQoMBAsgBEEDNgIoIARBrIvBgAA2AiQgBEECOwEgQQEhC0EAIQpBrPTAgAAhDAwEC0Gni8GAAEGs9MCAACAFQgBTIgobQaeLwYAAQaiLwYAAIAobIAJB/wFxIgJBAkkbIQxBASELIAogAkEBS3IhCgJAIAdBAk0NACAEQSBqIARB4ABqIARBCGpBERCahoCAAAJAAkAgBCgCIEEBRg0AIAQgBEHgAGogBEEIakEREJeGgIAAIAQvAQQhCyAEKAIAIQcMAQsgBEEoai8BACELIAQoAiQhBwsCQAJAAkAgB0ESTw0AIAdFDQEgBC0ACEExSQ0CAkACQAJAIAtBEHRBEHUiAkEBSA0AQQIhCyAEQQI7ASAgBCAEQQhqNgIkIAcgAk0NASAEQQI7ATggBEEBNgI0IARBpovBgAA2AjAgBEECOwEsIAQgAjYCKCAEIARBCGogAmo2AjwgBCAHIAJrIg02AkBBAyELIA0gA08NCiAEQQA7AUQgBCADIAdrIAJqNgJIDAILIARBAjsBOCAEQQI2AiggBEGki8GAADYCJCAEQQI7ASAgBEEAOwEsIARBACACayINNgIwIAQgBzYCQCAEIARBCGo2AjxBAyELIAcgA08NCSADIAdrIgcgDU0NCSAEQQA7AUQgBCAHIAJqNgJIDAELIARBADsBLCAEIAc2AiggBCACIAdrNgIwIANFDQggBCADNgJIIARBADsBRCAEQQE2AkAgBEGmi8GAADYCPCAEQQI7ATgLQQQhCwwHCyAHQREQj4aAgAAAC0HciMGAAEEhQeSKwYAAEI2GgIAAAAtB9IrBgABBH0GUi8GAABCNhoCAAAALIARBAzYCKCAEQamLwYAANgIkIARBAjsBIAwDC0Gni8GAAEGoi8GAACAFQgBTGyEMQQEhCgwBC0Goi8GAACEMQQEhCgtBAiELIARBAjsBIAJAIANFDQAgBCADNgIwIARBADsBLCAEQQI2AiggBEGki8GAADYCJAwBC0EBIQsgBEEBNgIoIARBrPTAgAA2AiQLIARB3ABqIAs2AgAgBCAKNgJUIAQgDDYCUCAEIARBIGo2AlggACAEQdAAahC7hoCAACELIARBgAFqJICAgIAAIAsLmwIBAn8jgICAgABBEGsiAiSAgICAACACQQA2AgwCQAJAAkACQCABQYABSQ0AIAFBgBBJDQEgAkEMaiEDIAFBgIAETw0CIAIgAUE/cUGAAXI6AA4gAiABQQZ2QT9xQYABcjoADSACIAFBDHZBD3FB4AFyOgAMQQMhAQwDCyACIAE6AAwgAkEMaiEDQQEhAQwCCyACIAFBP3FBgAFyOgANIAIgAUEGdkEfcUHAAXI6AAwgAkEMaiEDQQIhAQwBCyACIAFBP3FBgAFyOgAPIAIgAUESdkHwAXI6AAwgAiABQQZ2QT9xQYABcjoADiACIAFBDHZBP3FBgAFyOgANQQQhAQsgACADIAEQsoaAgAAhASACQRBqJICAgIAAIAELcQEBfyOAgICAAEEgayICJICAgIAAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQYySwYAAIAJBCGoQn4aAgAAhASACQSBqJICAgIAAIAELEQAgACgCACABIAIQsoaAgAALDwAgACgCACABEL2GgIAAC3QBAX8jgICAgABBIGsiAiSAgICAACACIAAoAgA2AgQgAkEIakEQaiABQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakGMksGAACACQQhqEJ+GgIAAIQEgAkEgaiSAgICAACABCxEAIAA1AgBBASABEMOGgIAAC+kCAwJ/AX4DfyOAgICAAEEwayIDJICAgIAAQSchBAJAAkAgAEKQzgBaDQAgACEFDAELQSchBANAIANBCWogBGoiBkF8aiAAIABCkM4AgCIFQpDOAH59pyIHQf//A3FB5ABuIghBAXRBw5DBgABqLwAAOwAAIAZBfmogByAIQeQAbGtB//8DcUEBdEHDkMGAAGovAAA7AAAgBEF8aiEEIABC/8HXL1YhBiAFIQAgBg0ACwsCQCAFpyIGQeMATA0AIANBCWogBEF+aiIEaiAFpyIGIAZB//8DcUHkAG4iBkHkAGxrQf//A3FBAXRBw5DBgABqLwAAOwAACwJAAkAgBkEKSA0AIANBCWogBEF+aiIEaiAGQQF0QcOQwYAAai8AADsAAAwBCyADQQlqIARBf2oiBGogBkEwajoAAAsgAiABQaz0wIAAQQAgA0EJaiAEakEnIARrEMWGgIAAIQQgA0EwaiSAgICAACAEC3QBAn8jgICAgABBIGsiAiSAgICAACABQRxqKAIAIQMgASgCGCEBIAJBCGpBEGogAEEQaikCADcDACACQQhqQQhqIABBCGopAgA3AwAgAiAAKQIANwMIIAEgAyACQQhqEJ+GgIAAIQAgAkEgaiSAgICAACAAC7EGAQZ/AkACQCABRQ0AQStBgIDEACAAKAIAIgZBAXEiARshByABIAVqIQgMAQsgBUEBaiEIIAAoAgAhBkEtIQcLAkACQCAGQQRxDQBBACECDAELQQAhCQJAIANFDQAgAyEKIAIhAQNAIAkgAS0AAEHAAXFBgAFGaiEJIAFBAWohASAKQX9qIgoNAAsLIAggA2ogCWshCAtBASEBAkACQCAAKAIIQQFGDQAgACAHIAIgAxDGhoCAAA0BIAAoAhggBCAFIABBHGooAgAoAgwRj4CAgAAADwsCQCAAQQxqKAIAIgkgCEsNACAAIAcgAiADEMaGgIAADQEgACgCGCAEIAUgAEEcaigCACgCDBGPgICAAAAPCwJAAkAgBkEIcQ0AQQAhASAJIAhrIgkhCAJAAkACQEEBIAAtACAiCiAKQQNGGw4EAgEAAQILIAlBAXYhASAJQQFqQQF2IQgMAQtBACEIIAkhAQsgAUEBaiEBA0AgAUF/aiIBRQ0CIAAoAhggACgCBCAAKAIcKAIQEYCAgIAAAEUNAAtBAQ8LIAAoAgQhBiAAQTA2AgQgAC0AICELQQEhASAAQQE6ACAgACAHIAIgAxDGhoCAAA0BQQAhASAJIAhrIgohAwJAAkACQEEBIAAtACAiCSAJQQNGGw4EAgEAAQILIApBAXYhASAKQQFqQQF2IQMMAQtBACEDIAohAQsgAUEBaiEBAkADQCABQX9qIgFFDQEgACgCGCAAKAIEIAAoAhwoAhARgICAgAAARQ0AC0EBDwsgACgCBCEKQQEhASAAKAIYIAQgBSAAKAIcKAIMEY+AgIAAAA0BIANBAWohCSAAKAIcIQMgACgCGCECAkADQCAJQX9qIglFDQFBASEBIAIgCiADKAIQEYCAgIAAAEUNAAwDCwsgACALOgAgIAAgBjYCBEEADwsgACgCBCEKQQEhASAAIAcgAiADEMaGgIAADQAgACgCGCAEIAUgACgCHCgCDBGPgICAAAANACAIQQFqIQkgACgCHCEDIAAoAhghAANAAkAgCUF/aiIJDQBBAA8LQQEhASAAIAogAygCEBGAgICAAABFDQALCyABC1wBAX8CQAJAIAFBgIDEAEYNAEEBIQQgACgCGCABIABBHGooAgAoAhARgICAgAAADQELAkAgAg0AQQAPCyAAKAIYIAIgAyAAQRxqKAIAKAIMEY+AgIAAACEECyAEC/wEAQh/I4CAgIAAQRBrIgIkgICAgAACQAJAIAEoAgQiA0UNAEEBIQQgACgCGCABKAIAIAMgAEEcaigCACgCDBGPgICAAAANAQsCQCABQQxqKAIAIgQNAEEAIQQMAQsgASgCCCIFIARBDGxqIQYgAkEIakF/aiEHIAJBCGpBBGohCANAAkACQAJAAkACQAJAAkACQAJAIAUvAQAOAwABAgALAkACQCAFKAIEIgFBwQBPDQAgAQ0BDAkLA0AgACgCGEHcksGAAEHAACAAKAIcKAIMEY+AgIAAAA0IIAFBQGoiAUHAAEsNAAsLQcAAIQQgACgCHCEDIAAoAhghCQJAIAFBwABGDQAgAUHcksGAAGosAABBv39MDQMgASEECyAJQdySwYAAIAQgAygCDBGPgICAAABFDQcMBgsgBS8BAiEBIAhBADoAACACQQA2AghBASEEAkACQAJAIAUvAQAOAwIAAQILAkAgBS8BAiIEQegHSQ0AQQRBBSAEQZDOAEkbIQkMBgtBASEJIARBCkkNBUECQQMgBEHkAEkbIQkMBQtBAiEECyAFIARBAnRqKAIAIglBBk8NAiAJDQNBACEJDAQLIAAoAhggBSgCBCAFKAIIIAAoAhwoAgwRj4CAgAAARQ0FDAQLQdySwYAAQcAAQQAgARCShoCAAAALIAlBBRCPhoCAAAALIAkhBANAIAcgBGogASABQf//A3FBCm4iA0EKbGtBMHI6AAAgAyEBIARBf2oiBA0ACwsgACgCGCACQQhqIAkgACgCHCgCDBGPgICAAABFDQELQQEhBAwCCyAGIAVBDGoiBUcNAAtBACEECyACQRBqJICAgIAAIAQLHQAgACgCGCABIAIgAEEcaigCACgCDBGPgICAAAALdAECfyOAgICAAEEgayICJICAgIAAIABBHGooAgAhAyAAKAIYIQAgAkEIakEQaiABQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggACADIAJBCGoQn4aAgAAhASACQSBqJICAgIAAIAELDQAgAC0AAEEQcUEEdgsNACAALQAAQSBxQQV2CzQAIAEoAhggAiADIAFBHGooAgAoAgwRj4CAgAAAIQIgAEEAOgAFIAAgAjoABCAAIAE2AgALOAAgACABKAIYIAIgAyABQRxqKAIAKAIMEY+AgIAAADoACCAAIAE2AgAgACADRToACSAAQQA2AgQLOgEBfyABKAIYQa+QwYAAQQEgAUEcaigCACgCDBGPgICAAAAhAiAAQQA6AAUgACACOgAEIAAgATYCAAstAAJAIAAtAAANACABQaCTwYAAQQUQkYaAgAAPCyABQZyTwYAAQQQQkYaAgAALvgkBDX8jgICAgABBMGsiAySAgICAAEEBIQQCQAJAIAIoAhhBIiACQRxqKAIAKAIQEYCAgIAAAA0AAkACQCABDQBBACEFDAELIAAgAWohBiAAIQdBACEFQQAhCAJAA0AgByEJIAdBAWohCgJAAkACQCAHLAAAIgtBf0oNAAJAAkAgCiAGRw0AQQAhDCAGIQcMAQsgBy0AAUE/cSEMIAdBAmoiCiEHCyALQR9xIQ0CQCALQf8BcSILQd8BSw0AIAwgDUEGdHIhCwwCCwJAAkAgByAGRw0AQQAhDiAGIQ8MAQsgBy0AAEE/cSEOIAdBAWoiCiEPCyAOIAxBBnRyIQwCQCALQfABTw0AIAwgDUEMdHIhCwwCCwJAAkAgDyAGRw0AQQAhCyAKIQcMAQsgD0EBaiEHIA8tAABBP3EhCwsgDEEGdCANQRJ0QYCA8ABxciALciILQYCAxABHDQIMBAsgC0H/AXEhCwsgCiEHCyADIAtBARClhoCAAAJAAkACQAJAIAMoAgAiCg4EAQIBAAELIAMoAgggAy0ADGpBAUYNAQsgAyABNgIUIAMgADYCECADIAU2AhggAyAINgIcAkACQCAIIAVJDQACQCAFRQ0AIAUgAUYNACAFIAFPDQEgACAFaiwAAEG/f0wNAQsCQCAIRQ0AIAggAUYNACAIIAFPDQEgACAIaiwAAEG/f0wNAQsgAigCGCAAIAVqIAggBWsgAigCHCgCDBGPgICAAABFDQEMAwsgAyADQRxqNgIoIAMgA0EYajYCJCADIANBEGo2AiAgA0EgahDRhoCAAAALIAMtAAwhDSADKAIIIQ8CQAJAIAMoAgQiDkGAgMQARw0AA0AgCiEFQQEhCkHcACEMAkACQCAFDgQEBAEABAsgDUH/AXEhBUEDIQpBBCENAkACQAJAAkAgBQ4GBwMCAQAEBwtBAyENQfUAIQxBAyEKDAMLQQIhDUH7ACEMDAILQQJBASAPGyENQYCAxAAgD0ECdEEccXZBD3FBMHIhDCAPQX9qQQAgDxshDwwBC0EAIQ1B/QAhDAsgAigCGCAMIAIoAhwoAhARgICAgAAADQQMAAsLA0AgCiEMQQEhCkHcACEFAkACQAJAAkAgDA4EBQEDAAULIA1B/wFxIQxBAyEKQQQhDQJAAkACQCAMDgYHAgEABAUHC0ECIQ1B+wAhBQwECyAOIA9BAnRBHHF2QQ9xIgVBMHIgBUHXAGogBUEKSRshBUECQQEgDxshDSAPQX9qQQAgDxshDwwDC0EAIQ1B/QAhBQwCC0EAIQogDiEFDAELQQMhDUH1ACEFQQMhCgsgAigCGCAFIAIoAhwoAhARgICAgAAADQMMAAsLQQEhBQJAIAtBgAFJDQBBAiEFIAtBgBBJDQBBA0EEIAtBgIAESRshBQsgBSAIaiEFCyAIIAlrIAdqIQggBiAHRw0BDAILC0EBIQQMAgsgBUUNACAFIAFGDQAgBSABTw0CIAAgBWosAABBv39MDQILIAIoAhggACAFaiABIAVrIAIoAhwoAgwRj4CAgAAADQAgAigCGEEiIAIoAhwoAhARgICAgAAAIQQLIANBMGokgICAgAAgBA8LIAAgASAFIAEQkoaAgAAACyoBAX8gACgCACIBKAIAIAEoAgQgACgCBCgCACAAKAIIKAIAEJKGgIAAAAsOACACIAAgARCRhoCAAAudBAEHfyOAgICAAEEQayICJICAgIAAQQEhAwJAIAEoAhhBJyABQRxqKAIAKAIQEYCAgIAAAA0AIAIgACgCAEEBEKWGgIAAIAJBDGotAAAhBCACQQhqKAIAIQUgAigCACEAAkACQCACKAIEIgZBgIDEAEYNAANAIAAhB0EBIQNB3AAhCEEBIQACQAJAAkACQCAHDgQGAQMABgsgBEH/AXEhB0EEIQRBAyEAAkACQAJAIAcOBggCAQAEBQgLQQIhBEH7ACEIDAQLIAYgBUECdEEccXZBD3EiCEEwciAIQdcAaiAIQQpJGyEIQQJBASAFGyEEIAVBf2pBACAFGyEFDAMLQQAhBEH9ACEIDAILQQAhACAGIQgMAQtBAyEAQfUAIQhBAyEECyABKAIYIAggASgCHCgCEBGAgICAAABFDQAMAwsLA0AgACEIQQEhA0HcACEHQQEhAAJAAkAgCA4EAwMBAAMLIARB/wFxIQhBBCEEQQMhAAJAAkACQAJAIAgOBgYDAgEABAYLQQMhAEH1ACEHQQMhBAwDC0ECIQRB+wAhBwwCC0ECQQEgBRshBEGAgMQAIAVBAnRBHHF2QQ9xQTByIQcgBUF/akEAIAUbIQUMAQtBACEEQf0AIQcLIAEoAhggByABKAIcKAIQEYCAgIAAAEUNAAwCCwsgASgCGEEnIAEoAhwoAhARgICAgAAAIQMLIAJBEGokgICAgAAgAwv0AgECfyOAgICAAEEQayICJICAgIAAAkACQAJAIAFBCGooAgBBAUYNACABQRBqKAIAQQFHDQELIAAoAgAhACACQQA2AgwCQAJAAkAgAEGAAUkNACAAQYAQSQ0BIAJBDGohAyAAQYCABE8NAiACIABBP3FBgAFyOgAOIAIgAEEGdkE/cUGAAXI6AA0gAiAAQQx2QQ9xQeABcjoADCABIANBAxCRhoCAACEBDAQLIAIgADoADCABIAJBDGpBARCRhoCAACEBDAMLIAIgAEE/cUGAAXI6AA0gAiAAQQZ2QR9xQcABcjoADCABIAJBDGpBAhCRhoCAACEBDAILIAIgAEE/cUGAAXI6AA8gAiAAQRJ2QfABcjoADCACIABBBnZBP3FBgAFyOgAOIAIgAEEMdkE/cUGAAXI6AA0gASADQQQQkYaAgAAhAQwBCyABKAIYIAAoAgAgAUEcaigCACgCEBGAgICAAAAhAQsgAkEQaiSAgICAACABC+UKAwl/AX4BfwJAAkACQAJAAkACQAJAAkAgBEEBSw0AQQAhBSAEIQYgBCEHQQAhCCAEDgICAQILQQEhCUEAIQVBASEKQQAhC0EBIQYDQCAKIQwCQAJAIAsgBWoiCiAETw0AAkAgAyAJai0AAEH/AXEiCSADIApqLQAAIgpJDQACQCAJIApGDQBBASEGIAxBAWohCkEAIQsgDCEFDAMLQQAgC0EBaiIKIAogBkYiCRshCyAKQQAgCRsgDGohCgwCCyAMIAtqQQFqIgogBWshBkEAIQsMAQtB6JTBgAAgCiAEEIyGgIAAAAsgCiALaiIJIARJDQALQQEhCUEAIQhBASEKQQAhC0EBIQcDQCAKIQwCQAJAIAsgCGoiCiAETw0AAkAgAyAJai0AAEH/AXEiCSADIApqLQAAIgpLDQACQCAJIApGDQBBASEHIAxBAWohCkEAIQsgDCEIDAMLQQAgC0EBaiIKIAogB0YiCRshCyAKQQAgCRsgDGohCgwCCyAMIAtqQQFqIgogCGshB0EAIQsMAQtB6JTBgAAgCiAEEIyGgIAAAAsgCiALaiIJIARJDQALCwJAAkACQCAFIAggBSAISyILGyINIARLDQAgBiAHIAsbIgogDWoiCyAKSQ0BIAsgBEsNAiAKRQ0EIAMgAyAKaiANEPSGgIAARQ0EIA0gBCANayILIA0gC0sbIQxCACEOIAQhCiADIQsDQEIBIAsxAABCP4OGIA6EIQ4gC0EBaiELIApBf2oiCg0ACyAMQQFqIQpBfyEMIA0hCUF/IQsMBQsgDSAEEI+GgIAAAAsgCiALEJCGgIAAAAsgCyAEEI+GgIAAAAsgACADNgI4IAAgATYCMCAAQgA3AwAgAEE8akEANgIAIABBNGogAjYCACAAQQxqQYECOwEAIABBCGogAjYCAA8LQQEhBUEAIQtBASEJQQAhBgJAA0AgCSIMIAtqIgcgBE8NASAEIAtrIAxBf3NqIgkgBE8NBSALQX9zIARqIAZrIgggBE8NBAJAAkACQCADIAlqLQAAQf8BcSIJIAMgCGotAAAiCEkNACAJIAhGDQEgDEEBaiEJQQAhC0EBIQUgDCEGDAILIAdBAWoiCSAGayEFQQAhCwwBC0EAIAtBAWoiCSAJIAVGIggbIQsgCUEAIAgbIAxqIQkLIAUgCkcNAAsLQQEhBUEAIQtBASEJQQAhBwJAAkACQAJAAkADQCAJIgwgC2oiDyAETw0BIAQgC2sgDEF/c2oiCSAETw0CIAtBf3MgBGogB2siCCAETw0DAkACQAJAIAMgCWotAABB/wFxIgkgAyAIai0AACIISw0AIAkgCEYNASAMQQFqIQlBACELQQEhBSAMIQcMAgsgD0EBaiIJIAdrIQVBACELDAELQQAgC0EBaiIJIAkgBUYiCBshCyAJQQAgCBsgDGohCQsgBSAKRw0ACwsgCiAESw0FIAQgBiAHIAYgB0sbayEJQgAhDiAKDQJBACEKQQAhDAwDC0H4lMGAACAJIAQQjIaAgAAAC0GIlcGAACAIIAQQjIaAgAAAC0EAIQxBACELA0BCASADIAtqMQAAQj+DhiAOhCEOIAogC0EBaiILRw0ACwsgBCELCyAAIAM2AjggACABNgIwIABBATYCACAAQTxqIAQ2AgAgAEE0aiACNgIAIABBKGogCzYCACAAQSRqIAw2AgAgAEEgaiACNgIAIABBHGpBADYCACAAQRhqIAo2AgAgAEEUaiAJNgIAIABBEGogDTYCACAAQQhqIA43AgAPCyAKIAQQj4aAgAAAC0GIlcGAACAIIAQQjIaAgAAAC0H4lMGAACAJIAQQjIaAgAAAC6YBAQN/I4CAgIAAQYABayICJICAgIAAIAAtAAAhA0EAIQADQCACIABqQf8AaiADQQ9xIgRBMHIgBEHXAGogBEEKSRs6AAAgAEF/aiEAIANBBHZBD3EiAw0ACwJAIABBgAFqIgNBgQFJDQAgA0GAARCQhoCAAAALIAFBAUHBkMGAAEECIAIgAGpBgAFqQQAgAGsQxYaAgAAhACACQYABaiSAgICAACAAC3gCAX8BfiOAgICAAEEQayIDJICAgIAAIANBCGogASACENiGgIAAAkACQCADKQMIIgRCgICAgPAfg0KAgICAIFENACAAIAQ3AgRBASEBDAELIAAgATYCBCAAQQhqIAI2AgBBACEBCyAAIAE2AgAgA0EQaiSAgICAAAulBwEGfwJAAkACQCACRQ0AQQAgAWtBACABQQNxGyEDIAJBeWpBACACQQdLGyEEQQAhBQNAAkACQAJAAkACQCABIAVqLQAAIgZBGHRBGHUiB0F/Sg0AAkACQAJAAkAgBkGulcGAAGotAABBfmoiCEECSw0AIAgOAwECAwELIABBgQI7AQQgACAFNgIADwsCQCAFQQFqIgYgAkkNACAAQQA6AAQgACAFNgIADwsgASAGai0AAEHAAXFBgAFGDQMgAEGBAjsBBCAAIAU2AgAPCwJAIAVBAWoiCCACSQ0AIABBADoABCAAIAU2AgAPCyABIAhqLQAAIQgCQAJAIAZBoH5qIgZBDUsNAAJAAkAgBg4OAAICAgICAgICAgICAgEACyAIQeABcUGgAUcNDAwCCyAIQRh0QRh1QX9KDQsgCEH/AXFBoAFJDQEMCwsCQCAHQR9qQf8BcUELSw0AIAhBGHRBGHVBf0oNCyAIQf8BcUHAAU8NCwwBCyAIQf8BcUG/AUsNCiAHQf4BcUHuAUcNCiAIQRh0QRh1QX9KDQoLAkAgBUECaiIGIAJJDQAgAEEAOgAEIAAgBTYCAA8LIAEgBmotAABBwAFxQYABRg0CIABBgQQ7AQQgACAFNgIADwsCQCAFQQFqIgggAkkNACAAQQA6AAQgACAFNgIADwsgASAIai0AACEIAkACQCAGQZB+aiIGQQRLDQACQAJAIAYOBQACAgIBAAsgCEHwAGpB/wFxQTBPDQoMAgsgCEEYdEEYdUF/Sg0JIAhB/wFxQZABSQ0BDAkLIAhB/wFxQb8BSw0IIAdBD2pB/wFxQQJLDQggCEEYdEEYdUF/Sg0ICwJAIAVBAmoiBiACSQ0AIABBADoABCAAIAU2AgAPCyABIAZqLQAAQcABcUGAAUcNAgJAIAVBA2oiBiACSQ0AIABBADoABCAAIAU2AgAPCyABIAZqLQAAQcABcUGAAUYNASAAQYEGOwEEIAAgBTYCAA8LIAMgBWtBA3ENAgJAIAUgBE8NAANAIAEgBWoiBkEEaigCACAGKAIAckGAgYKEeHENASAFQQhqIgUgBEkNAAsLIAUgAk8NAwNAIAEgBWosAABBAEgNBCACIAVBAWoiBUcNAAwGCwsgBkEBaiEFDAILIABBgQQ7AQQgACAFNgIADwsgBUEBaiEFCyAFIAJJDQALCyAAQQI6AAQPCyAAQYECOwEEIAAgBTYCAA8LIABBgQI7AQQgACAFNgIACxEAIAAxAABBASABEMOGgIAACxEAIAApAwBBASABEMOGgIAAC6MBAQN/I4CAgIAAQYABayICJICAgIAAIAAoAgAhA0EAIQADQCACIABqQf8AaiADQQ9xIgRBMHIgBEHXAGogBEEKSRs6AAAgAEF/aiEAIANBBHYiAw0ACwJAIABBgAFqIgNBgQFJDQAgA0GAARCQhoCAAAALIAFBAUHBkMGAAEECIAIgAGpBgAFqQQAgAGsQxYaAgAAhACACQYABaiSAgICAACAACxAAIAAgASACIAMQnYaAgAAL2wEDAn8BfgF/AkAgAg0AIABBADoAASAAQQE6AAAPCwJAAkAgAS0AAEErRw0AIAJBf2oiAkUNASABQQFqIQELQQAhAwJAAkACQANAIAJFDQMgAS0AAEFQaiIEQQlLDQEgA61CCn4iBUIgiKcNAiABQQFqIQEgAkF/aiECIAWnIgYgBGoiAyAGTw0ACyAAQQI6AAEgAEEBOgAADwsgAEEBOgABIABBAToAAA8LIABBAjoAASAAQQE6AAAPCyAAQQRqIAM2AgAgAEEAOgAADwsgAEEAOgABIABBAToAAAubAgECfyOAgICAAEEQayICJICAgIAAIAIgASgCGEGMpsGAAEERIAFBHGooAgAoAgwRj4CAgAAAOgAIIAIgATYCACACQQA6AAkgAkEANgIEIAIgADYCDCACIAJBDGpB/KXBgAAQtYaAgAAaIAItAAghAQJAIAIoAgQiA0UNACABQf8BcSEAQQEhAQJAIAANAAJAIANBAUcNACACLQAJQf8BcUUNACACKAIAIgAtAABBBHENAEEBIQEgACgCGEGskMGAAEEBIABBHGooAgAoAgwRj4CAgAAADQELIAIoAgAiASgCGEGtkMGAAEEBIAFBHGooAgAoAgwRj4CAgAAAIQELIAIgAToACAsgAkEQaiSAgICAACABQf8BcUEARwtKAgF/AXwgAS0AAEEBdEECcSECIAArAwAhAwJAIAEoAhBBAUYNACABIAMgAkEAELyGgIAADwsgASADIAIgAUEUaigCABC6hoCAAAulAQEDfyOAgICAAEGAAWsiAiSAgICAACAALQAAIQNBACEAA0AgAiAAakH/AGogA0EPcSIEQTByIARBN2ogBEEKSRs6AAAgAEF/aiEAIANBBHZBD3EiAw0ACwJAIABBgAFqIgNBgQFJDQAgA0GAARCQhoCAAAALIAFBAUHBkMGAAEECIAIgAGpBgAFqQQAgAGsQxYaAgAAhACACQYABaiSAgICAACAAC6IBAQN/I4CAgIAAQYABayICJICAgIAAIAAoAgAhA0EAIQADQCACIABqQf8AaiADQQ9xIgRBMHIgBEE3aiAEQQpJGzoAACAAQX9qIQAgA0EEdiIDDQALAkAgAEGAAWoiA0GBAUkNACADQYABEJCGgIAAAAsgAUEBQcGQwYAAQQIgAiAAakGAAWpBACAAaxDFhoCAACEAIAJBgAFqJICAgIAAIAALqwEDAX8BfgF/I4CAgIAAQYABayICJICAgIAAIAApAwAhA0EAIQADQCACIABqQf8AaiADp0EPcSIEQTByIARB1wBqIARBCkkbOgAAIABBf2ohACADQgSIIgNCAFINAAsCQCAAQYABaiIEQYEBSQ0AIARBgAEQkIaAgAAACyABQQFBwZDBgABBAiACIABqQYABakEAIABrEMWGgIAAIQAgAkGAAWokgICAgAAgAAuqAQMBfwF+AX8jgICAgABBgAFrIgIkgICAgAAgACkDACEDQQAhAANAIAIgAGpB/wBqIAOnQQ9xIgRBMHIgBEE3aiAEQQpJGzoAACAAQX9qIQAgA0IEiCIDQgBSDQALAkAgAEGAAWoiBEGBAUkNACAEQYABEJCGgIAAAAsgAUEBQcGQwYAAQQIgAiAAakGAAWpBACAAaxDFhoCAACEAIAJBgAFqJICAgIAAIAALKQEBfiAAKAIAIgCsIgIgAkI/hyICfCAChSAAQX9zQR92IAEQw4aAgAALIwECfiAAKQMAIgIgAkI/hyIDfCADhSACQn9VIAEQw4aAgAALwgMDBH8CfgJ/I4CAgIAAQdAAayIEJICAgIAAAkACQCAAQpDOAFRBACABUBtFDQAgAKchBUEnIQYMAQtBJyEGIARBIGohBwNAIARBGGogACABQpDOAEIAEPiGgIAAIARBCGogBCkDGCIIIAcpAwAiCUKQzgBCABD3hoCAACAEQSlqIAZqIgVBfGogACAEKQMIfaciCkH//wNxQeQAbiILQQF0QcOQwYAAai8AADsAACAFQX5qIAogC0HkAGxrQf//A3FBAXRBw5DBgABqLwAAOwAAIABC/8HXL1YhBSABQgBSIQogAVAhCyAGQXxqIQYgCCEAIAkhASAFIAogCxsNAAsgCKchBQsCQAJAIAVB4wBKDQAgBSEKDAELIARBKWogBkF+aiIGaiAFIAVB//8DcUHkAG4iCkHkAGxrQf//A3FBAXRBw5DBgABqLwAAOwAACwJAAkAgCkEKSA0AIARBKWogBkF+aiIGaiAKQQF0QcOQwYAAai8AADsAAAwBCyAEQSlqIAZBf2oiBmogCkEwajoAAAsgAyACQaz0wIAAQQAgBEEpaiAGakEnIAZrEMWGgIAAIQYgBEHQAGokgICAgAAgBgsZACAAKQMAIABBCGopAwBBASABEOaGgIAACyEAIAEoAhhBpabBgABBBSABQRxqKAIAKAIMEY+AgIAAAAsPACAAKAIAIAEQoYaAgAALEgAgAUGlk8GAAEECEJGGgIAAC9oCAQJ/I4CAgIAAQRBrIgIkgICAgAACQAJAIAAoAgAiAC0AAEEBRg0AIAEoAhhBoabBgABBBCABQRxqKAIAKAIMEY+AgIAAACEBDAELIAIgASgCGEGdpsGAAEEEIAFBHGooAgAoAgwRj4CAgAAAOgAIIAIgATYCACACQQA6AAkgAkEANgIEIAIgAEEBajYCDCACIAJBDGpBsJDBgAAQtYaAgAAaIAItAAghAQJAIAIoAgQiA0UNACABQf8BcSEAQQEhAQJAIAANAAJAIANBAUcNACACLQAJQf8BcUUNACACKAIAIgAtAABBBHENAEEBIQEgACgCGEGskMGAAEEBIABBHGooAgAoAgwRj4CAgAAADQELIAIoAgAiASgCGEGtkMGAAEEBIAFBHGooAgAoAgwRj4CAgAAAIQELIAIgAToACAsgAUH/AXFBAEchAQsgAkEQaiSAgICAACABC+ICAQN/I4CAgIAAQYABayICJICAgIAAIAAoAgAhAAJAAkACQAJAAkAgASgCACIDQRBxDQAgAC0AACEEIANBIHENASAErUL/AYNBASABEMOGgIAAIQAMAgsgAC0AACEEQQAhAANAIAIgAGpB/wBqIARBD3EiA0EwciADQdcAaiADQQpJGzoAACAAQX9qIQAgBEEEdkEPcSIEDQALIABBgAFqIgRBgQFPDQIgAUEBQcGQwYAAQQIgAiAAakGAAWpBACAAaxDFhoCAACEADAELQQAhAANAIAIgAGpB/wBqIARBD3EiA0EwciADQTdqIANBCkkbOgAAIABBf2ohACAEQQR2QQ9xIgQNAAsgAEGAAWoiBEGBAU8NAiABQQFBwZDBgABBAiACIABqQYABakEAIABrEMWGgIAAIQALIAJBgAFqJICAgIAAIAAPCyAEQYABEJCGgIAAAAsgBEGAARCQhoCAAAALpgIBAn8jgICAgABBEGsiAiSAgICAACABKAIYQaqmwYAAQQkgAUEcaigCACgCDBGPgICAAAAhAyACQQA6AAUgAiADOgAEIAIgATYCACACIAA2AgwgAkGzpsGAAEELIAJBDGpB7KXBgAAQp4aAgAAaIAIgAEEEajYCDCACQb6mwYAAQQkgAkEMakHIpsGAABCnhoCAABogAi0ABCEBAkAgAi0ABUUNACABQf8BcSEAQQEhAQJAIAANACACKAIAIgFBHGooAgAoAgwhACABKAIYIQMCQCABLQAAQQRxDQAgA0GnkMGAAEECIAARj4CAgAAAIQEMAQsgA0GmkMGAAEEBIAARj4CAgAAAIQELIAIgAToABAsgAkEQaiSAgICAACABQf8BcUEARwuiAQECfyAAQQp2IQECQAJAAkACQCAAQYDoB0kNAEEhIQIgAUGAB0YNAUEADwsgAUHYpsGAAGotAAAiAkEhSw0BCyACQQR0IABBBnZBD3FyQdWnwYAAai0AACIBQbMBSw0BIAFBA3RB+KvBgABqKQMAQgEgAEE/ca2Gg0IAUg8LQcykwYAAIAJBIhCMhoCAAAALQdykwYAAIAFBtAEQjIaAgAAAC6ABAQJ/IABBCnYhAQJAAkACQAJAIABBgNgHSQ0AQQYhAiABQfwARg0BQQAPCyABQZi3wYAAai0AACICQRJLDQELIAJBBHQgAEEGdkEPcXJBk7jBgABqLQAAIgFBPksNASABQQN0Qci6wYAAaikDAEIBIABBP3GthoNCAFIPC0HMpMGAACACQRMQjIaAgAAAC0HcpMGAACABQT8QjIaAgAAAC/YCAQF/AkBBAEG3BSABQew8SRsiAiACQdsCaiICIAJBBHRBiMzBgABqKAIAIAFLGyICIAJBrgFqIgIgAkEEdEGIzMGAAGooAgAgAUsbIgIgAkHXAGoiAiACQQR0QYjMwYAAaigCACABSxsiAiACQStqIgIgAkEEdEGIzMGAAGooAgAgAUsbIgIgAkEWaiICIAJBBHRBiMzBgABqKAIAIAFLGyICIAJBC2oiAiACQQR0QYjMwYAAaigCACABSxsiAiACQQVqIgIgAkEEdEGIzMGAAGooAgAgAUsbIgIgAkEDaiICIAJBBHRBiMzBgABqKAIAIAFLGyICIAJBAWoiAiACQQR0QYjMwYAAaigCACABSxsiAiACQQFqIgIgAkEEdEGIzMGAAGooAgAgAUsbQQR0IgJBiMzBgABqKAIAIAFGDQAgAEIANwIEIAAgATYCAA8LIABBCGogAkGUzMGAAGooAgA2AgAgACACQYzMwYAAaikCADcCAAuAAgECfyOAgICAAEEQayICJICAgIAAIAEoAhhB6PnCgABBCSABQRxqKAIAKAIMEY+AgIAAACEDIAJBADoABSACIAM6AAQgAiABNgIAIAIgADYCDCACQfH5woAAQQcgAkEMakH8pcGAABCnhoCAABogAi0ABCEBAkAgAi0ABUUNACABQf8BcSEAQQEhAQJAIAANACACKAIAIgFBHGooAgAoAgwhACABKAIYIQMCQCABLQAAQQRxDQAgA0GnkMGAAEECIAARj4CAgAAAIQEMAQsgA0GmkMGAAEEBIAARj4CAgAAAIQELIAIgAToABAsgAkEQaiSAgICAACABQf8BcUEARwssAQF/AkAgAkUNACAAIQMDQCADIAE6AAAgA0EBaiEDIAJBf2oiAg0ACwsgAAs2AQF/AkAgAkUNACAAIQMDQCADIAEtAAA6AAAgA0EBaiEDIAFBAWohASACQX9qIgINAAsLIAALSgEDf0EAIQMCQCACRQ0AAkADQCAALQAAIgQgAS0AACIFRw0BIABBAWohACABQQFqIQEgAkF/aiICRQ0CDAALCyAEIAVrIQMLIAMLVwEBfgJAAkAgA0HAAHENACADRQ0BIAFBACADa0E/ca2IIAIgA0E/ca0iBIaEIQIgASAEhiEBDAELIAEgA0E/ca2GIQJCACEBCyAAIAE3AwAgACACNwMIC1cBAX4CQAJAIANBwABxDQAgA0UNASABIANBP3GtIgSIIAJBACADa0E/ca2GhCEBIAIgBIghAgwBCyACIANBP3GtiCEBQgAhAgsgACABNwMAIAAgAjcDCAt1AQJ+IAAgA0IgiCIFIAFCIIgiBn4gAyACfnwgBCABfnwgA0L/////D4MiAyABQv////8PgyIBfiIEQiCIIAMgBn58IgNCIIh8IANC/////w+DIAUgAX58IgNCIIh8NwMIIAAgA0IghiAEQv////8Pg4Q3AwALTQEBfyOAgICAAEEQayIFJICAgIAAIAUgASACIAMgBEEAEPmGgIAAIAUpAwAhASAAIAVBCGopAwA3AwggACABNwMAIAVBEGokgICAgAAL9wUCA38GfiOAgICAAEEwayIGJICAgIAAAkACQAJAAkACQAJAAkACQAJAAkAgAlANACADUA0BIARQDQIgBHmnIAJ5p2siB0E/Sw0DQf8AIAdrIQggB0EBaiEHDAgLAkAgBFANACAFDQQMBgsCQAJAIAVFDQAgA0IAUQ0GIAVCADcDCCAFIAEgA4I3AwAMAQsgA0IAUQ0FCyABIAOAIQEMBgsgBFANAwJAAkACQCABUA0AIAR7QgFRDQEgBHmnIAJ5p2siB0E+Sw0CQf8AIAdrIQggB0EBaiEHDAkLAkAgBUUNACAFQgA3AwAgBSACIASCNwMICyACIASAIQEMBwsCQCAFRQ0AIAUgATcDACAFIARCf3wgAoM3AwgLIAIgBHpCP4OIIQEMBgsgBUUNBAwCCwJAIAN7QgFRDQBBv38gA3mnIAJ5p2siB2shCCAHQcEAaiEHDAYLAkAgBUUNACAFQgA3AwggBSADQn98IAGDNwMACyADQgFRDQYgBkEgaiABIAIgA3qnEPaGgIAAIAZBKGopAwAhAiAGKQMgIQEMBgsgBUUNAgsgBSABNwMAIAUgAjcDCEIAIQEMAgsAAAtCACEBC0IAIQIMAQsgBiABIAIgCEH/AHEQ9YaAgAAgBkEQaiABIAIgB0H/AHEQ9oaAgAAgBkEIaikDACECIAZBEGpBCGopAwAhCSAGKQMAIQEgBikDECEKAkACQCAHDQBCACELQgAhDAwBC0IAIQxCACENA0AgCUIBhiAKQj+IhCILIAtCf4UgBHwgCkIBhiACQj+IhCIKQn+FIgsgA3wgC1StfEI/hyILIASDfSAKIAsgA4MiDlStfSEJIAogDn0hCkIAIAJCAYYgAUI/iISEIQIgDSABQgGGhCEBIAtCAYMiCyENIAdBf2oiBw0ACwsCQCAFRQ0AIAUgCjcDACAFIAk3AwgLIAwgAkIBhiABQj+IhIQhAiALIAFCAYaEIQELIAAgATcDACAAIAI3AwggBkEwaiSAgICAAAsLw/oCAgBBgIDAAAv4+QJhbHNlcnVldWxsaW50ZXJuYWwgZXJyb3I6IGVudGVyZWQgdW5yZWFjaGFibGUgY29kZTw6OnN0ZDo6bWFjcm9zOjpwYW5pYyBtYWNyb3M+ADIAEAAdAAAAAgAAAAQAAAAvVXNlcnMvY3lwcmVzcy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9zZXJkZV9qc29uLTEuMC40OC9zcmMvZGUucnMAAAAAAABhdHRlbXB0IHRvIHN1YnRyYWN0IHdpdGggb3ZlcmZsb3cAAABgABAAWgAAAPYBAAAbAAAAAAAAAAAAAAAAAAAAYXR0ZW1wdCB0byBtdWx0aXBseSB3aXRoIG92ZXJmbG93AAAAAAAAAAAAAAAAAAAAYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvd2AAEABaAAAA9wEAAA0AAABgABAAWgAAAKMBAAAjAAAAYAAQAFoAAAAyAgAAEwAAAGAAEABaAAAAuwEAABUAAAB9InRydWVmYWxzZW51bGx7LFx0XHJcblxmXGJcXFwiOqwBEAAAAAAAYSBEaXNwbGF5IGltcGxlbWVudGF0aW9uIHJldHVybmVkIGFuIGVycm9yIHVuZXhwZWN0ZWRseS9ydXN0Yy9jZDFlZjM5MGU3MzFlZDc3YjkwYjExYjFmNzdlMmM1Y2E2NDFiMjYxL3NyYy9saWJhbGxvYy9zdHJpbmcucnMAAADrARAARgAAAHwIAAAJAAAAYWxyZWFkeSBib3Jyb3dlZC9ydXN0Yy9jZDFlZjM5MGU3MzFlZDc3YjkwYjExYjFmNzdlMmM1Y2E2NDFiMjYxL3NyYy9saWJjb3JlL2NlbGwucnMAVAIQAEMAAABuAwAACQAAAAoAAAAIAAAABAAAAAsAAAAMAAAAAAAAAAEAAAANAAAADgAAAAAAAAABAAAADwAAABAAAAAEAAAABAAAABEAAABjYWxsZWQgYFJlc3VsdDo6dW53cmFwKClgIG9uIGFuIGBFcnJgIHZhbHVlABIAAAAAAAAAAQAAABMAAABVbmV4cGVjdGVkIGxlbmd0aCBvZiBpbnB1dC9Vc2Vycy9jeXByZXNzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2JvcnNoLTAuNi4xL3NyYy9kZS9tb2QucnMAAD4DEABYAAAASAAAADAAAAAvcnVzdGMvY2QxZWYzOTBlNzMxZWQ3N2I5MGIxMWIxZjc3ZTJjNWNhNjQxYjI2MS9zcmMvbGliY29yZS9vcHMvYXJpdGgucnOoAxAASAAAALsCAAAzAAAAAQAAAAAAAABOb3QgYWxsIGJ5dGVzIHJlYWRtaXNzaW5nIGZpZWxkIGBgAAAaBBAADwAAACkEEAABAAAAaW52YWxpZCBsZW5ndGggLCBleHBlY3RlZCAAADwEEAAPAAAASwQQAAsAAABkdXBsaWNhdGUgZmllbGQgYAAAAGgEEAARAAAAKQQQAAEAAAAvVXNlcnMvY3lwcmVzcy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9zZXJkZV9qc29uLTEuMC40OC9zcmMvcmVhZC5yc4wEEABcAAAA8wEAAAkAAAAvVXNlcnMvY3lwcmVzcy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9uZWFyLXNkay0wLjkuMi9zcmMvY29sbGVjdGlvbnMvbWFwLnJz+AQQAGQAAACAAAAAQAAAAFRoZSBjb2xsZWN0aW9uIGlzIGFuIGluY29uc2lzdGVudCBzdGF0ZS4gRGlkIHByZXZpb3VzIHNtYXJ0IGNvbnRyYWN0IGV4ZWN1dGlvbiB0ZXJtaW5hdGUgdW5leHBlY3RlZGx5P0Nhbm5vdCBzZXJpYWxpemUga2V5IHdpdGggQm9yc2hDYW5ub3Qgc2VyaWFsaXplIHZhbHVlIHdpdGggQm9yc2hDYW5ub3QgZGVzZXJpYWxpemUgdmFsdWUgd2l0aCBCb3JzaAAAAPgEEABkAAAARwAAACoAAAD4BBAAZAAAACkAAAA3AAAA+AQQAGQAAAAtAAAAMwAAAPgEEABkAAAAMQAAADUAAABJbmRleCBvdXQgb2YgYm91bmRzL1VzZXJzL2N5cHJlc3MvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbmVhci1zZGstMC45LjIvc3JjL2NvbGxlY3Rpb25zL3ZlY3Rvci5yc2NoZWNrZWQgYGluZGV4IDwgbGVuYCBhYm92ZSwgc28gYGxlbiA+IDBgAIsGEABnAAAATAAAACIAAACLBhAAZwAAACkAAAAxAAAAiwYQAGcAAABnAAAADQAAAIsGEABnAAAAWwAAAAkAAABTVEFURUNhbm5vdCBkZXNlcmlhbGl6ZSB0aGUgY29udHJhY3Qgc3RhdGUuL1VzZXJzL2N5cHJlc3MvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbmVhci1zZGstMC45LjIvc3JjL2Vudmlyb25tZW50L2Vudi5ycwCHBxAAZAAAANUCAAAVAAAAQ2Fubm90IHNlcmlhbGl6ZSB0aGUgY29udHJhY3Qgc3RhdGUuhwcQAGQAAADZAgAAEAAAABQAAAAAAAAAAQAAABUAAAAWAAAAAAAAAAEAAAAXAAAAYAAQAFoAAABBBAAAEwAAAGAAEABaAAAASgQAABMAAAAYAAAAAAAAAAEAAAAZAAAAGgAAAAAAAAABAAAAGwAAABwAAAAAAAAAAQAAAB0AAAAeAAAAAAAAAAEAAAAfAAAAIAAAAAAAAAABAAAAIQAAAGFzc2VydGlvbiBmYWlsZWQ6IGAobGVmdCA9PSByaWdodClgCiAgbGVmdDogYGAsCiByaWdodDogYGA6IMAIEAAtAAAA7QgQAAwAAAD5CBAAAwAAAENvbnRyYWN0IGV4cGVjdGVkIGEgcmVzdWx0IG9uIHRoZSBjYWxsYmFjawAAFAkQACoAAABzcmMvbGliLnJzAABICRAACgAAACQAAAAFAAAARmFpbGVkIHRvIHNlcmlhbGl6ZSB0aGUgY3Jvc3MgY29udHJhY3QgYXJncyB1c2luZyBKU09OLgBICRAACgAAABoAAAABAAAAb25fYWNjb3VudF9jcmVhdGVkb25fYWNjb3VudF9jcmVhdGVkX2FuZF9jbGFpbWVkYWNjb3VudF9pZGFtb3VudHN0cnVjdCBJbnB1dEF0dGFjaGVkIGRlcG9zaXQgbXVzdCBiZSBncmVhdGVyIHRoYW4gQUNDRVNTX0tFWV9BTExPV0FOQ0UAAEgJEAAKAAAANQAAAAkAAABICRAACgAAADkAAAAcAAAASAkQAAoAAAA5AAAAGwAAAGNsYWltLGNyZWF0ZV9hY2NvdW50X2FuZF9jbGFpbUNsYWltIG9ubHkgY2FuIGNvbWUgZnJvbSB0aGlzIGFjY291bnQAggoQACUAAABICRAACgAAAEQAAAAJAAAASW52YWxpZCBhY2NvdW50IGlkAABICRAACgAAAEkAAAAJAAAAVW5leHBlY3RlZCBwdWJsaWMga2V5AAAASAkQAAoAAABNAAAAFgAAAENyZWF0ZSBhY2NvdW50IGFuZCBjbGFpbSBvbmx5IGNhbiBjb21lIGZyb20gdGhpcyBhY2NvdW50DAsQADgAAABICRAACgAAAFsAAAAJAAAASAkQAAoAAABgAAAACQAAAEgJEAAKAAAAZAAAABYAAABICRAACgAAAHsAAAAJAAAAQ2FsbGJhY2sgY2FuIG9ubHkgYmUgY2FsbGVkIGZyb20gdGhlIGNvbnRyYWN0AAAAjAsQAC0AAABICRAACgAAAIkAAAAJAAAASAkQAAoAAACYAAAACQAAACIAAAAAAAAAAQAAACMAAAAkAAAAJQAAACYAAAAnAAAAKAAAACkAAAAqAAAAKwAAACwAAAAtAAAALgAAAC8AAAAwAAAAMQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAAAA5AAAAOgAAADsAAAA8AAAAPQAAAD4AAAA/AAAAQAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAAAEoAAABLAAAATAAAAE0AAABOAAAATwAAAFAAAABRAAAAUgAAAEV4cGVjdGVkIGlucHV0IHNpbmNlIG1ldGhvZCBoYXMgYXJndW1lbnRzLgAASAkQAAoAAAAvAAAAAQAAAEZhaWxlZCB0byBkZXNlcmlhbGl6ZSBpbnB1dCBmcm9tIEpTT04uRmFpbGVkIHRvIHNlcmlhbGl6ZSB0aGUgcmV0dXJuIHZhbHVlIHVzaW5nIEpTT04uTWV0aG9kIGRvZXNuJ3QgYWNjZXB0IGRlcG9zaXRwdWJsaWNfa2V5c3RydWN0IElucHV0IHdpdGggMSBlbGVtZW50aQ0QABsAAABTAAAACAAAAAQAAABUAAAAbmV3X2FjY291bnRfaWRuZXdfcHVibGljX2tleXN0cnVjdCBJbnB1dCB3aXRoIDIgZWxlbWVudHO4DRAAHAAAAHByZWRlY2Vzc29yX2FjY291bnRfaWQAAFoAAAAMAAAABAAAAFsAAABcAAAAXQAAAF4AAABfAAAAYAAAAGEAAAAcDhAAAAAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHkvcnVzdGMvY2QxZWYzOTBlNzMxZWQ3N2I5MGIxMWIxZjc3ZTJjNWNhNjQxYjI2MS9zcmMvbGliYWxsb2Mvc3RyaW5nLnJzAAAAWw4QAEYAAAB8CAAACQAAAAAAAAAAAAAAAAAAAGF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3cAAAAAYXR0ZW1wdCB0byBzdWJ0cmFjdCB3aXRoIG92ZXJmbG93AAAAYgAAAAQAAAAEAAAAYwAAAGQAAABlAAAAL3J1c3RjL2NkMWVmMzkwZTczMWVkNzdiOTBiMTFiMWY3N2UyYzVjYTY0MWIyNjEvc3JjL2xpYmNvcmUvbWFjcm9zL21vZC5ycy9ydXN0Yy9jZDFlZjM5MGU3MzFlZDc3YjkwYjExYjFmNzdlMmM1Y2E2NDFiMjYxL3NyYy9saWJjb3JlL3N0ci9wYXR0ZXJuLnJzAGUPEABKAAAA2QQAABQAAABlDxAASgAAANkEAAAhAAAAZQ8QAEoAAADlBAAAFAAAAGUPEABKAAAA5QQAACEAAAAcDxAASQAAABIAAAANAAAAYXNzZXJ0aW9uIGZhaWxlZDogYChsZWZ0ID09IHJpZ2h0KWAKICBsZWZ0OiBgYCwKIHJpZ2h0OiBgYDogABAQAC0AAAAtEBAADAAAADkQEAADAAAAZGVzdGluYXRpb24gYW5kIHNvdXJjZSBzbGljZXMgaGF2ZSBkaWZmZXJlbnQgbGVuZ3Roc1QQEAA0AAAAZgAAAAAAAAABAAAADQAAAGNhbGxlZCBgUmVzdWx0Ojp1bndyYXAoKWAgb24gYW4gYEVycmAgdmFsdWUAZwAAAAAAAAABAAAAaAAAAGFzc2VydGlvbiBmYWlsZWQ6IHNlbGYuaXNfY2hhcl9ib3VuZGFyeShuZXdfbGVuKTw6OmNvcmU6Om1hY3Jvczo6cGFuaWMgbWFjcm9zPgAADBEQAB4AAAACAAAAAgAAAC9ydXN0Yy9jZDFlZjM5MGU3MzFlZDc3YjkwYjExYjFmNzdlMmM1Y2E2NDFiMjYxL3NyYy9saWJhbGxvYy9yYXdfdmVjLnJzADwREABHAAAAVwAAAB4AAABpbnRlcm5hbCBlcnJvcjogZW50ZXJlZCB1bnJlYWNoYWJsZSBjb2RlVHJpZWQgdG8gc2hyaW5rIHRvIGEgbGFyZ2VyIGNhcGFjaXR5AAAAAAAA8D8AAAAAAAAkQAAAAAAAAFlAAAAAAABAj0AAAAAAAIjDQAAAAAAAavhAAAAAAICELkEAAAAA0BJjQQAAAACE15dBAAAAAGXNzUEAAAAgX6ACQgAAAOh2SDdCAAAAopQabUIAAEDlnDCiQgAAkB7EvNZCAAA0JvVrDEMAgOA3ecNBQwCg2IVXNHZDAMhOZ23Bq0MAPZFg5FjhQ0CMtXgdrxVEUO/i1uQaS0SS1U0Gz/CARPZK4ccCLbVEtJ3ZeUN46kSRAigsKosgRTUDMrf0rVRFAoT+5HHZiUWBEh8v5yfARSHX5vrgMfRF6oygOVk+KUYksAiI741fRhduBbW1uJNGnMlGIuOmyEYDfNjqm9D+RoJNx3JhQjNH4yB5z/kSaEcbaVdDuBeeR7GhFirTztJHHUqc9IeCB0ilXMPxKWM9SOcZGjf6XXJIYaDgxHj1pkh5yBj21rLcSEx9z1nG7xFJnlxD8LdrRknGM1TspQZ8SVygtLMnhLFJc8ihoDHl5UmPOsoIfl4bSppkfsUOG1FKwP3ddtJhhUowfZUUR7q6Sj5u3WxstPBKzskUiIfhJEtB/Blq6RlaS6k9UOIxUJBLE03kWj5kxEtXYJ3xTX35S224BG6h3C9MRPPC5OTpY0wVsPMdXuSYTBuccKV1Hc9MkWFmh2lyA031+T/pA084TXL4j+PEYm5NR/s5Drv9ok0ZesjRKb3XTZ+YOkZ0rA1OZJ/kq8iLQk49x93Wui53Tgw5lYxp+qxOp0Pd94Ec4k6RlNR1oqMWT7W5SROLTExPERQO7NavgU8WmRGnzBu2T1v/1dC/outPmb+F4rdFIVB/LyfbJZdVUF/78FHv/IpQG502kxXewFBiRAT4mhX1UHtVBbYBWypRbVXDEeF4YFHIKjRWGZeUUXo1wavfvMlRbMFYywsWAFLH8S6+jhs0Ujmuum1yImlSx1kpCQ9rn1Id2Lll6aLTUiROKL+jiwhTrWHyroyuPlMMfVftFy1zU09crehd+KdTY7PYYnX23VMecMddCboSVCVMObWLaEdULp+Hoq5CfVR9w5QlrUmyVFz0+W4Y3OZUc3G4ih6THFXoRrMW89tRVaIYYNzvUoZVyh5406vnu1U/Eytky3DxVQ7YNT3+zCVWEk6DzD1AW1bLENKfJgiRVv6UxkcwSsVWPTq4Wbyc+lZmJBO49aEwV4DtFyZzymRX4Oid7w/9mVeMscL1KT7QV+9dM3O0TQRYazUAkCFhOVjFQgD0ablvWLspgDji06NYKjSgxtrI2Fg1QUh4EfsOWcEoLevqXENZ8XL4pSU0eFmtj3YPL0GuWcwZqmm96OJZP6AUxOyiF1pPyBn1p4tNWjIdMPlId4JafiR8NxsVt1qeLVsFYtrsWoL8WEN9CCJbozsvlJyKVluMCju5Qy2MW5fmxFNKnMFbPSC26FwD9ltNqOMiNIQrXDBJzpWgMmFcfNtBu0h/lVxbUhLqGt/KXHlzS9JwywBdV1DeBk3+NF1t5JVI4D1qXcSuXS2sZqBddRq1OFeA1F0SYeIGbaAJXqt8TSREBEBe1ttgLVUFdF7MErl4qgapXn9X5xZVSN9er5ZQLjWNE19bvOR5gnBIX3LrXRijjH5fJ7M67+UXs1/xXwlr393nX+23y0VX1R1g9FKfi1alUmCxJ4curE6HYJ3xKDpXIr1gApdZhHY18mDD/G8l1MImYfT7yy6Jc1xheH0/vTXIkWHWXI8sQzrGYQw0s/fTyPthhwDQeoRdMWKpAISZ5bRlYtQA5f8eIptihCDvX1P10GKl6Oo3qDIFY8+i5UVSfzpjwYWva5OPcGMyZ5tGeLOkY/5AQlhW4Nljn2gp9zUsEGTGwvN0QzdEZHizMFIURXlkVuC8ZlmWr2Q2DDbg973jZEOPQ9h1rRhlFHNUTtPYTmXsx/QQhEeDZej5MRVlGbhlYXh+Wr4f7mU9C4/41tMiZgzOsrbMiFdmj4Ff5P9qjWb5sLvu32LCZjidauqX+/ZmhkQF5X26LGfUSiOvjvRhZ4kd7FqycZZn6ySn8R4OzGcTdwhX04gBaNeUyiwI6zVoDTr9N8pla2hIRP5inh+haFrVvfuFZ9VosUqtemfBCmmvTqys4LhAaVpi19cY53Rp8TrNDd8gqmnWRKBoi1TgaQxWyEKuaRRqj2t60xmESWpzBllIIOV/agikNy0077NqCo2FOAHr6GpM8KaGwSUfazBWKPSYd1Nru2syMX9ViGuqBn/93mq+aypkb17LAvNrNT0LNn7DJ2yCDI7DXbRdbNHHOJq6kJJsxvnGQOk0x2w3uPiQIwL9bCNzmzpWITJt609CyaupZm3m45K7FlScbXDOOzWOtNFtDMKKwrEhBm6Pci0zHqo7bpln/N9SSnFuf4H7l+ecpW7fYfp9IQTbbix9vO6U4hBvdpxrKjobRW+Ugwa1CGJ6bz0SJHFFfbBvzBZtzZac5G9/XMiAvMMZcM85fdBVGlBwQ4icROsghHBUqsMVJim5cOmUNJtvc+9wEd0AwSWoI3FWFEExL5JYcWtZkf26to5x49d63jQyw3HcjRkWwv73cVPxn5ty/i1y1PZDoQe/YnKJ9JSJyW6Xcqsx+ut7Ss1yC198c41OAnPNdlvQMOI2c4FUcgS9mmxz0HTHIrbgoXMEUnmr41jWc4amV5Yc7wt0FMj23XF1QXQYenRVztJ1dJ6Y0eqBR6t0Y//CMrEM4XQ8v3N/3U8VdQuvUN/Uo0p1Z22SC2WmgHXACHdO/s+0dfHKFOL9A+p11v5MrX5CIHaMPqBYHlNUdi9OyO7lZ4l2u2F6at/Bv3YVfYyiK9nzdlqcL4t2zyh3cIP7LVQDX3cmMr2cFGKTd7B+7MOZOsh3XJ7nNEBJ/nf5whAhyO0yeLjzVCk6qWd4pTCqs4iTnXhnXkpwNXzSeAH2XMxCGwd5gjN0fxPiPHkxoKgvTA1yeT3IkjufkKZ5TXp3Csc03HlwrIpm/KAReoxXLYA7CUZ6b604YIqLe3plbCN8Njexen9HLBsEheV6Xln3IUXmGnvblzo1689Qe9I9iQLmA4V7Ro0rg99EuntMOPuxC2vwe18Gep7OhSR89ocYRkKnWXz6VM9riQiQfDgqw8arCsR8x/RzuFYN+Xz48ZBmrFAvfTuXGsBrkmN9Cj0hsAZ3mH1MjClcyJTOfbD3mTn9HAN+nHUAiDzkN34DkwCqS91tfuJbQEpPqqJ+2nLQHONU136QjwTkGyoNf7rZgm5ROkJ/KZAjyuXIdn8zdKw8H3usf6DI64XzzOF/L1VzZXJzL2N5cHJlc3MvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGVfanNvbi0xLjAuNDgvc3JjL2Vycm9yLnJzcmVjdXJzaW9uIGxpbWl0IGV4Y2VlZGVkdW5leHBlY3RlZCBlbmQgb2YgaGV4IGVzY2FwZXRyYWlsaW5nIGNoYXJhY3RlcnN0cmFpbGluZyBjb21tYWxvbmUgbGVhZGluZyBzdXJyb2dhdGUgaW4gaGV4IGVzY2FwZWtleSBtdXN0IGJlIGEgc3RyaW5nY29udHJvbCBjaGFyYWN0ZXIgKFx1MDAwMC1cdTAwMUYpIGZvdW5kIHdoaWxlIHBhcnNpbmcgYSBzdHJpbmdpbnZhbGlkIHVuaWNvZGUgY29kZSBwb2ludG51bWJlciBvdXQgb2YgcmFuZ2VpbnZhbGlkIG51bWJlcmludmFsaWQgZXNjYXBlZXhwZWN0ZWQgdmFsdWVleHBlY3RlZCBpZGVudGV4cGVjdGVkIGAsYCBvciBgfWBleHBlY3RlZCBgLGAgb3IgYF1gZXhwZWN0ZWQgYDpgRU9GIHdoaWxlIHBhcnNpbmcgYSB2YWx1ZUVPRiB3aGlsZSBwYXJzaW5nIGEgc3RyaW5nRU9GIHdoaWxlIHBhcnNpbmcgYW4gb2JqZWN0RU9GIHdoaWxlIHBhcnNpbmcgYSBsaXN0IGF0IGxpbmUgIGNvbHVtbiBFcnJvcigsIGxpbmU6ICwgY29sdW1uOiApAL4dEAAGAAAAxB0QAAgAAADMHRAACgAAANYdEAABAAAAaW52YWxpZCB0eXBlOiAsIGV4cGVjdGVkIAAAAPgdEAAOAAAABh4QAAsAAABpbnZhbGlkIHR5cGU6IG51bGwsIGV4cGVjdGVkIAAAACQeEAAdAAAAiBsQAF0AAACUAQAAGQAAAIgbEABdAAAAlwEAAAkAAACIGxAAXQAAAJ8BAAAbAAAAiBsQAF0AAACiAQAACQAAADAxMjM0NTY3ODlhYmNkZWZ1dXV1dXV1dWJ0bnVmcnV1dXV1dXV1dXV1dXV1dXV1dQAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL1VzZXJzL2N5cHJlc3MvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGVfanNvbi0xLjAuNDgvc3JjL3JlYWQucnOcHxAAXAAAAJkBAAAVAAAAnB8QAFwAAACVAQAAFQAAAJwfEABcAAAAtwEAABMAAACcHxAAXAAAAMcBAAAVAAAAnB8QAFwAAAC9AQAAGQAAAJwfEABcAAAAwQEAABkAAACcHxAAXAAAAP0BAAA7AAAAnB8QAFwAAAAXAgAAEwAAAJwfEABcAAAAKAIAAAwAAACcHxAAXAAAAC8CAAAlAAAAnB8QAFwAAAA0AgAAGQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACcHxAAXAAAAPoCAAAfAAAAnB8QAFwAAAD6AgAAPQAAAJwfEABcAAAAMwMAABcAAACcHxAAXAAAADMDAAA1AAAA////////////////////////////////////////////////////////////////AAECAwQFBgcICf////////8KCwwNDg///////////////////////////////////woLDA0OD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////y9Vc2Vycy9jeXByZXNzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL21lbW9yeV91bml0cy0wLjQuMC9zcmMvbGliLnJz6CIQAFwAAACPAAAABgAAAAAAAAAAAAAAAAAAAGF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3foIhAAXAAAAI8AAAAFAAAAAAAAAGF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvdwAAAOgiEABcAAAAFgAAABcAAAAAAAAAAAAAAAAAAABhdHRlbXB0IHRvIG11bHRpcGx5IHdpdGggb3ZlcmZsb3cAAADoIhAAXAAAACoAAAAXAAAA6CIQAFwAAAA8AAAAFwAAAOgiEABcAAAAMwAAABcAAAAvVXNlcnMvY3lwcmVzcy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy93ZWVfYWxsb2MtMC40LjUvc3JjL2xpYi5ycwAAACQkEABZAAAAngEAAA8AAAAkJBAAWQAAABkCAAAhAAAAJCQQAFkAAAAZAgAANAAAACQkEABZAAAAHAIAAAwAAAAkJBAAWQAAAB0CAAAjAAAAJCQQAFkAAAAhAgAAGwAAAGkAAAAAAAAAAQAAAGoAAABrAAAAbAAAAC9Vc2Vycy9jeXByZXNzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3dlZV9hbGxvYy0wLjQuNS9zcmMvaW1wX3dhc20zMi5yc/gkEABgAAAADAAAABMAAABvAAAADAAAAAQAAABwAAAAcQAAAHIAAABzAAAAdAAAAHUAAAB2AAAAZGVzY3JpcHRpb24oKSBpcyBkZXByZWNhdGVkOyB1c2UgRGlzcGxheWNhbm5vdCBhY2Nlc3MgYSBUaHJlYWQgTG9jYWwgU3RvcmFnZSB2YWx1ZSBkdXJpbmcgb3IgYWZ0ZXIgZGVzdHJ1Y3Rpb24vcnVzdGMvY2QxZWYzOTBlNzMxZWQ3N2I5MGIxMWIxZjc3ZTJjNWNhNjQxYjI2MS9zcmMvbGlic3RkL3RocmVhZC9sb2NhbC5yc/4lEABKAAAA7wAAAAkAAABYJhAAAAAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHkvcnVzdGMvY2QxZWYzOTBlNzMxZWQ3N2I5MGIxMWIxZjc3ZTJjNWNhNjQxYjI2MS9zcmMvbGliYWxsb2Mvc3RyaW5nLnJzAAAAlyYQAEYAAAB8CAAACQAAADEyMzQ1Njc4OUFCQ0RFRkdISktMTU5QUVJTVFVWV1hZWmFiY2RlZmdoaWprbW5vcHFyc3R1dnd4eXppbnRlcm5hbCBlcnJvcjogZW50ZXJlZCB1bnJlYWNoYWJsZSBjb2RlOiAqJxAAKgAAAFRoaXMgZnVuY3Rpb24gcmVxdWlyZXMgJ2NoZWNrJyBmZWF0dXJlAABcJxAAJgAAADw6OmNvcmU6Om1hY3Jvczo6cGFuaWMgbWFjcm9zPgAAjCcQAB4AAAAFAAAAMgAAAIwnEAAeAAAAAgAAAAIAAAAAAAAAYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvdwAAAABhdHRlbXB0IHRvIG11bHRpcGx5IHdpdGggb3ZlcmZsb3dhbHJlYWR5IGJvcnJvd2VkL3J1c3RjL2NkMWVmMzkwZTczMWVkNzdiOTBiMTFiMWY3N2UyYzVjYTY0MWIyNjEvc3JjL2xpYmNvcmUvY2VsbC5ycyEoEABDAAAAbgMAAAkAAABhbHJlYWR5IG11dGFibHkgYm9ycm93ZWQhKBAAQwAAAB4DAAAJAAAAdwAAAAAAAAABAAAADwAAAHgAAAAAAAAAAQAAAHkAAAB6AAAAAAAAAAEAAAB7AAAAfAAAAAAAAAABAAAADQAAAGNhbGxlZCBgUmVzdWx0Ojp1bndyYXAoKWAgb24gYW4gYEVycmAgdmFsdWUAfQAAABQAAAAEAAAAfgAAAGludGVybmFsIGVycm9yOiBlbnRlcmVkIHVucmVhY2hhYmxlIGNvZGUvVXNlcnMvY3lwcmVzcy8uY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9uZWFyLXNkay0wLjkuMi9zcmMvY29sbGVjdGlvbnMvbW9kLnJzQCkQAGQAAAA5AAAACQAAAH8AAACAAAAAAAAAAAEAAACBAAAAggAAAIMAAABCbG9ja2NoYWluIGludGVyZmFjZSBub3Qgc2V0Li9Vc2Vycy9jeXByZXNzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL25lYXItc2RrLTAuOS4yL3NyYy9lbnZpcm9ubWVudC9lbnYucnMAAADtKRAAZAAAAIEAAAAJAAAA7SkQAGQAAACLAAAACQAAAFJlZ2lzdGVyIHdhcyBleHBlY3RlZCB0byBoYXZlIGRhdGEgYmVjYXVzZSB3ZSBqdXN0IHdyb3RlIGl0IGludG8gaXQu7SkQAGQAAACZAAAAFwAAAO0pEABkAAAAmQAAAAUAAADtKRAAZAAAAKMAAAAFAAAA7SkQAGQAAACoAAAAFwAAAO0pEABkAAAAqAAAAAUAAADtKRAAZAAAAKwAAAAFAAAA7SkQAGQAAADwAAAADQAAAO0pEABkAAAAbgEAAB4AAADtKRAAZAAAAHABAAAOAAAA7SkQAGQAAABwAQAALQAAAO0pEABkAAAAcQEAAB8AAADtKRAAZAAAAHUBAAANAAAA7SkQAGQAAAB/AQAADQAAAO0pEABkAAAAiQEAAA0AAADtKRAAZAAAAJQBAAANAAAA7SkQAGQAAACeAQAADQAAAO0pEABkAAAAsgEAAA0AAADtKRAAZAAAAMQBAAANAAAA7SkQAGQAAADSAQAADQAAAO0pEABkAAAA4gEAAA0AAADtKRAAZAAAAPgBAAANAAAA7SkQAGQAAAAMAgAADQAAAO0pEABkAAAAHQIAAA0AAADtKRAAZAAAAC8CAAANAAAAVW5leHBlY3RlZCByZXR1cm4gY29kZS4A7SkQAGQAAABFAgAADgAAAFByb21pc2UgcmVzdWx0IHNob3VsZCd2ZSByZXR1cm5lZCBpbnRvIHJlZ2lzdGVyLu0pEABkAAAAQAIAABgAAADtKRAAZAAAADgCAAANAAAA7SkQAGQAAABNAgAADQAAAO0pEABkAAAAWQIAAA0AAADtKRAAZAAAAGoCAAAFAAAA7SkQAGQAAABkAgAADQAAAO0pEABkAAAAiwIAAA4AAADtKRAAZAAAAIACAAANAAAA7SkQAGQAAACbAgAADgAAAO0pEABkAAAAmgIAABMAAADtKRAAZAAAAJICAAANAAAA7SkQAGQAAACsAgAADgAAAO0pEABkAAAAowIAAA0AAABDYW5ub3QgYWRkIGFjdGlvbiB0byBhIGpvaW50IHByb21pc2UuL1VzZXJzL2N5cHJlc3MvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbmVhci1zZGstMC45LjIvc3JjL3Byb21pc2UucnMAAACFLRAAXAAAAOAAAAApAAAAQ2Fubm90IGNhbGxiYWNrIGpvaW50IHByb21pc2UuAACFLRAAXAAAAE8BAAApAAAAZWQyNTUxOXNlY3AyNTZrMVVua25vd24gY3VydmUga2luZEludmFsaWQgbGVuZ3RoIG9mIHRoZSBwdWJsaWMga2V5L3J1c3RjL2NkMWVmMzkwZTczMWVkNzdiOTBiMTFiMWY3N2UyYzVjYTY0MWIyNjEvc3JjL2xpYmNvcmUvb3BzL2FyaXRoLnJzAABmLhAASAAAALsCAAAzAAAAYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvdy9Vc2Vycy9jeXByZXNzLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2JzNTgtMC4zLjAvc3JjL2RlY29kZS5ycwDcLhAAVwAAAMwAAAANAAAAaW50ZXJuYWwgZXJyb3I6IGVudGVyZWQgdW5yZWFjaGFibGUgY29kZdwuEABXAAAATQEAACcAAABwcm92aWRlZCBzdHJpbmcgY29udGFpbmVkIG5vbi1hc2NpaSBjaGFyYWN0ZXIgc3RhcnRpbmcgYXQgYnl0ZSAAfC8QAD8AAABwcm92aWRlZCBzdHJpbmcgY29udGFpbmVkIGludmFsaWQgY2hhcmFjdGVyICBhdCBieXRlIAAAAMQvEAAsAAAA8C8QAAkAAABidWZmZXIgcHJvdmlkZWQgdG8gZGVjb2RlIGJhc2U1OCBlbmNvZGVkIHN0cmluZyBpbnRvIHdhcyB0b28gc21hbGwAAAwwEABCAAAAX19Ob25FeGhhdXN0aXZlTm9uQXNjaWlDaGFyYWN0ZXJpbmRleAAAAIUAAAAEAAAABAAAAIYAAABJbnZhbGlkQ2hhcmFjdGVyY2hhcmFjdGVyAAAAhwAAAAQAAAAEAAAAiAAAAEJ1ZmZlclRvb1NtYWxsYXNzZXJ0aW9uIGZhaWxlZDogYChsZWZ0ID09IHJpZ2h0KWAKICBsZWZ0OiBgYCwKIHJpZ2h0OiBgYDogAADKMBAALQAAAPcwEAAMAAAAAzEQAAMAAABkZXN0aW5hdGlvbiBhbmQgc291cmNlIHNsaWNlcyBoYXZlIGRpZmZlcmVudCBsZW5ndGhzIDEQADQAAAAvcnVzdGMvY2QxZWYzOTBlNzMxZWQ3N2I5MGIxMWIxZjc3ZTJjNWNhNjQxYjI2MS9zcmMvbGliY29yZS9tYWNyb3MvbW9kLnJzAAAAXDEQAEkAAAASAAAADQAAAGludGVybmFsIGVycm9yOiBlbnRlcmVkIHVucmVhY2hhYmxlIGNvZGU8Ojpjb3JlOjptYWNyb3M6OnBhbmljIG1hY3Jvcz4AAOAxEAAeAAAAAgAAAAIAAACKAAAACAAAAAQAAACLAAAAjAAAAI0AAAAIAAAABAAAAI4AAABhIHN0cmluZ2J5dGUgYXJyYXlzdHJ1Y3QgdmFyaWFudEYyEAAOAAAAdHVwbGUgdmFyaWFudAAAAFwyEAANAAAAbmV3dHlwZSB2YXJpYW50AHQyEAAPAAAAdW5pdCB2YXJpYW50jDIQAAwAAABlbnVtoDIQAAQAAABtYXAArDIQAAMAAABzZXF1ZW5jZbgyEAAIAAAAbmV3dHlwZSBzdHJ1Y3QAAMgyEAAOAAAAT3B0aW9uIHZhbHVl4DIQAAwAAAB1bml0IHZhbHVlAAD0MhAACgAAADwyEAAKAAAAc3RyaW5nIAAQMxAABwAAAGNoYXJhY3RlciBgYCAzEAALAAAAKzMQAAEAAABmbG9hdGluZyBwb2ludCBgPDMQABAAAAArMxAAAQAAAGludGVnZXIgYAAAAFwzEAAJAAAAKzMQAAEAAABib29sZWFuIGAAAAB4MxAACQAAACszEAABAAAAlgAAAAQAAAAEAAAAlwAAAJgAAACZAAAAL3J1c3RjL2NkMWVmMzkwZTczMWVkNzdiOTBiMTFiMWY3N2UyYzVjYTY0MWIyNjEvc3JjL2xpYmNvcmUvbWFjcm9zL21vZC5ycwAAAKwzEABJAAAAEgAAAA0AAABhc3NlcnRpb24gZmFpbGVkOiBgKGxlZnQgPT0gcmlnaHQpYAogIGxlZnQ6IGBgLAogcmlnaHQ6IGBgOiAINBAALQAAADU0EAAMAAAAQTQQAAMAAABkZXN0aW5hdGlvbiBhbmQgc291cmNlIHNsaWNlcyBoYXZlIGRpZmZlcmVudCBsZW5ndGhzXDQQADQAAABjYWxsZWQgYFJlc3VsdDo6dW53cmFwKClgIG9uIGFuIGBFcnJgIHZhbHVlAJoAAAAAAAAAAQAAAGgAAAA8Ojpjb3JlOjptYWNyb3M6OnBhbmljIG1hY3Jvcz4AANQ0EAAeAAAAAgAAAAIAAAAvcnVzdGMvY2QxZWYzOTBlNzMxZWQ3N2I5MGIxMWIxZjc3ZTJjNWNhNjQxYjI2MS9zcmMvbGliYWxsb2MvcmF3X3ZlYy5ycwAENRAARwAAAFcAAAAeAAAAVHJpZWQgdG8gc2hyaW5rIHRvIGEgbGFyZ2VyIGNhcGFjaXR5aW50ZXJuYWwgZXJyb3I6IGVudGVyZWQgdW5yZWFjaGFibGUgY29kZZ8AAAAEAAAABAAAAKAAAAChAAAAogAAAJ8AAAAAAAAAAQAAAKMAAABjYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlQWNjZXNzRXJyb3IAAKQAAAAMAAAABAAAAKUAAACmAAAApwAAAF4AAAClAAAAYAAAAGEAAAB1bmV4cGVjdGVkIGVuZCBvZiBmaWxlb3RoZXIgb3MgZXJyb3JvcGVyYXRpb24gaW50ZXJydXB0ZWR3cml0ZSB6ZXJvdGltZWQgb3V0aW52YWxpZCBkYXRhaW52YWxpZCBpbnB1dCBwYXJhbWV0ZXJvcGVyYXRpb24gd291bGQgYmxvY2tlbnRpdHkgYWxyZWFkeSBleGlzdHNicm9rZW4gcGlwZWFkZHJlc3Mgbm90IGF2YWlsYWJsZWFkZHJlc3MgaW4gdXNlbm90IGNvbm5lY3RlZGNvbm5lY3Rpb24gYWJvcnRlZGNvbm5lY3Rpb24gcmVzZXRjb25uZWN0aW9uIHJlZnVzZWRwZXJtaXNzaW9uIGRlbmllZGVudGl0eSBub3QgZm91bmRLaW5kAAAAnwAAAAEAAAABAAAAqAAAAE9zY29kZQAAnwAAAAQAAAAEAAAAqQAAAGtpbmRtZXNzYWdlAKQAAAAMAAAABAAAAKoAAADANRAAAAAAACAob3MgZXJyb3IgKcA1EAAAAAAArDcQAAsAAAC3NxAAAQAAAGNhbm5vdCBtb2RpZnkgdGhlIHBhbmljIGhvb2sgZnJvbSBhIHBhbmlja2luZyB0aHJlYWRzcmMvbGlic3RkL3Bhbmlja2luZy5ycwAEOBAAFwAAAHAAAAAJAAAABDgQABcAAAB6AQAADwAAAAQ4EAAXAAAAewEAAA8AAACrAAAAEAAAAAQAAACsAAAArQAAAKQAAAAMAAAABAAAAK4AAACfAAAACAAAAAQAAACvAAAAsAAAAJ8AAAAIAAAABAAAALEAAABlcnJvckN1c3RvbQCfAAAABAAAAAQAAACyAAAAnwAAAAQAAAAEAAAAswAAAFVuZXhwZWN0ZWRFb2ZPdGhlckludGVycnVwdGVkV3JpdGVaZXJvVGltZWRPdXRJbnZhbGlkRGF0YUludmFsaWRJbnB1dFdvdWxkQmxvY2tBbHJlYWR5RXhpc3RzQnJva2VuUGlwZUFkZHJOb3RBdmFpbGFibGVBZGRySW5Vc2VOb3RDb25uZWN0ZWRDb25uZWN0aW9uQWJvcnRlZENvbm5lY3Rpb25SZXNldENvbm5lY3Rpb25SZWZ1c2VkUGVybWlzc2lvbkRlbmllZE5vdEZvdW5kb3BlcmF0aW9uIHN1Y2Nlc3NmdWy0AAAABAAAAAQAAAC1AAAAc3JjL2xpYmFsbG9jL3Jhd192ZWMucnNjYXBhY2l0eSBvdmVyZmxvd7g5EAAXAAAA6gIAAAUAAABGcm9tVXRmOEVycm9yYnl0ZXMAALQAAAAEAAAABAAAALYAAABlcnJvcgAAALQAAAAEAAAABAAAALcAAAAwYXNzZXJ0aW9uIGZhaWxlZDogZWRlbHRhID49IDBzcmMvbGliY29yZS9udW0vZGl5X2Zsb2F0LnJzAABKOhAAHAAAAEwAAAAJAAAAYXNzZXJ0aW9uIGZhaWxlZDogYChsZWZ0ID09IHJpZ2h0KWAKICBsZWZ0OiBgYCwKIHJpZ2h0OiBgYAAAeDoQAC0AAAClOhAADAAAALE6EAABAAAASjoQABwAAABOAAAACQAAAAEAAAAKAAAAZAAAAOgDAAAQJwAAoIYBAEBCDwCAlpgAAOH1BQDKmjsCAAAAFAAAAMgAAADQBwAAIE4AAEANAwCAhB4AAC0xAQDC6wsAlDV3AADBb/KGIwAAAAAAge+shVtBbS3uBAAAAAAAAAAAAAABH2q/ZO04bu2Xp9r0+T/pA08YAAAAAAAAAAAAAAAAAAAAAAABPpUuCZnfA/04FQ8v5HQj7PXP0wjcBMTasM28GX8zpgMmH+lOAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABfC6YW4fTvnKf2diHLxUSxlDea3BuSs8P2JXVbnGyJrBmxq0kNhUdWtNCPA5U/2PAc1XMF+/5ZfIovFX3x9yA3O1u9M7v3F/3UwUAc3JjL2xpYmNvcmUvbnVtL2ZsdDJkZWMvc3RyYXRlZ3kvZHJhZ29uLnJzYXNzZXJ0aW9uIGZhaWxlZDogZC5tYW50ID4gMAAABDwQACoAAABxAAAABQAAAGFzc2VydGlvbiBmYWlsZWQ6IGQubWludXMgPiAwAAAABDwQACoAAAByAAAABQAAAGFzc2VydGlvbiBmYWlsZWQ6IGQucGx1cyA+IDAEPBAAKgAAAHMAAAAFAAAAYXNzZXJ0aW9uIGZhaWxlZDogZC5tYW50LmNoZWNrZWRfYWRkKGQucGx1cykuaXNfc29tZSgpAAAEPBAAKgAAAHQAAAAFAAAAYXNzZXJ0aW9uIGZhaWxlZDogZC5tYW50LmNoZWNrZWRfc3ViKGQubWludXMpLmlzX3NvbWUoKQAEPBAAKgAAAHUAAAAFAAAAYXNzZXJ0aW9uIGZhaWxlZDogYnVmLmxlbigpID49IE1BWF9TSUdfRElHSVRTAAAABDwQACoAAAB2AAAABQAAAAQ8EAAqAAAAvQAAAAkAAAAEPBAAKgAAAPUAAAANAAAABDwQACoAAAAAAQAABQAAAAQ8EAAqAAAAAQEAAAUAAAAEPBAAKgAAAAIBAAAFAAAABDwQACoAAAADAQAABQAAAAQ8EAAqAAAABAEAAAUAAAAEPBAAKgAAAFoBAAANAAAABDwQACoAAABkAQAANgAAAN9FGj0DzxrmwfvM/gAAAADKxprHF/5wq9z71P4AAAAAT9y8vvyxd//2+9z+AAAAAAzWa0HvkVa+Efzk/gAAAAA8/H+QrR/QjSz87P4AAAAAg5pVMShcUdNG/PT+AAAAALXJpq2PrHGdYfz8/gAAAADLi+4jdyKc6nv8BP8AAAAAbVN4QJFJzK6W/Az/AAAAAFfOtl15EjyCsfwU/wAAAAA3VvtNNpQQwsv8HP8AAAAAT5hIOG/qlpDm/CT/AAAAAMc6giXLhXTXAP0s/wAAAAD0l7+Xzc+GoBv9NP8AAAAA5awqF5gKNO81/Tz/AAAAAI6yNSr7ZziyUP1E/wAAAAA7P8bS39TIhGv9TP8AAAAAus3TGidE3cWF/VT/AAAAAJbJJbvOn2uToP1c/wAAAACEpWJ9JGys27r9ZP8AAAAA9tpfDVhmq6PV/Wz/AAAAACbxw96T+OLz7/10/wAAAAC4gP+qqK21tQr+fP8AAAAAi0p8bAVfYocl/oT/AAAAAFMwwTRg/7zJP/6M/wAAAABVJrqRjIVOllr+lP8AAAAAvX4pcCR3+d90/pz/AAAAAI+45bifvd+mj/6k/wAAAACUfXSIz1+p+Kn+rP8AAAAAz5uoj5NwRLnE/rT/AAAAAGsVD7/48AiK3/68/wAAAAC2MTFlVSWwzfn+xP8AAAAArH970MbiP5kU/8z/AAAAAAY7KyrEEFzkLv/U/wAAAADTknNpmSQkqkn/3P8AAAAADsoAg/K1h/1j/+T/AAAAAOsaEZJkCOW8fv/s/wAAAADMiFBvCcy8jJn/9P8AAAAALGUZ4lgXt9Gz//z/AAAAAAAAAAAAAECczv8EAAAAAAAAAAAAEKXU6Oj/DAAAAAAAAABirMXreK0DABQAAAAAAIQJlPh4OT+BHgAcAAAAAACzFQfJe86XwDgAJAAAAAAAcFzqe84yfo9TACwAAAAAAGiA6aukONLVbQA0AAAAAABFIpoXJidPn4gAPAAAAAAAJ/vE1DGiY+2iAEQAAAAAAKityIw4Zd6wvQBMAAAAAADbZasajgjHg9gAVAAAAAAAmh1xQvkdXcTyAFwAAAAAAFjnG6YsaU2SDQFkAAAAAADqjXAaZO4B2icBbAAAAAAASnfvmpmjbaJCAXQAAAAAAIVrfbR7eAnyXAF8AAAAAAB3GN15oeRUtHcBhAAAAAAAwsWbW5KGW4aSAYwAAAAAAD1dlsjFUzXIrAGUAAAAAACzoJf6XLQqlccBnAAAAAAA41+gmb2fRt7hAaQAAAAAACWMOds0wpul/AGsAAAAAABcn5ijcprG9hYCtAAAAAAAzr7pVFO/3LcxArwAAAAAAOJBIvIX8/yITALEAAAAAACleFzTm84gzGYCzAAAAAAA31Mhe/NaFpiBAtQAAAAAADowH5fctaDimwLcAAAAAACWs+NcU9HZqLYC5AAAAAAAPESnpNl8m/vQAuwAAAAAABBEpKdMTHa76wL0AAAAAAAanEC2746riwYD/AAAAAAALIRXphDvH9AgAwQBAAAAACkxkenlpBCbOwMMAQAAAACdDJyh+5sQ51UDFAEAAAAAKfQ7YtkgKKxwAxwBAAAAAIXPp3peS0SAiwMkAQAAAAAt3awDQOQhv6UDLAEAAAAAj/9EXi+cZ47AAzQBAAAAAEG4jJydFzPU2gM8AQAAAACpG+O0ktsZnvUDRAEAAAAA2Xffum6/lusPBEwBAAAAAHNyYy9saWJjb3JlL251bS9mbHQyZGVjL3N0cmF0ZWd5L2dyaXN1LnJzAAAAKEMQACkAAAB8AAAAFQAAAChDEAApAAAAqAAAAAUAAAAoQxAAKQAAAKkAAAAFAAAAKEMQACkAAACqAAAABQAAAChDEAApAAAAqwAAAAUAAAAoQxAAKQAAAKwAAAAFAAAAKEMQACkAAACtAAAABQAAAGFzc2VydGlvbiBmYWlsZWQ6IGQubWFudCArIGQucGx1cyA8ICgxIDw8IDYxKQAAAChDEAApAAAArgAAAAUAAAAoQxAAKQAAAAoBAAARAAAAAAAAAAAAAAAAAAAAYXR0ZW1wdCB0byBkaXZpZGUgYnkgemVybwAAAChDEAApAAAADQEAAAkAAAAoQxAAKQAAADkBAAAJAAAAYXNzZXJ0aW9uIGZhaWxlZDogIWJ1Zi5pc19lbXB0eSgpAAAAKEMQACkAAADTAQAABQAAAGFzc2VydGlvbiBmYWlsZWQ6IGQubWFudCA8ICgxIDw8IDYxKShDEAApAAAA1AEAAAUAAAAoQxAAKQAAANUBAAAFAAAAKEMQACkAAAAWAgAAEQAAAChDEAApAAAAGQIAAAkAAAAoQxAAKQAAAEwCAAAJAAAAc3JjL2xpYmNvcmUvbnVtL2ZsdDJkZWMvbW9kLnJzAAAERRAAHgAAAJcAAAANAAAABEUQAB4AAACZAAAAEQAAAARFEAAeAAAAnwAAAA0AAAAERRAAHgAAAKEAAAARAAAABEUQAB4AAAAfAQAABQAAAGFzc2VydGlvbiBmYWlsZWQ6IGJ1ZlswXSA+IGInMCcABEUQAB4AAAAgAQAABQAAADAuLi0raW5mTmFOYXNzZXJ0aW9uIGZhaWxlZDogYnVmLmxlbigpID49IG1heGxlbgRFEAAeAAAAzAIAAA0AAABmcm9tX3N0cl9yYWRpeF9pbnQ6IG11c3QgbGllIGluIHRoZSByYW5nZSBgWzIsIDM2XWAgLSBmb3VuZCDkRRAAPAAAAHNyYy9saWJjb3JlL251bS9tb2QucnMAAChGEAAWAAAAPhMAAAUAAABudW1iZXIgd291bGQgYmUgemVybyBmb3Igbm9uLXplcm8gdHlwZW51bWJlciB0b28gc21hbGwgdG8gZml0IGluIHRhcmdldCB0eXBlbnVtYmVyIHRvbyBsYXJnZSB0byBmaXQgaW4gdGFyZ2V0IHR5cGVpbnZhbGlkIGRpZ2l0IGZvdW5kIGluIHN0cmluZ2Nhbm5vdCBwYXJzZSBpbnRlZ2VyIGZyb20gZW1wdHkgc3RyaW5nLi4ABUcQAAIAAABCb3Jyb3dFcnJvckJvcnJvd011dEVycm9yY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZSw6EAAAAAAAOiAAACw6EAAAAAAAXEcQAAIAAAC/AAAAAAAAAAEAAADAAAAAcGFuaWNrZWQgYXQgJycsIIxHEAABAAAAjUcQAAMAAAA6AAAALDoQAAAAAACgRxAAAQAAAKBHEAABAAAAaW5kZXggb3V0IG9mIGJvdW5kczogdGhlIGxlbiBpcyAgYnV0IHRoZSBpbmRleCBpcyAAALxHEAAgAAAA3EcQABIAAAC/AAAADAAAAAQAAADBAAAAwgAAAMMAAAAgICAgIHsKLAosICB7IH0gfSgKKCwpClu/AAAABAAAAAQAAADEAAAAXTB4MDAwMTAyMDMwNDA1MDYwNzA4MDkxMDExMTIxMzE0MTUxNjE3MTgxOTIwMjEyMjIzMjQyNTI2MjcyODI5MzAzMTMyMzMzNDM1MzYzNzM4Mzk0MDQxNDI0MzQ0NDU0NjQ3NDg0OTUwNTE1MjUzNTQ1NTU2NTc1ODU5NjA2MTYyNjM2NDY1NjY2NzY4Njk3MDcxNzI3Mzc0NzU3Njc3Nzg3OTgwODE4MjgzODQ4NTg2ODc4ODg5OTA5MTkyOTM5NDk1OTY5Nzk4OTkAvwAAAAQAAAAEAAAAxQAAAMYAAADHAAAAc3JjL2xpYmNvcmUvZm10L21vZC5ycwAAJEkQABYAAABEBAAADQAAACRJEAAWAAAAUAQAACQAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwdHJ1ZWZhbHNlKClzcmMvbGliY29yZS9zbGljZS9tb2QucnNpbmRleCAgb3V0IG9mIHJhbmdlIGZvciBzbGljZSBvZiBsZW5ndGggAL9JEAAGAAAAxUkQACIAAACnSRAAGAAAAHIKAAAFAAAAc2xpY2UgaW5kZXggc3RhcnRzIGF0ICBidXQgZW5kcyBhdCAACEoQABYAAAAeShAADQAAAKdJEAAYAAAAeAoAAAUAAABzcmMvbGliY29yZS9zdHIvcGF0dGVybi5ycwAATEoQABoAAAAQBQAAFQAAAExKEAAaAAAAPgUAABUAAABMShAAGgAAAD8FAAAVAAAAc3JjL2xpYmNvcmUvc3RyL21vZC5ycwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwMDAwMDAwMDAwMDAwMDAwQEBAQEAAAAAAAAAAAAAABbLi4uXWJ5dGUgaW5kZXggIGlzIG91dCBvZiBib3VuZHMgb2YgYLNLEAALAAAAvksQABYAAACxOhAAAQAAAJhKEAAWAAAAUwgAAAkAAABiZWdpbiA8PSBlbmQgKCA8PSApIHdoZW4gc2xpY2luZyBgAAD8SxAADgAAAApMEAAEAAAADkwQABAAAACxOhAAAQAAAJhKEAAWAAAAVwgAAAUAAACYShAAFgAAAGgIAAAOAAAAIGlzIG5vdCBhIGNoYXIgYm91bmRhcnk7IGl0IGlzIGluc2lkZSAgKGJ5dGVzICkgb2YgYLNLEAALAAAAYEwQACYAAACGTBAACAAAAI5MEAAGAAAAsToQAAEAAACYShAAFgAAAGoIAAAFAAAAc3JjL2xpYmNvcmUvdW5pY29kZS9wcmludGFibGUucnPMTBAAIAAAABoAAAAoAAAAAAEDBQUGBgMHBggICREKHAsZDBQNEg4NDwQQAxISEwkWARcFGAIZAxoHHAIdAR8WIAMrBCwCLQsuATADMQIyAacCqQKqBKsI+gL7Bf0E/gP/Ca14eYuNojBXWIuMkBwd3Q4PS0z7/C4vP1xdX7XihI2OkZKpsbq7xcbJyt7k5f8ABBESKTE0Nzo7PUlKXYSOkqmxtLq7xsrOz+TlAAQNDhESKTE0OjtFRklKXmRlhJGbncnOzw0RKUVJV2RljZGptLq7xcnf5OXwBA0RRUlkZYCBhLK8vr/V1/Dxg4WLpKa+v8XHzs/a20iYvc3Gzs9JTk9XWV5fiY6Psba3v8HGx9cRFhdbXPb3/v+ADW1x3t8ODx9ubxwdX31+rq+7vPoWFx4fRkdOT1haXF5+f7XF1NXc8PH1cnOPdHWWly9fJi4vp6+3v8fP19+aQJeYMI8fwMHO/05PWlsHCA8QJy/u725vNz0/QkWQkf7/U2d1yMnQ0djZ5/7/ACBfIoLfBIJECBsEBhGBrA6AqzUeFYDgAxkIAQQvBDQEBwMBBwYHEQpQDxIHVQgCBBwKCQMIAwcDAgMDAwwEBQMLBgEOFQU6AxEHBgUQB1cHAgcVDVAEQwMtAwEEEQYPDDoEHSVfIG0EaiWAyAWCsAMaBoL9A1kHFQsXCRQMFAxqBgoGGgZZBysFRgosBAwEAQMxCywEGgYLA4CsBgoGH0FMBC0DdAg8Aw8DPAc4CCsFgv8RGAgvES0DIBAhD4CMBIKXGQsViJQFLwU7BwIOGAmAsDB0DIDWGgwFgP8FgLYFJAybxgrSMBCEjQM3CYFcFIC4CIDHMDUECgY4CEYIDAZ0Cx4DWgRZCYCDGBwKFglICICKBqukDBcEMaEEgdomBwwFBYClEYFtEHgoKgZMBICNBIC+AxsDDw0ABgEBAwEEAggICQIKBQsCEAERBBIFExEUAhUCFwIZBBwFHQgkAWoDawK8AtEC1AzVCdYC1wLaAeAF4QLoAu4g8AT5BvoCDCc7Pk5Pj56enwYHCTY9Plbz0NEEFBg2N1ZXvTXOz+ASh4mOngQNDhESKTE0OkVGSUpOT2RlWly2txscqKnY2Qk3kJGoBwo7PmZpj5JvX+7vWmKamycoVZ2goaOkp6iturzEBgsMFR06P0VRpqfMzaAHGRoiJT4/xcYEICMlJigzODpISkxQU1VWWFpcXmBjZWZrc3h9f4qkqq+wwNAMcqOky8xub14iewUDBC0DZQQBLy6Agh0DMQ8cBCQJHgUrBUQEDiqAqgYkBCQEKAg0CwGAkIE3CRYKCICYOQNjCAkwFgUhAxsFAUA4BEsFLwQKBwkHQCAnBAwJNgM6BRoHBAwHUEk3Mw0zBy4ICoEmH4CBKAgqgIYXCU4EHg9DDhkHCgZHCScJdQs/QSoGOwUKBlEGAQUQAwWAi2AgSAgKgKZeIkULCgYNEzkHCjYsBBCAwDxkUwwBgKBFG0gIUx05gQdGCh0DR0k3Aw4ICgY5BwqBNhmAxzINg5tmdQuAxIq8hC+P0YJHobmCOQcqBAJgJgpGCigFE4KwW2VLBDkHEUAEHJf4CILzpQ2BHzEDEQQIgYyJBGsFDQMJBxCTYID2CnMIbhdGgJoUDFcJGYCHgUcDhUIPFYVQK4DVLQMaBAKBcDoFAYUAgNcpTAQKBAKDEURMPYDCPAYBBFUFGzQCgQ4sBGQMVgoNA10DPTkdDSwECQcCDgaAmoPWCg0DCwV0DFkHDBQMBDgICgYoCB5SdwMxA4CmDBQEAwUDDQaFanNyYy9saWJjb3JlL3VuaWNvZGUvbW9kLnJzADFSEAAaAAAAOAAAAA8AAAAxUhAAGgAAADkAAAAQAAAAc3JjL2xpYmNvcmUvbnVtL2JpZ251bS5ycwAAAGxSEAAZAAAA4wEAAAEAAABhc3NlcnRpb24gZmFpbGVkOiBub2JvcnJvd2Fzc2VydGlvbiBmYWlsZWQ6IGRpZ2l0cyA8IDQwYXNzZXJ0aW9uIGZhaWxlZDogb3RoZXIgPiAwAAC/AAAABAAAAAQAAADIAAAAvwAAAAQAAAAEAAAAyQAAAFRyeUZyb21TbGljZUVycm9yU29tZU5vbmVFcnJvclV0ZjhFcnJvcnZhbGlkX3VwX3RvZXJyb3JfbGVuAL8AAAAEAAAABAAAAMoAAAAZDhUeHAQRFxYAABAbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEg0TAAAAAAAAAAAAAAAAAAAAAAAAAAMGCQAHCyAfGh0AAAAAABgAAAAAAAAAAAAAAAAFAgAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAoACAAUAAwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkAAAAAAAAAAAAAAAAABYvNAAAAAAAAAAAAAAAACgAqwIAAAAAAAAAAAAAAABcWIYmAAAAAAAAAAAAAABeZgYAAAAAAAAAAAAATBoAkohPK3UAAAAAAAAAAJgAADoAAAAAAAAAAAAAAAClYUsAAAAAAAAAAAAAAACAAAAAMAByAAAAAAAAAKpEAAAHAAAAAAAAAAAAAD0AAAAAAAAAABcAAAAAABwADgAAAAAAAAAAAAAAAACFAAAAAA+gLVQzTgxtAAALAAAeoVojUABFrQ1RgQAAOQAAAAAAAAAAAAAAAAAAgwBVAJQAr0kAAAAAAAAAFAQ+AHYAAAAgmpEAfFlDVhkAAAAAAAAAAAAAAAAAAAA7AACWRhiEPGR6o2MALgBCPwAAAIcAAAAAAABKAAAAAEchALJ7U3iJeWJ5p5k3AxJIlSRSAAAAAAAAAAAAAAAAaIUAbq5psaYAAAAAAACbi2sAAAAAAAAAAAAAAAAAAABvMmoAAAAAAAAArLOzcAkAcQAAAAAAADGOIh8AAAAAAHQAKo0AAAAAAAAAAAAAAACMXSV3AAAAAAAAAAAALAAAnwBlAJ4KHQAAAABbAAAAAKI4mTZ9NQAbcxV+E2yQfwioKZcFAACdJ5wBZwBBAAAAqZOCEWBXjxCKAABAfV8AALCzAACzs7NNAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAwAAAAAAAAAEAAAAAAAAAAgAAAAAAAAADQAAAAAAAAAPAAAAAAAAABwAAAAAAAAAQAAAAAAAAACwAAAAAAAAAL8AAAAAAAAA+AMAAAAAAAAABwAAAAAAAP8HAAAAAAAA8A8AAAAAAAAAEAAAAAAAAAAeAAAAAAAAACAAAAAAAAABIAAAAAAAAEA/AAAAAAAAAHgAAAAAAADAfwAAAAAAAACAAAAAAAAAwP8BAAAAAACA/wMAAAAAAAAAIAAAAAAAAAAkAAAAAAAABFwAAAAAAAAAfwAAAAAAAACjAAAAAAAAAAACAAAAAAD8fwMAAAAAAACACQAAAAAAAAAOAAAAAIAAfg4AAAAAPwD/FwAAAAAAAP8fAAAAAGQgACAAAAAAQP6PIAAAAAABAAAwAAAAAAAAAEAAAAAAXAAAQAAAAAAAAAB+AAAAAAAAAMAAAAAAAAAA4AAAAAAAAADwAAAAAAAAAPgAAAAAAID//wAAAAAAAAAAAQAAAAAA8AwBAAAAAAAAQAEAAAD/////AwAAAAAAAAALAAAAHiAAAAwAAABAMAAADAAAAB4gQAAMAAAAwT1gAAwAAAAAAABgDwAAAAAAAABgAAAARAgAAGAAAAAAgAAAYAAAAAAAAADwAAAAYAAAAAACAAB////52wcAAAAAAID4BwAAAAAA4LwPAAAAAAAAICEAAAMAAAA8OwAA5w8AAAA8AAAAAMC//z0AAAAAAADAPwAAAADA//8/AAAA+AADkHwAAAAAAAAAgAAAAAAAAADwAAAQAAD4/v8AAP//CAD//wAA////////AAAAAAAAAAABAAEAAAAAAAEAAfj//wAAAQAAAAAAwP8BAAAA/////wEA/iH+AAwAAgAAAAAAAAADAAAAAAAAgAMAAAAAAECjAwAAAAAAAAAIAAAADAAAAAwABAAAAAD4DwC2AAAAAAAQAAAAAAAAABgAAAAcAAAAHAAAAADDAQAeAAAAAAAAAB8AAQAAAMAfHwAHAAAAgO8fAP//////HyAAhjkCAAAAIwACAAAgADBYAAAAAAAAfmYAAAD8///8bQAAAAAAAAB/AAAAAAAAKL8AAAAAAADwzwD//////wcAAQAAAAAAoZABAAAAAAAA/wEAAAADAACgAgAAAAAAAAADAAD3//0hEAMAAAAAgEAABP///////zAEAAAAAACA/wYAAAAAAADABwAAAAAAAPIHIAAAAAA8PggAAAAACAAADgAAAACHAQQOAAAAAAAAABACAAAAAAAAEAYAAAAAAAAQCBAAAAAAARAHAAAAAAAAFA8AAAAAANAXAwAAAAAAABgAAAAAAADyH9/g//7///8fAAAAAAAAACAAAAAAAPg/JAMAAAAAAHgmAAAAAAAAADAHAAAAAADIMwAAAAAAAAA/AAAAAAAAsD8AAAAAAAD/P4BAAAQAAABAHiAAAAwAAEAAAAAAAIDTQAPgAOAA4ABgAAAAAADg/WYAAAAAAAAAcP4HAAAAAPh5AwAAAAAAwH8AAAAAAAD+fwAAAACAAP9/AAAAAAAAAIB/AAAAAAAAgAAAAAAAgACAAAAAAN//AoAwAAAA//8DgAAAAAAAAPiFbvAAAAAAAIcCAAAAAAAAkAAAQH/lH/ifAAAAAAAAAKAAAAAAAAD4pwAAAAAAADywAAAAAAAAfrQAAAAAAAB/vwAAAAAAgPe/AAD+/////78RAAAAAAAAwAAAAAAAAJ3BAAAAAAAAAPj///////9/+P/////////7viEAAAwAAPwAAAAAAAAA/wIAAAAAAAD/AAAAAIAD+P8AAAAAAAD8/wAAAAAAAP//AIACAAAA//8AAAAAAPD//wAAAAD/////AgAAAP////8AAAD4/////wAA+P////////////////8NEgAADAAACQ4KAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQDAAAAAAAAAAAAAAAAAAAAAAAAAAECABAACAAACwAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAABUIAAAAAAAAAAAAAAAAAAAqKz4AAAAAAAAAAAAADwoAMj46FAAAAAAAAAAAAD4AAAAAAAAAAAAAKiwEAAAAAAAAAAAAAAAAAD4BAAAAAAAAAAAAAAAAEBAAAAAAAAAAAAAAAAAAAB8APj4+AD4+Pj42GhsYAAAnDQAAAAAAAAAAAAAAAAAAMwsAAAAAAAAAAAAAAAAAADMgAAAAAAAAAAAAADMZABYTJT4+JD0+PhIMAB4xJgAdCQAiNAIAAAAAAAAAAAAuNz4RDgAAAAAAAAAAAAAAPgYqFwAAAAAAAAAAAAAAAD44ITwcOT4+Pj4wIygtLwU+Pjs+KTUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAAGAAAAAAAAAD/AQAAAAAAAP8DAAAAAAAA9w8AAAAAAAD//wAAAAAAAH8A+AAAAAAA/v//BwAAAAAAAP8fAAAAAP///z8AAAAA/////wAAAAADAAAAHwAAAP//////AwAA/////78gAAD//////z8AAP///////wcA/////x94DAD//+//////AQAAAAAABCAEfAAAAAAAAAcAAAAA/v//B/7//wf+//8H//8P/////w/cH88P/x/cH////////z8/Pz//qv///z/////////fX7/n39////97AAAAAAAAAoAgAAAAAADPvP8B///////nvyD//////+f////fZN7/64T8Lz5QvR/y/f//9/////f/////////9///f////3//////////v/8AAAAAAADA////3////9///////////v8AAAAAAAD///////f/AP///wP///8D////f////3//////////f/////3////9////////P////0DX///7////AAAAAPz///8AAAAA/////+BDAAD///////9/AP///////z8//////////3/////////f//////9f/P3///////94////////A/z////////v//////////D///////////////////8EDxUbGQMSFxEAAA4WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYTAAAAAAAAAAAAAAAAAAAAAAAAAAIHCgAIDB0cGBoAAAAAAAAAAAAAAAAAAAAAAAAFAQAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAsACQAUAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8SAAAAAAAAAAAAAAAAAB8AAAAAAAAAAAAAAAAAAABJRmYdAAAAAAAAAAAAAAAAij4AAAAAAAAAAAAAAAAAS1MAAAAAAAAAAAAAAABnI0IAAAAAAAAAAAAAAAA9AAAAAAAjAAAAAAAAAAAAdQAALQAAAAAAAAAAAAAAAIJOPAAAAAAAAAAAAAAAAGMAAAAlAFoAAAAAAAAAgTYAAAMAAAAAAAAAAAAALwAAAAAAAAAAEAAAAAAAEwAIAAAAAAAAAAAAAAAAAEMAcgCJAAAAAAAAAAAAAAcAAAB9BRg/ADeHCUBkAAAhAAAAAAAAAAAAAAAAAAoAAEEAAAAAAAAAAAAAAAAMADAAXAAAABl3cQBgRzVELgAAdDkRZSxRXn9QAAAANDEAAABTAAAAAAAAOgAAAAA4GgCIXytraV1PXYSAKmgUOwAXAAAAAAAAAAAAAAAAAFUAAFcAAACDAAAAAAAAAABZAAAAAAAAJm4bFgAAAAAAbUocAAAAAAAAAAAAACQAAHwAUgB7BhUAAAAASAAAAAB+KHYnbCkAIlsOYQ1WcGIEhSB4AgAAeh55AVQAMwAAAIZzWABNRW8LagAAMmxMAACJigAAioqKPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAADQAAAAAAAAAcAAAAAAAAAEAAAAAAAAAAtgAAAAAAAAC/AAAAAAAAAPgDAAAAAAAA8AcAAAAAAAD/BwAAAAAAAAAQAAAAAAAAAB4AAAAAAAAAOAAAAAAAAAA/AAAAAAAAgH8AAAAAAAAAgAAAAAAAAMD/AQAAAAAAgP8DAAAAAAAAgAcAAAAAAAAAfwAAAAAAASCAAAAAAAAAAKMAAAAAAAD8fwMAAAAAAAAABgAAAAAAAP8HAAAAAAAAgAkAAAAAAAAADgAAAACAAH4OAAAAAGQgACAAAAAAQP4PIAAAAAABAAAwAAAAAAAAAEAAAAAAXAAAQAAAAAAAAABgAAAAAACEXIAAAAAAAAAAwAAAAAAAAADgAAAAAAAAAAABAAAAAADwDAEAAABEMGAADAAAAME9YAAMAAAAHiCAAAwAAAAeIMAADAAAAP4h/gAMAAAAAAAAACAAAAAAAAAAYAAAAEQIAABgAAAAAAAAAPAAAABgAAAAAAIAAH////nbBwAAAAAAgPgHAAAAAADgvA8AAAAAAAAgIQAAAwAAADw7AADnDwAAADwAAAAAwJ+fPQAAAADA++8+AAAAAAAAwD8AAAAAAAAA8AAAAAAAAAD8AAAQAAD4/v8AAP//AAD//wAA////////AAAA+P//AAABAAAAAADA/wEAAAD/////AQAAAAAAAAADAAAAAAAAgAMAAAAAAECjAwAAAAAAAAAIAAAADAAAAAwABAAAAAD4DwAAAAAAAAAYAAAAHAAAABwAAAAAwwEAHgAAAAAAAAAfAAEAgADAHx8ABwAAAIDvHwD//////x8gAIY5AgAAACMAAgAAAAAwQAAAAAAAAH5mAAAA/P///G0AAAAAAAAAfwAAAAAAACi/AAAAAAAA8M8AAAAAAwAAoAIAAPf//SEQAwMAAAAAAHgGAAAAAACA/wYAAAAAAADABwAAAAAAAPIHAAAAAIcBBA4GAAAAAAAAEAgQAAAAAAAQBwAAAAAAABQPAAAAAADwFwAAAAAAAPIf3+D//v///x8AAAAAAAAAIAAAAAAA+A8gBwAAAAAAyDMAAAAAAACwPwAAAAAAgPc/BAAAAAAAAEAeIIAADAAAQAAAAAAAgNNAAgAAAAAAAFADAAAAAAAAWAAAAAAA4P1m/gcAAAAA+HkDAAAAAADAfwAAAAAAAP5/AAAAAAAA/38AAAAAAAAAgH8AAAAAAACAMAAAAP//A4Bu8AAAAAAAhwIAAAAAAACQAABAf+Uf+J8AAAAAAAD5pQAAAAAAAPinAAAAAACAPLAAAAAAAAB+tAAAAAAAAH+/AAD+/////78RAAAAAAAAwAAAAAAAAJ3BAgAAAAAAANAAAAAAoMMH+P///////3/4//////////u+IQAADAAA/AAAAAAAAAD/AgAAAAAAAP8AAAIAAAD//wAA+P/7////AAAAAP///////////////0EAAABhAAAAAAAAAAAAAABCAAAAYgAAAAAAAAAAAAAAQwAAAGMAAAAAAAAAAAAAAEQAAABkAAAAAAAAAAAAAABFAAAAZQAAAAAAAAAAAAAARgAAAGYAAAAAAAAAAAAAAEcAAABnAAAAAAAAAAAAAABIAAAAaAAAAAAAAAAAAAAASQAAAGkAAAAAAAAAAAAAAEoAAABqAAAAAAAAAAAAAABLAAAAawAAAAAAAAAAAAAATAAAAGwAAAAAAAAAAAAAAE0AAABtAAAAAAAAAAAAAABOAAAAbgAAAAAAAAAAAAAATwAAAG8AAAAAAAAAAAAAAFAAAABwAAAAAAAAAAAAAABRAAAAcQAAAAAAAAAAAAAAUgAAAHIAAAAAAAAAAAAAAFMAAABzAAAAAAAAAAAAAABUAAAAdAAAAAAAAAAAAAAAVQAAAHUAAAAAAAAAAAAAAFYAAAB2AAAAAAAAAAAAAABXAAAAdwAAAAAAAAAAAAAAWAAAAHgAAAAAAAAAAAAAAFkAAAB5AAAAAAAAAAAAAABaAAAAegAAAAAAAAAAAAAAwAAAAOAAAAAAAAAAAAAAAMEAAADhAAAAAAAAAAAAAADCAAAA4gAAAAAAAAAAAAAAwwAAAOMAAAAAAAAAAAAAAMQAAADkAAAAAAAAAAAAAADFAAAA5QAAAAAAAAAAAAAAxgAAAOYAAAAAAAAAAAAAAMcAAADnAAAAAAAAAAAAAADIAAAA6AAAAAAAAAAAAAAAyQAAAOkAAAAAAAAAAAAAAMoAAADqAAAAAAAAAAAAAADLAAAA6wAAAAAAAAAAAAAAzAAAAOwAAAAAAAAAAAAAAM0AAADtAAAAAAAAAAAAAADOAAAA7gAAAAAAAAAAAAAAzwAAAO8AAAAAAAAAAAAAANAAAADwAAAAAAAAAAAAAADRAAAA8QAAAAAAAAAAAAAA0gAAAPIAAAAAAAAAAAAAANMAAADzAAAAAAAAAAAAAADUAAAA9AAAAAAAAAAAAAAA1QAAAPUAAAAAAAAAAAAAANYAAAD2AAAAAAAAAAAAAADYAAAA+AAAAAAAAAAAAAAA2QAAAPkAAAAAAAAAAAAAANoAAAD6AAAAAAAAAAAAAADbAAAA+wAAAAAAAAAAAAAA3AAAAPwAAAAAAAAAAAAAAN0AAAD9AAAAAAAAAAAAAADeAAAA/gAAAAAAAAAAAAAAAAEAAAEBAAAAAAAAAAAAAAIBAAADAQAAAAAAAAAAAAAEAQAABQEAAAAAAAAAAAAABgEAAAcBAAAAAAAAAAAAAAgBAAAJAQAAAAAAAAAAAAAKAQAACwEAAAAAAAAAAAAADAEAAA0BAAAAAAAAAAAAAA4BAAAPAQAAAAAAAAAAAAAQAQAAEQEAAAAAAAAAAAAAEgEAABMBAAAAAAAAAAAAABQBAAAVAQAAAAAAAAAAAAAWAQAAFwEAAAAAAAAAAAAAGAEAABkBAAAAAAAAAAAAABoBAAAbAQAAAAAAAAAAAAAcAQAAHQEAAAAAAAAAAAAAHgEAAB8BAAAAAAAAAAAAACABAAAhAQAAAAAAAAAAAAAiAQAAIwEAAAAAAAAAAAAAJAEAACUBAAAAAAAAAAAAACYBAAAnAQAAAAAAAAAAAAAoAQAAKQEAAAAAAAAAAAAAKgEAACsBAAAAAAAAAAAAACwBAAAtAQAAAAAAAAAAAAAuAQAALwEAAAAAAAAAAAAAMAEAAGkAAAAHAwAAAAAAADIBAAAzAQAAAAAAAAAAAAA0AQAANQEAAAAAAAAAAAAANgEAADcBAAAAAAAAAAAAADkBAAA6AQAAAAAAAAAAAAA7AQAAPAEAAAAAAAAAAAAAPQEAAD4BAAAAAAAAAAAAAD8BAABAAQAAAAAAAAAAAABBAQAAQgEAAAAAAAAAAAAAQwEAAEQBAAAAAAAAAAAAAEUBAABGAQAAAAAAAAAAAABHAQAASAEAAAAAAAAAAAAASgEAAEsBAAAAAAAAAAAAAEwBAABNAQAAAAAAAAAAAABOAQAATwEAAAAAAAAAAAAAUAEAAFEBAAAAAAAAAAAAAFIBAABTAQAAAAAAAAAAAABUAQAAVQEAAAAAAAAAAAAAVgEAAFcBAAAAAAAAAAAAAFgBAABZAQAAAAAAAAAAAABaAQAAWwEAAAAAAAAAAAAAXAEAAF0BAAAAAAAAAAAAAF4BAABfAQAAAAAAAAAAAABgAQAAYQEAAAAAAAAAAAAAYgEAAGMBAAAAAAAAAAAAAGQBAABlAQAAAAAAAAAAAABmAQAAZwEAAAAAAAAAAAAAaAEAAGkBAAAAAAAAAAAAAGoBAABrAQAAAAAAAAAAAABsAQAAbQEAAAAAAAAAAAAAbgEAAG8BAAAAAAAAAAAAAHABAABxAQAAAAAAAAAAAAByAQAAcwEAAAAAAAAAAAAAdAEAAHUBAAAAAAAAAAAAAHYBAAB3AQAAAAAAAAAAAAB4AQAA/wAAAAAAAAAAAAAAeQEAAHoBAAAAAAAAAAAAAHsBAAB8AQAAAAAAAAAAAAB9AQAAfgEAAAAAAAAAAAAAgQEAAFMCAAAAAAAAAAAAAIIBAACDAQAAAAAAAAAAAACEAQAAhQEAAAAAAAAAAAAAhgEAAFQCAAAAAAAAAAAAAIcBAACIAQAAAAAAAAAAAACJAQAAVgIAAAAAAAAAAAAAigEAAFcCAAAAAAAAAAAAAIsBAACMAQAAAAAAAAAAAACOAQAA3QEAAAAAAAAAAAAAjwEAAFkCAAAAAAAAAAAAAJABAABbAgAAAAAAAAAAAACRAQAAkgEAAAAAAAAAAAAAkwEAAGACAAAAAAAAAAAAAJQBAABjAgAAAAAAAAAAAACWAQAAaQIAAAAAAAAAAAAAlwEAAGgCAAAAAAAAAAAAAJgBAACZAQAAAAAAAAAAAACcAQAAbwIAAAAAAAAAAAAAnQEAAHICAAAAAAAAAAAAAJ8BAAB1AgAAAAAAAAAAAACgAQAAoQEAAAAAAAAAAAAAogEAAKMBAAAAAAAAAAAAAKQBAAClAQAAAAAAAAAAAACmAQAAgAIAAAAAAAAAAAAApwEAAKgBAAAAAAAAAAAAAKkBAACDAgAAAAAAAAAAAACsAQAArQEAAAAAAAAAAAAArgEAAIgCAAAAAAAAAAAAAK8BAACwAQAAAAAAAAAAAACxAQAAigIAAAAAAAAAAAAAsgEAAIsCAAAAAAAAAAAAALMBAAC0AQAAAAAAAAAAAAC1AQAAtgEAAAAAAAAAAAAAtwEAAJICAAAAAAAAAAAAALgBAAC5AQAAAAAAAAAAAAC8AQAAvQEAAAAAAAAAAAAAxAEAAMYBAAAAAAAAAAAAAMUBAADGAQAAAAAAAAAAAADHAQAAyQEAAAAAAAAAAAAAyAEAAMkBAAAAAAAAAAAAAMoBAADMAQAAAAAAAAAAAADLAQAAzAEAAAAAAAAAAAAAzQEAAM4BAAAAAAAAAAAAAM8BAADQAQAAAAAAAAAAAADRAQAA0gEAAAAAAAAAAAAA0wEAANQBAAAAAAAAAAAAANUBAADWAQAAAAAAAAAAAADXAQAA2AEAAAAAAAAAAAAA2QEAANoBAAAAAAAAAAAAANsBAADcAQAAAAAAAAAAAADeAQAA3wEAAAAAAAAAAAAA4AEAAOEBAAAAAAAAAAAAAOIBAADjAQAAAAAAAAAAAADkAQAA5QEAAAAAAAAAAAAA5gEAAOcBAAAAAAAAAAAAAOgBAADpAQAAAAAAAAAAAADqAQAA6wEAAAAAAAAAAAAA7AEAAO0BAAAAAAAAAAAAAO4BAADvAQAAAAAAAAAAAADxAQAA8wEAAAAAAAAAAAAA8gEAAPMBAAAAAAAAAAAAAPQBAAD1AQAAAAAAAAAAAAD2AQAAlQEAAAAAAAAAAAAA9wEAAL8BAAAAAAAAAAAAAPgBAAD5AQAAAAAAAAAAAAD6AQAA+wEAAAAAAAAAAAAA/AEAAP0BAAAAAAAAAAAAAP4BAAD/AQAAAAAAAAAAAAAAAgAAAQIAAAAAAAAAAAAAAgIAAAMCAAAAAAAAAAAAAAQCAAAFAgAAAAAAAAAAAAAGAgAABwIAAAAAAAAAAAAACAIAAAkCAAAAAAAAAAAAAAoCAAALAgAAAAAAAAAAAAAMAgAADQIAAAAAAAAAAAAADgIAAA8CAAAAAAAAAAAAABACAAARAgAAAAAAAAAAAAASAgAAEwIAAAAAAAAAAAAAFAIAABUCAAAAAAAAAAAAABYCAAAXAgAAAAAAAAAAAAAYAgAAGQIAAAAAAAAAAAAAGgIAABsCAAAAAAAAAAAAABwCAAAdAgAAAAAAAAAAAAAeAgAAHwIAAAAAAAAAAAAAIAIAAJ4BAAAAAAAAAAAAACICAAAjAgAAAAAAAAAAAAAkAgAAJQIAAAAAAAAAAAAAJgIAACcCAAAAAAAAAAAAACgCAAApAgAAAAAAAAAAAAAqAgAAKwIAAAAAAAAAAAAALAIAAC0CAAAAAAAAAAAAAC4CAAAvAgAAAAAAAAAAAAAwAgAAMQIAAAAAAAAAAAAAMgIAADMCAAAAAAAAAAAAADoCAABlLAAAAAAAAAAAAAA7AgAAPAIAAAAAAAAAAAAAPQIAAJoBAAAAAAAAAAAAAD4CAABmLAAAAAAAAAAAAABBAgAAQgIAAAAAAAAAAAAAQwIAAIABAAAAAAAAAAAAAEQCAACJAgAAAAAAAAAAAABFAgAAjAIAAAAAAAAAAAAARgIAAEcCAAAAAAAAAAAAAEgCAABJAgAAAAAAAAAAAABKAgAASwIAAAAAAAAAAAAATAIAAE0CAAAAAAAAAAAAAE4CAABPAgAAAAAAAAAAAABwAwAAcQMAAAAAAAAAAAAAcgMAAHMDAAAAAAAAAAAAAHYDAAB3AwAAAAAAAAAAAAB/AwAA8wMAAAAAAAAAAAAAhgMAAKwDAAAAAAAAAAAAAIgDAACtAwAAAAAAAAAAAACJAwAArgMAAAAAAAAAAAAAigMAAK8DAAAAAAAAAAAAAIwDAADMAwAAAAAAAAAAAACOAwAAzQMAAAAAAAAAAAAAjwMAAM4DAAAAAAAAAAAAAJEDAACxAwAAAAAAAAAAAACSAwAAsgMAAAAAAAAAAAAAkwMAALMDAAAAAAAAAAAAAJQDAAC0AwAAAAAAAAAAAACVAwAAtQMAAAAAAAAAAAAAlgMAALYDAAAAAAAAAAAAAJcDAAC3AwAAAAAAAAAAAACYAwAAuAMAAAAAAAAAAAAAmQMAALkDAAAAAAAAAAAAAJoDAAC6AwAAAAAAAAAAAACbAwAAuwMAAAAAAAAAAAAAnAMAALwDAAAAAAAAAAAAAJ0DAAC9AwAAAAAAAAAAAACeAwAAvgMAAAAAAAAAAAAAnwMAAL8DAAAAAAAAAAAAAKADAADAAwAAAAAAAAAAAAChAwAAwQMAAAAAAAAAAAAAowMAAMMDAAAAAAAAAAAAAKQDAADEAwAAAAAAAAAAAAClAwAAxQMAAAAAAAAAAAAApgMAAMYDAAAAAAAAAAAAAKcDAADHAwAAAAAAAAAAAACoAwAAyAMAAAAAAAAAAAAAqQMAAMkDAAAAAAAAAAAAAKoDAADKAwAAAAAAAAAAAACrAwAAywMAAAAAAAAAAAAAzwMAANcDAAAAAAAAAAAAANgDAADZAwAAAAAAAAAAAADaAwAA2wMAAAAAAAAAAAAA3AMAAN0DAAAAAAAAAAAAAN4DAADfAwAAAAAAAAAAAADgAwAA4QMAAAAAAAAAAAAA4gMAAOMDAAAAAAAAAAAAAOQDAADlAwAAAAAAAAAAAADmAwAA5wMAAAAAAAAAAAAA6AMAAOkDAAAAAAAAAAAAAOoDAADrAwAAAAAAAAAAAADsAwAA7QMAAAAAAAAAAAAA7gMAAO8DAAAAAAAAAAAAAPQDAAC4AwAAAAAAAAAAAAD3AwAA+AMAAAAAAAAAAAAA+QMAAPIDAAAAAAAAAAAAAPoDAAD7AwAAAAAAAAAAAAD9AwAAewMAAAAAAAAAAAAA/gMAAHwDAAAAAAAAAAAAAP8DAAB9AwAAAAAAAAAAAAAABAAAUAQAAAAAAAAAAAAAAQQAAFEEAAAAAAAAAAAAAAIEAABSBAAAAAAAAAAAAAADBAAAUwQAAAAAAAAAAAAABAQAAFQEAAAAAAAAAAAAAAUEAABVBAAAAAAAAAAAAAAGBAAAVgQAAAAAAAAAAAAABwQAAFcEAAAAAAAAAAAAAAgEAABYBAAAAAAAAAAAAAAJBAAAWQQAAAAAAAAAAAAACgQAAFoEAAAAAAAAAAAAAAsEAABbBAAAAAAAAAAAAAAMBAAAXAQAAAAAAAAAAAAADQQAAF0EAAAAAAAAAAAAAA4EAABeBAAAAAAAAAAAAAAPBAAAXwQAAAAAAAAAAAAAEAQAADAEAAAAAAAAAAAAABEEAAAxBAAAAAAAAAAAAAASBAAAMgQAAAAAAAAAAAAAEwQAADMEAAAAAAAAAAAAABQEAAA0BAAAAAAAAAAAAAAVBAAANQQAAAAAAAAAAAAAFgQAADYEAAAAAAAAAAAAABcEAAA3BAAAAAAAAAAAAAAYBAAAOAQAAAAAAAAAAAAAGQQAADkEAAAAAAAAAAAAABoEAAA6BAAAAAAAAAAAAAAbBAAAOwQAAAAAAAAAAAAAHAQAADwEAAAAAAAAAAAAAB0EAAA9BAAAAAAAAAAAAAAeBAAAPgQAAAAAAAAAAAAAHwQAAD8EAAAAAAAAAAAAACAEAABABAAAAAAAAAAAAAAhBAAAQQQAAAAAAAAAAAAAIgQAAEIEAAAAAAAAAAAAACMEAABDBAAAAAAAAAAAAAAkBAAARAQAAAAAAAAAAAAAJQQAAEUEAAAAAAAAAAAAACYEAABGBAAAAAAAAAAAAAAnBAAARwQAAAAAAAAAAAAAKAQAAEgEAAAAAAAAAAAAACkEAABJBAAAAAAAAAAAAAAqBAAASgQAAAAAAAAAAAAAKwQAAEsEAAAAAAAAAAAAACwEAABMBAAAAAAAAAAAAAAtBAAATQQAAAAAAAAAAAAALgQAAE4EAAAAAAAAAAAAAC8EAABPBAAAAAAAAAAAAABgBAAAYQQAAAAAAAAAAAAAYgQAAGMEAAAAAAAAAAAAAGQEAABlBAAAAAAAAAAAAABmBAAAZwQAAAAAAAAAAAAAaAQAAGkEAAAAAAAAAAAAAGoEAABrBAAAAAAAAAAAAABsBAAAbQQAAAAAAAAAAAAAbgQAAG8EAAAAAAAAAAAAAHAEAABxBAAAAAAAAAAAAAByBAAAcwQAAAAAAAAAAAAAdAQAAHUEAAAAAAAAAAAAAHYEAAB3BAAAAAAAAAAAAAB4BAAAeQQAAAAAAAAAAAAAegQAAHsEAAAAAAAAAAAAAHwEAAB9BAAAAAAAAAAAAAB+BAAAfwQAAAAAAAAAAAAAgAQAAIEEAAAAAAAAAAAAAIoEAACLBAAAAAAAAAAAAACMBAAAjQQAAAAAAAAAAAAAjgQAAI8EAAAAAAAAAAAAAJAEAACRBAAAAAAAAAAAAACSBAAAkwQAAAAAAAAAAAAAlAQAAJUEAAAAAAAAAAAAAJYEAACXBAAAAAAAAAAAAACYBAAAmQQAAAAAAAAAAAAAmgQAAJsEAAAAAAAAAAAAAJwEAACdBAAAAAAAAAAAAACeBAAAnwQAAAAAAAAAAAAAoAQAAKEEAAAAAAAAAAAAAKIEAACjBAAAAAAAAAAAAACkBAAApQQAAAAAAAAAAAAApgQAAKcEAAAAAAAAAAAAAKgEAACpBAAAAAAAAAAAAACqBAAAqwQAAAAAAAAAAAAArAQAAK0EAAAAAAAAAAAAAK4EAACvBAAAAAAAAAAAAACwBAAAsQQAAAAAAAAAAAAAsgQAALMEAAAAAAAAAAAAALQEAAC1BAAAAAAAAAAAAAC2BAAAtwQAAAAAAAAAAAAAuAQAALkEAAAAAAAAAAAAALoEAAC7BAAAAAAAAAAAAAC8BAAAvQQAAAAAAAAAAAAAvgQAAL8EAAAAAAAAAAAAAMAEAADPBAAAAAAAAAAAAADBBAAAwgQAAAAAAAAAAAAAwwQAAMQEAAAAAAAAAAAAAMUEAADGBAAAAAAAAAAAAADHBAAAyAQAAAAAAAAAAAAAyQQAAMoEAAAAAAAAAAAAAMsEAADMBAAAAAAAAAAAAADNBAAAzgQAAAAAAAAAAAAA0AQAANEEAAAAAAAAAAAAANIEAADTBAAAAAAAAAAAAADUBAAA1QQAAAAAAAAAAAAA1gQAANcEAAAAAAAAAAAAANgEAADZBAAAAAAAAAAAAADaBAAA2wQAAAAAAAAAAAAA3AQAAN0EAAAAAAAAAAAAAN4EAADfBAAAAAAAAAAAAADgBAAA4QQAAAAAAAAAAAAA4gQAAOMEAAAAAAAAAAAAAOQEAADlBAAAAAAAAAAAAADmBAAA5wQAAAAAAAAAAAAA6AQAAOkEAAAAAAAAAAAAAOoEAADrBAAAAAAAAAAAAADsBAAA7QQAAAAAAAAAAAAA7gQAAO8EAAAAAAAAAAAAAPAEAADxBAAAAAAAAAAAAADyBAAA8wQAAAAAAAAAAAAA9AQAAPUEAAAAAAAAAAAAAPYEAAD3BAAAAAAAAAAAAAD4BAAA+QQAAAAAAAAAAAAA+gQAAPsEAAAAAAAAAAAAAPwEAAD9BAAAAAAAAAAAAAD+BAAA/wQAAAAAAAAAAAAAAAUAAAEFAAAAAAAAAAAAAAIFAAADBQAAAAAAAAAAAAAEBQAABQUAAAAAAAAAAAAABgUAAAcFAAAAAAAAAAAAAAgFAAAJBQAAAAAAAAAAAAAKBQAACwUAAAAAAAAAAAAADAUAAA0FAAAAAAAAAAAAAA4FAAAPBQAAAAAAAAAAAAAQBQAAEQUAAAAAAAAAAAAAEgUAABMFAAAAAAAAAAAAABQFAAAVBQAAAAAAAAAAAAAWBQAAFwUAAAAAAAAAAAAAGAUAABkFAAAAAAAAAAAAABoFAAAbBQAAAAAAAAAAAAAcBQAAHQUAAAAAAAAAAAAAHgUAAB8FAAAAAAAAAAAAACAFAAAhBQAAAAAAAAAAAAAiBQAAIwUAAAAAAAAAAAAAJAUAACUFAAAAAAAAAAAAACYFAAAnBQAAAAAAAAAAAAAoBQAAKQUAAAAAAAAAAAAAKgUAACsFAAAAAAAAAAAAACwFAAAtBQAAAAAAAAAAAAAuBQAALwUAAAAAAAAAAAAAMQUAAGEFAAAAAAAAAAAAADIFAABiBQAAAAAAAAAAAAAzBQAAYwUAAAAAAAAAAAAANAUAAGQFAAAAAAAAAAAAADUFAABlBQAAAAAAAAAAAAA2BQAAZgUAAAAAAAAAAAAANwUAAGcFAAAAAAAAAAAAADgFAABoBQAAAAAAAAAAAAA5BQAAaQUAAAAAAAAAAAAAOgUAAGoFAAAAAAAAAAAAADsFAABrBQAAAAAAAAAAAAA8BQAAbAUAAAAAAAAAAAAAPQUAAG0FAAAAAAAAAAAAAD4FAABuBQAAAAAAAAAAAAA/BQAAbwUAAAAAAAAAAAAAQAUAAHAFAAAAAAAAAAAAAEEFAABxBQAAAAAAAAAAAABCBQAAcgUAAAAAAAAAAAAAQwUAAHMFAAAAAAAAAAAAAEQFAAB0BQAAAAAAAAAAAABFBQAAdQUAAAAAAAAAAAAARgUAAHYFAAAAAAAAAAAAAEcFAAB3BQAAAAAAAAAAAABIBQAAeAUAAAAAAAAAAAAASQUAAHkFAAAAAAAAAAAAAEoFAAB6BQAAAAAAAAAAAABLBQAAewUAAAAAAAAAAAAATAUAAHwFAAAAAAAAAAAAAE0FAAB9BQAAAAAAAAAAAABOBQAAfgUAAAAAAAAAAAAATwUAAH8FAAAAAAAAAAAAAFAFAACABQAAAAAAAAAAAABRBQAAgQUAAAAAAAAAAAAAUgUAAIIFAAAAAAAAAAAAAFMFAACDBQAAAAAAAAAAAABUBQAAhAUAAAAAAAAAAAAAVQUAAIUFAAAAAAAAAAAAAFYFAACGBQAAAAAAAAAAAACgEAAAAC0AAAAAAAAAAAAAoRAAAAEtAAAAAAAAAAAAAKIQAAACLQAAAAAAAAAAAACjEAAAAy0AAAAAAAAAAAAApBAAAAQtAAAAAAAAAAAAAKUQAAAFLQAAAAAAAAAAAACmEAAABi0AAAAAAAAAAAAApxAAAActAAAAAAAAAAAAAKgQAAAILQAAAAAAAAAAAACpEAAACS0AAAAAAAAAAAAAqhAAAAotAAAAAAAAAAAAAKsQAAALLQAAAAAAAAAAAACsEAAADC0AAAAAAAAAAAAArRAAAA0tAAAAAAAAAAAAAK4QAAAOLQAAAAAAAAAAAACvEAAADy0AAAAAAAAAAAAAsBAAABAtAAAAAAAAAAAAALEQAAARLQAAAAAAAAAAAACyEAAAEi0AAAAAAAAAAAAAsxAAABMtAAAAAAAAAAAAALQQAAAULQAAAAAAAAAAAAC1EAAAFS0AAAAAAAAAAAAAthAAABYtAAAAAAAAAAAAALcQAAAXLQAAAAAAAAAAAAC4EAAAGC0AAAAAAAAAAAAAuRAAABktAAAAAAAAAAAAALoQAAAaLQAAAAAAAAAAAAC7EAAAGy0AAAAAAAAAAAAAvBAAABwtAAAAAAAAAAAAAL0QAAAdLQAAAAAAAAAAAAC+EAAAHi0AAAAAAAAAAAAAvxAAAB8tAAAAAAAAAAAAAMAQAAAgLQAAAAAAAAAAAADBEAAAIS0AAAAAAAAAAAAAwhAAACItAAAAAAAAAAAAAMMQAAAjLQAAAAAAAAAAAADEEAAAJC0AAAAAAAAAAAAAxRAAACUtAAAAAAAAAAAAAMcQAAAnLQAAAAAAAAAAAADNEAAALS0AAAAAAAAAAAAAoBMAAHCrAAAAAAAAAAAAAKETAABxqwAAAAAAAAAAAACiEwAAcqsAAAAAAAAAAAAAoxMAAHOrAAAAAAAAAAAAAKQTAAB0qwAAAAAAAAAAAAClEwAAdasAAAAAAAAAAAAAphMAAHarAAAAAAAAAAAAAKcTAAB3qwAAAAAAAAAAAACoEwAAeKsAAAAAAAAAAAAAqRMAAHmrAAAAAAAAAAAAAKoTAAB6qwAAAAAAAAAAAACrEwAAe6sAAAAAAAAAAAAArBMAAHyrAAAAAAAAAAAAAK0TAAB9qwAAAAAAAAAAAACuEwAAfqsAAAAAAAAAAAAArxMAAH+rAAAAAAAAAAAAALATAACAqwAAAAAAAAAAAACxEwAAgasAAAAAAAAAAAAAshMAAIKrAAAAAAAAAAAAALMTAACDqwAAAAAAAAAAAAC0EwAAhKsAAAAAAAAAAAAAtRMAAIWrAAAAAAAAAAAAALYTAACGqwAAAAAAAAAAAAC3EwAAh6sAAAAAAAAAAAAAuBMAAIirAAAAAAAAAAAAALkTAACJqwAAAAAAAAAAAAC6EwAAiqsAAAAAAAAAAAAAuxMAAIurAAAAAAAAAAAAALwTAACMqwAAAAAAAAAAAAC9EwAAjasAAAAAAAAAAAAAvhMAAI6rAAAAAAAAAAAAAL8TAACPqwAAAAAAAAAAAADAEwAAkKsAAAAAAAAAAAAAwRMAAJGrAAAAAAAAAAAAAMITAACSqwAAAAAAAAAAAADDEwAAk6sAAAAAAAAAAAAAxBMAAJSrAAAAAAAAAAAAAMUTAACVqwAAAAAAAAAAAADGEwAAlqsAAAAAAAAAAAAAxxMAAJerAAAAAAAAAAAAAMgTAACYqwAAAAAAAAAAAADJEwAAmasAAAAAAAAAAAAAyhMAAJqrAAAAAAAAAAAAAMsTAACbqwAAAAAAAAAAAADMEwAAnKsAAAAAAAAAAAAAzRMAAJ2rAAAAAAAAAAAAAM4TAACeqwAAAAAAAAAAAADPEwAAn6sAAAAAAAAAAAAA0BMAAKCrAAAAAAAAAAAAANETAAChqwAAAAAAAAAAAADSEwAAoqsAAAAAAAAAAAAA0xMAAKOrAAAAAAAAAAAAANQTAACkqwAAAAAAAAAAAADVEwAApasAAAAAAAAAAAAA1hMAAKarAAAAAAAAAAAAANcTAACnqwAAAAAAAAAAAADYEwAAqKsAAAAAAAAAAAAA2RMAAKmrAAAAAAAAAAAAANoTAACqqwAAAAAAAAAAAADbEwAAq6sAAAAAAAAAAAAA3BMAAKyrAAAAAAAAAAAAAN0TAACtqwAAAAAAAAAAAADeEwAArqsAAAAAAAAAAAAA3xMAAK+rAAAAAAAAAAAAAOATAACwqwAAAAAAAAAAAADhEwAAsasAAAAAAAAAAAAA4hMAALKrAAAAAAAAAAAAAOMTAACzqwAAAAAAAAAAAADkEwAAtKsAAAAAAAAAAAAA5RMAALWrAAAAAAAAAAAAAOYTAAC2qwAAAAAAAAAAAADnEwAAt6sAAAAAAAAAAAAA6BMAALirAAAAAAAAAAAAAOkTAAC5qwAAAAAAAAAAAADqEwAAuqsAAAAAAAAAAAAA6xMAALurAAAAAAAAAAAAAOwTAAC8qwAAAAAAAAAAAADtEwAAvasAAAAAAAAAAAAA7hMAAL6rAAAAAAAAAAAAAO8TAAC/qwAAAAAAAAAAAADwEwAA+BMAAAAAAAAAAAAA8RMAAPkTAAAAAAAAAAAAAPITAAD6EwAAAAAAAAAAAADzEwAA+xMAAAAAAAAAAAAA9BMAAPwTAAAAAAAAAAAAAPUTAAD9EwAAAAAAAAAAAACQHAAA0BAAAAAAAAAAAAAAkRwAANEQAAAAAAAAAAAAAJIcAADSEAAAAAAAAAAAAACTHAAA0xAAAAAAAAAAAAAAlBwAANQQAAAAAAAAAAAAAJUcAADVEAAAAAAAAAAAAACWHAAA1hAAAAAAAAAAAAAAlxwAANcQAAAAAAAAAAAAAJgcAADYEAAAAAAAAAAAAACZHAAA2RAAAAAAAAAAAAAAmhwAANoQAAAAAAAAAAAAAJscAADbEAAAAAAAAAAAAACcHAAA3BAAAAAAAAAAAAAAnRwAAN0QAAAAAAAAAAAAAJ4cAADeEAAAAAAAAAAAAACfHAAA3xAAAAAAAAAAAAAAoBwAAOAQAAAAAAAAAAAAAKEcAADhEAAAAAAAAAAAAACiHAAA4hAAAAAAAAAAAAAAoxwAAOMQAAAAAAAAAAAAAKQcAADkEAAAAAAAAAAAAAClHAAA5RAAAAAAAAAAAAAAphwAAOYQAAAAAAAAAAAAAKccAADnEAAAAAAAAAAAAACoHAAA6BAAAAAAAAAAAAAAqRwAAOkQAAAAAAAAAAAAAKocAADqEAAAAAAAAAAAAACrHAAA6xAAAAAAAAAAAAAArBwAAOwQAAAAAAAAAAAAAK0cAADtEAAAAAAAAAAAAACuHAAA7hAAAAAAAAAAAAAArxwAAO8QAAAAAAAAAAAAALAcAADwEAAAAAAAAAAAAACxHAAA8RAAAAAAAAAAAAAAshwAAPIQAAAAAAAAAAAAALMcAADzEAAAAAAAAAAAAAC0HAAA9BAAAAAAAAAAAAAAtRwAAPUQAAAAAAAAAAAAALYcAAD2EAAAAAAAAAAAAAC3HAAA9xAAAAAAAAAAAAAAuBwAAPgQAAAAAAAAAAAAALkcAAD5EAAAAAAAAAAAAAC6HAAA+hAAAAAAAAAAAAAAvRwAAP0QAAAAAAAAAAAAAL4cAAD+EAAAAAAAAAAAAAC/HAAA/xAAAAAAAAAAAAAAAB4AAAEeAAAAAAAAAAAAAAIeAAADHgAAAAAAAAAAAAAEHgAABR4AAAAAAAAAAAAABh4AAAceAAAAAAAAAAAAAAgeAAAJHgAAAAAAAAAAAAAKHgAACx4AAAAAAAAAAAAADB4AAA0eAAAAAAAAAAAAAA4eAAAPHgAAAAAAAAAAAAAQHgAAER4AAAAAAAAAAAAAEh4AABMeAAAAAAAAAAAAABQeAAAVHgAAAAAAAAAAAAAWHgAAFx4AAAAAAAAAAAAAGB4AABkeAAAAAAAAAAAAABoeAAAbHgAAAAAAAAAAAAAcHgAAHR4AAAAAAAAAAAAAHh4AAB8eAAAAAAAAAAAAACAeAAAhHgAAAAAAAAAAAAAiHgAAIx4AAAAAAAAAAAAAJB4AACUeAAAAAAAAAAAAACYeAAAnHgAAAAAAAAAAAAAoHgAAKR4AAAAAAAAAAAAAKh4AACseAAAAAAAAAAAAACweAAAtHgAAAAAAAAAAAAAuHgAALx4AAAAAAAAAAAAAMB4AADEeAAAAAAAAAAAAADIeAAAzHgAAAAAAAAAAAAA0HgAANR4AAAAAAAAAAAAANh4AADceAAAAAAAAAAAAADgeAAA5HgAAAAAAAAAAAAA6HgAAOx4AAAAAAAAAAAAAPB4AAD0eAAAAAAAAAAAAAD4eAAA/HgAAAAAAAAAAAABAHgAAQR4AAAAAAAAAAAAAQh4AAEMeAAAAAAAAAAAAAEQeAABFHgAAAAAAAAAAAABGHgAARx4AAAAAAAAAAAAASB4AAEkeAAAAAAAAAAAAAEoeAABLHgAAAAAAAAAAAABMHgAATR4AAAAAAAAAAAAATh4AAE8eAAAAAAAAAAAAAFAeAABRHgAAAAAAAAAAAABSHgAAUx4AAAAAAAAAAAAAVB4AAFUeAAAAAAAAAAAAAFYeAABXHgAAAAAAAAAAAABYHgAAWR4AAAAAAAAAAAAAWh4AAFseAAAAAAAAAAAAAFweAABdHgAAAAAAAAAAAABeHgAAXx4AAAAAAAAAAAAAYB4AAGEeAAAAAAAAAAAAAGIeAABjHgAAAAAAAAAAAABkHgAAZR4AAAAAAAAAAAAAZh4AAGceAAAAAAAAAAAAAGgeAABpHgAAAAAAAAAAAABqHgAAax4AAAAAAAAAAAAAbB4AAG0eAAAAAAAAAAAAAG4eAABvHgAAAAAAAAAAAABwHgAAcR4AAAAAAAAAAAAAch4AAHMeAAAAAAAAAAAAAHQeAAB1HgAAAAAAAAAAAAB2HgAAdx4AAAAAAAAAAAAAeB4AAHkeAAAAAAAAAAAAAHoeAAB7HgAAAAAAAAAAAAB8HgAAfR4AAAAAAAAAAAAAfh4AAH8eAAAAAAAAAAAAAIAeAACBHgAAAAAAAAAAAACCHgAAgx4AAAAAAAAAAAAAhB4AAIUeAAAAAAAAAAAAAIYeAACHHgAAAAAAAAAAAACIHgAAiR4AAAAAAAAAAAAAih4AAIseAAAAAAAAAAAAAIweAACNHgAAAAAAAAAAAACOHgAAjx4AAAAAAAAAAAAAkB4AAJEeAAAAAAAAAAAAAJIeAACTHgAAAAAAAAAAAACUHgAAlR4AAAAAAAAAAAAAnh4AAN8AAAAAAAAAAAAAAKAeAAChHgAAAAAAAAAAAACiHgAAox4AAAAAAAAAAAAApB4AAKUeAAAAAAAAAAAAAKYeAACnHgAAAAAAAAAAAACoHgAAqR4AAAAAAAAAAAAAqh4AAKseAAAAAAAAAAAAAKweAACtHgAAAAAAAAAAAACuHgAArx4AAAAAAAAAAAAAsB4AALEeAAAAAAAAAAAAALIeAACzHgAAAAAAAAAAAAC0HgAAtR4AAAAAAAAAAAAAth4AALceAAAAAAAAAAAAALgeAAC5HgAAAAAAAAAAAAC6HgAAux4AAAAAAAAAAAAAvB4AAL0eAAAAAAAAAAAAAL4eAAC/HgAAAAAAAAAAAADAHgAAwR4AAAAAAAAAAAAAwh4AAMMeAAAAAAAAAAAAAMQeAADFHgAAAAAAAAAAAADGHgAAxx4AAAAAAAAAAAAAyB4AAMkeAAAAAAAAAAAAAMoeAADLHgAAAAAAAAAAAADMHgAAzR4AAAAAAAAAAAAAzh4AAM8eAAAAAAAAAAAAANAeAADRHgAAAAAAAAAAAADSHgAA0x4AAAAAAAAAAAAA1B4AANUeAAAAAAAAAAAAANYeAADXHgAAAAAAAAAAAADYHgAA2R4AAAAAAAAAAAAA2h4AANseAAAAAAAAAAAAANweAADdHgAAAAAAAAAAAADeHgAA3x4AAAAAAAAAAAAA4B4AAOEeAAAAAAAAAAAAAOIeAADjHgAAAAAAAAAAAADkHgAA5R4AAAAAAAAAAAAA5h4AAOceAAAAAAAAAAAAAOgeAADpHgAAAAAAAAAAAADqHgAA6x4AAAAAAAAAAAAA7B4AAO0eAAAAAAAAAAAAAO4eAADvHgAAAAAAAAAAAADwHgAA8R4AAAAAAAAAAAAA8h4AAPMeAAAAAAAAAAAAAPQeAAD1HgAAAAAAAAAAAAD2HgAA9x4AAAAAAAAAAAAA+B4AAPkeAAAAAAAAAAAAAPoeAAD7HgAAAAAAAAAAAAD8HgAA/R4AAAAAAAAAAAAA/h4AAP8eAAAAAAAAAAAAAAgfAAAAHwAAAAAAAAAAAAAJHwAAAR8AAAAAAAAAAAAACh8AAAIfAAAAAAAAAAAAAAsfAAADHwAAAAAAAAAAAAAMHwAABB8AAAAAAAAAAAAADR8AAAUfAAAAAAAAAAAAAA4fAAAGHwAAAAAAAAAAAAAPHwAABx8AAAAAAAAAAAAAGB8AABAfAAAAAAAAAAAAABkfAAARHwAAAAAAAAAAAAAaHwAAEh8AAAAAAAAAAAAAGx8AABMfAAAAAAAAAAAAABwfAAAUHwAAAAAAAAAAAAAdHwAAFR8AAAAAAAAAAAAAKB8AACAfAAAAAAAAAAAAACkfAAAhHwAAAAAAAAAAAAAqHwAAIh8AAAAAAAAAAAAAKx8AACMfAAAAAAAAAAAAACwfAAAkHwAAAAAAAAAAAAAtHwAAJR8AAAAAAAAAAAAALh8AACYfAAAAAAAAAAAAAC8fAAAnHwAAAAAAAAAAAAA4HwAAMB8AAAAAAAAAAAAAOR8AADEfAAAAAAAAAAAAADofAAAyHwAAAAAAAAAAAAA7HwAAMx8AAAAAAAAAAAAAPB8AADQfAAAAAAAAAAAAAD0fAAA1HwAAAAAAAAAAAAA+HwAANh8AAAAAAAAAAAAAPx8AADcfAAAAAAAAAAAAAEgfAABAHwAAAAAAAAAAAABJHwAAQR8AAAAAAAAAAAAASh8AAEIfAAAAAAAAAAAAAEsfAABDHwAAAAAAAAAAAABMHwAARB8AAAAAAAAAAAAATR8AAEUfAAAAAAAAAAAAAFkfAABRHwAAAAAAAAAAAABbHwAAUx8AAAAAAAAAAAAAXR8AAFUfAAAAAAAAAAAAAF8fAABXHwAAAAAAAAAAAABoHwAAYB8AAAAAAAAAAAAAaR8AAGEfAAAAAAAAAAAAAGofAABiHwAAAAAAAAAAAABrHwAAYx8AAAAAAAAAAAAAbB8AAGQfAAAAAAAAAAAAAG0fAABlHwAAAAAAAAAAAABuHwAAZh8AAAAAAAAAAAAAbx8AAGcfAAAAAAAAAAAAAIgfAACAHwAAAAAAAAAAAACJHwAAgR8AAAAAAAAAAAAAih8AAIIfAAAAAAAAAAAAAIsfAACDHwAAAAAAAAAAAACMHwAAhB8AAAAAAAAAAAAAjR8AAIUfAAAAAAAAAAAAAI4fAACGHwAAAAAAAAAAAACPHwAAhx8AAAAAAAAAAAAAmB8AAJAfAAAAAAAAAAAAAJkfAACRHwAAAAAAAAAAAACaHwAAkh8AAAAAAAAAAAAAmx8AAJMfAAAAAAAAAAAAAJwfAACUHwAAAAAAAAAAAACdHwAAlR8AAAAAAAAAAAAAnh8AAJYfAAAAAAAAAAAAAJ8fAACXHwAAAAAAAAAAAACoHwAAoB8AAAAAAAAAAAAAqR8AAKEfAAAAAAAAAAAAAKofAACiHwAAAAAAAAAAAACrHwAAox8AAAAAAAAAAAAArB8AAKQfAAAAAAAAAAAAAK0fAAClHwAAAAAAAAAAAACuHwAAph8AAAAAAAAAAAAArx8AAKcfAAAAAAAAAAAAALgfAACwHwAAAAAAAAAAAAC5HwAAsR8AAAAAAAAAAAAAuh8AAHAfAAAAAAAAAAAAALsfAABxHwAAAAAAAAAAAAC8HwAAsx8AAAAAAAAAAAAAyB8AAHIfAAAAAAAAAAAAAMkfAABzHwAAAAAAAAAAAADKHwAAdB8AAAAAAAAAAAAAyx8AAHUfAAAAAAAAAAAAAMwfAADDHwAAAAAAAAAAAADYHwAA0B8AAAAAAAAAAAAA2R8AANEfAAAAAAAAAAAAANofAAB2HwAAAAAAAAAAAADbHwAAdx8AAAAAAAAAAAAA6B8AAOAfAAAAAAAAAAAAAOkfAADhHwAAAAAAAAAAAADqHwAAeh8AAAAAAAAAAAAA6x8AAHsfAAAAAAAAAAAAAOwfAADlHwAAAAAAAAAAAAD4HwAAeB8AAAAAAAAAAAAA+R8AAHkfAAAAAAAAAAAAAPofAAB8HwAAAAAAAAAAAAD7HwAAfR8AAAAAAAAAAAAA/B8AAPMfAAAAAAAAAAAAACYhAADJAwAAAAAAAAAAAAAqIQAAawAAAAAAAAAAAAAAKyEAAOUAAAAAAAAAAAAAADIhAABOIQAAAAAAAAAAAABgIQAAcCEAAAAAAAAAAAAAYSEAAHEhAAAAAAAAAAAAAGIhAAByIQAAAAAAAAAAAABjIQAAcyEAAAAAAAAAAAAAZCEAAHQhAAAAAAAAAAAAAGUhAAB1IQAAAAAAAAAAAABmIQAAdiEAAAAAAAAAAAAAZyEAAHchAAAAAAAAAAAAAGghAAB4IQAAAAAAAAAAAABpIQAAeSEAAAAAAAAAAAAAaiEAAHohAAAAAAAAAAAAAGshAAB7IQAAAAAAAAAAAABsIQAAfCEAAAAAAAAAAAAAbSEAAH0hAAAAAAAAAAAAAG4hAAB+IQAAAAAAAAAAAABvIQAAfyEAAAAAAAAAAAAAgyEAAIQhAAAAAAAAAAAAALYkAADQJAAAAAAAAAAAAAC3JAAA0SQAAAAAAAAAAAAAuCQAANIkAAAAAAAAAAAAALkkAADTJAAAAAAAAAAAAAC6JAAA1CQAAAAAAAAAAAAAuyQAANUkAAAAAAAAAAAAALwkAADWJAAAAAAAAAAAAAC9JAAA1yQAAAAAAAAAAAAAviQAANgkAAAAAAAAAAAAAL8kAADZJAAAAAAAAAAAAADAJAAA2iQAAAAAAAAAAAAAwSQAANskAAAAAAAAAAAAAMIkAADcJAAAAAAAAAAAAADDJAAA3SQAAAAAAAAAAAAAxCQAAN4kAAAAAAAAAAAAAMUkAADfJAAAAAAAAAAAAADGJAAA4CQAAAAAAAAAAAAAxyQAAOEkAAAAAAAAAAAAAMgkAADiJAAAAAAAAAAAAADJJAAA4yQAAAAAAAAAAAAAyiQAAOQkAAAAAAAAAAAAAMskAADlJAAAAAAAAAAAAADMJAAA5iQAAAAAAAAAAAAAzSQAAOckAAAAAAAAAAAAAM4kAADoJAAAAAAAAAAAAADPJAAA6SQAAAAAAAAAAAAAACwAADAsAAAAAAAAAAAAAAEsAAAxLAAAAAAAAAAAAAACLAAAMiwAAAAAAAAAAAAAAywAADMsAAAAAAAAAAAAAAQsAAA0LAAAAAAAAAAAAAAFLAAANSwAAAAAAAAAAAAABiwAADYsAAAAAAAAAAAAAAcsAAA3LAAAAAAAAAAAAAAILAAAOCwAAAAAAAAAAAAACSwAADksAAAAAAAAAAAAAAosAAA6LAAAAAAAAAAAAAALLAAAOywAAAAAAAAAAAAADCwAADwsAAAAAAAAAAAAAA0sAAA9LAAAAAAAAAAAAAAOLAAAPiwAAAAAAAAAAAAADywAAD8sAAAAAAAAAAAAABAsAABALAAAAAAAAAAAAAARLAAAQSwAAAAAAAAAAAAAEiwAAEIsAAAAAAAAAAAAABMsAABDLAAAAAAAAAAAAAAULAAARCwAAAAAAAAAAAAAFSwAAEUsAAAAAAAAAAAAABYsAABGLAAAAAAAAAAAAAAXLAAARywAAAAAAAAAAAAAGCwAAEgsAAAAAAAAAAAAABksAABJLAAAAAAAAAAAAAAaLAAASiwAAAAAAAAAAAAAGywAAEssAAAAAAAAAAAAABwsAABMLAAAAAAAAAAAAAAdLAAATSwAAAAAAAAAAAAAHiwAAE4sAAAAAAAAAAAAAB8sAABPLAAAAAAAAAAAAAAgLAAAUCwAAAAAAAAAAAAAISwAAFEsAAAAAAAAAAAAACIsAABSLAAAAAAAAAAAAAAjLAAAUywAAAAAAAAAAAAAJCwAAFQsAAAAAAAAAAAAACUsAABVLAAAAAAAAAAAAAAmLAAAViwAAAAAAAAAAAAAJywAAFcsAAAAAAAAAAAAACgsAABYLAAAAAAAAAAAAAApLAAAWSwAAAAAAAAAAAAAKiwAAFosAAAAAAAAAAAAACssAABbLAAAAAAAAAAAAAAsLAAAXCwAAAAAAAAAAAAALSwAAF0sAAAAAAAAAAAAAC4sAABeLAAAAAAAAAAAAABgLAAAYSwAAAAAAAAAAAAAYiwAAGsCAAAAAAAAAAAAAGMsAAB9HQAAAAAAAAAAAABkLAAAfQIAAAAAAAAAAAAAZywAAGgsAAAAAAAAAAAAAGksAABqLAAAAAAAAAAAAABrLAAAbCwAAAAAAAAAAAAAbSwAAFECAAAAAAAAAAAAAG4sAABxAgAAAAAAAAAAAABvLAAAUAIAAAAAAAAAAAAAcCwAAFICAAAAAAAAAAAAAHIsAABzLAAAAAAAAAAAAAB1LAAAdiwAAAAAAAAAAAAAfiwAAD8CAAAAAAAAAAAAAH8sAABAAgAAAAAAAAAAAACALAAAgSwAAAAAAAAAAAAAgiwAAIMsAAAAAAAAAAAAAIQsAACFLAAAAAAAAAAAAACGLAAAhywAAAAAAAAAAAAAiCwAAIksAAAAAAAAAAAAAIosAACLLAAAAAAAAAAAAACMLAAAjSwAAAAAAAAAAAAAjiwAAI8sAAAAAAAAAAAAAJAsAACRLAAAAAAAAAAAAACSLAAAkywAAAAAAAAAAAAAlCwAAJUsAAAAAAAAAAAAAJYsAACXLAAAAAAAAAAAAACYLAAAmSwAAAAAAAAAAAAAmiwAAJssAAAAAAAAAAAAAJwsAACdLAAAAAAAAAAAAACeLAAAnywAAAAAAAAAAAAAoCwAAKEsAAAAAAAAAAAAAKIsAACjLAAAAAAAAAAAAACkLAAApSwAAAAAAAAAAAAApiwAAKcsAAAAAAAAAAAAAKgsAACpLAAAAAAAAAAAAACqLAAAqywAAAAAAAAAAAAArCwAAK0sAAAAAAAAAAAAAK4sAACvLAAAAAAAAAAAAACwLAAAsSwAAAAAAAAAAAAAsiwAALMsAAAAAAAAAAAAALQsAAC1LAAAAAAAAAAAAAC2LAAAtywAAAAAAAAAAAAAuCwAALksAAAAAAAAAAAAALosAAC7LAAAAAAAAAAAAAC8LAAAvSwAAAAAAAAAAAAAviwAAL8sAAAAAAAAAAAAAMAsAADBLAAAAAAAAAAAAADCLAAAwywAAAAAAAAAAAAAxCwAAMUsAAAAAAAAAAAAAMYsAADHLAAAAAAAAAAAAADILAAAySwAAAAAAAAAAAAAyiwAAMssAAAAAAAAAAAAAMwsAADNLAAAAAAAAAAAAADOLAAAzywAAAAAAAAAAAAA0CwAANEsAAAAAAAAAAAAANIsAADTLAAAAAAAAAAAAADULAAA1SwAAAAAAAAAAAAA1iwAANcsAAAAAAAAAAAAANgsAADZLAAAAAAAAAAAAADaLAAA2ywAAAAAAAAAAAAA3CwAAN0sAAAAAAAAAAAAAN4sAADfLAAAAAAAAAAAAADgLAAA4SwAAAAAAAAAAAAA4iwAAOMsAAAAAAAAAAAAAOssAADsLAAAAAAAAAAAAADtLAAA7iwAAAAAAAAAAAAA8iwAAPMsAAAAAAAAAAAAAECmAABBpgAAAAAAAAAAAABCpgAAQ6YAAAAAAAAAAAAARKYAAEWmAAAAAAAAAAAAAEamAABHpgAAAAAAAAAAAABIpgAASaYAAAAAAAAAAAAASqYAAEumAAAAAAAAAAAAAEymAABNpgAAAAAAAAAAAABOpgAAT6YAAAAAAAAAAAAAUKYAAFGmAAAAAAAAAAAAAFKmAABTpgAAAAAAAAAAAABUpgAAVaYAAAAAAAAAAAAAVqYAAFemAAAAAAAAAAAAAFimAABZpgAAAAAAAAAAAABapgAAW6YAAAAAAAAAAAAAXKYAAF2mAAAAAAAAAAAAAF6mAABfpgAAAAAAAAAAAABgpgAAYaYAAAAAAAAAAAAAYqYAAGOmAAAAAAAAAAAAAGSmAABlpgAAAAAAAAAAAABmpgAAZ6YAAAAAAAAAAAAAaKYAAGmmAAAAAAAAAAAAAGqmAABrpgAAAAAAAAAAAABspgAAbaYAAAAAAAAAAAAAgKYAAIGmAAAAAAAAAAAAAIKmAACDpgAAAAAAAAAAAACEpgAAhaYAAAAAAAAAAAAAhqYAAIemAAAAAAAAAAAAAIimAACJpgAAAAAAAAAAAACKpgAAi6YAAAAAAAAAAAAAjKYAAI2mAAAAAAAAAAAAAI6mAACPpgAAAAAAAAAAAACQpgAAkaYAAAAAAAAAAAAAkqYAAJOmAAAAAAAAAAAAAJSmAACVpgAAAAAAAAAAAACWpgAAl6YAAAAAAAAAAAAAmKYAAJmmAAAAAAAAAAAAAJqmAACbpgAAAAAAAAAAAAAipwAAI6cAAAAAAAAAAAAAJKcAACWnAAAAAAAAAAAAACanAAAnpwAAAAAAAAAAAAAopwAAKacAAAAAAAAAAAAAKqcAACunAAAAAAAAAAAAACynAAAtpwAAAAAAAAAAAAAupwAAL6cAAAAAAAAAAAAAMqcAADOnAAAAAAAAAAAAADSnAAA1pwAAAAAAAAAAAAA2pwAAN6cAAAAAAAAAAAAAOKcAADmnAAAAAAAAAAAAADqnAAA7pwAAAAAAAAAAAAA8pwAAPacAAAAAAAAAAAAAPqcAAD+nAAAAAAAAAAAAAECnAABBpwAAAAAAAAAAAABCpwAAQ6cAAAAAAAAAAAAARKcAAEWnAAAAAAAAAAAAAEanAABHpwAAAAAAAAAAAABIpwAASacAAAAAAAAAAAAASqcAAEunAAAAAAAAAAAAAEynAABNpwAAAAAAAAAAAABOpwAAT6cAAAAAAAAAAAAAUKcAAFGnAAAAAAAAAAAAAFKnAABTpwAAAAAAAAAAAABUpwAAVacAAAAAAAAAAAAAVqcAAFenAAAAAAAAAAAAAFinAABZpwAAAAAAAAAAAABapwAAW6cAAAAAAAAAAAAAXKcAAF2nAAAAAAAAAAAAAF6nAABfpwAAAAAAAAAAAABgpwAAYacAAAAAAAAAAAAAYqcAAGOnAAAAAAAAAAAAAGSnAABlpwAAAAAAAAAAAABmpwAAZ6cAAAAAAAAAAAAAaKcAAGmnAAAAAAAAAAAAAGqnAABrpwAAAAAAAAAAAABspwAAbacAAAAAAAAAAAAAbqcAAG+nAAAAAAAAAAAAAHmnAAB6pwAAAAAAAAAAAAB7pwAAfKcAAAAAAAAAAAAAfacAAHkdAAAAAAAAAAAAAH6nAAB/pwAAAAAAAAAAAACApwAAgacAAAAAAAAAAAAAgqcAAIOnAAAAAAAAAAAAAISnAACFpwAAAAAAAAAAAACGpwAAh6cAAAAAAAAAAAAAi6cAAIynAAAAAAAAAAAAAI2nAABlAgAAAAAAAAAAAACQpwAAkacAAAAAAAAAAAAAkqcAAJOnAAAAAAAAAAAAAJanAACXpwAAAAAAAAAAAACYpwAAmacAAAAAAAAAAAAAmqcAAJunAAAAAAAAAAAAAJynAACdpwAAAAAAAAAAAACepwAAn6cAAAAAAAAAAAAAoKcAAKGnAAAAAAAAAAAAAKKnAACjpwAAAAAAAAAAAACkpwAApacAAAAAAAAAAAAApqcAAKenAAAAAAAAAAAAAKinAACppwAAAAAAAAAAAACqpwAAZgIAAAAAAAAAAAAAq6cAAFwCAAAAAAAAAAAAAKynAABhAgAAAAAAAAAAAACtpwAAbAIAAAAAAAAAAAAArqcAAGoCAAAAAAAAAAAAALCnAACeAgAAAAAAAAAAAACxpwAAhwIAAAAAAAAAAAAAsqcAAJ0CAAAAAAAAAAAAALOnAABTqwAAAAAAAAAAAAC0pwAAtacAAAAAAAAAAAAAtqcAALenAAAAAAAAAAAAALinAAC5pwAAAAAAAAAAAAC6pwAAu6cAAAAAAAAAAAAAvKcAAL2nAAAAAAAAAAAAAL6nAAC/pwAAAAAAAAAAAADCpwAAw6cAAAAAAAAAAAAAxKcAAJSnAAAAAAAAAAAAAMWnAACCAgAAAAAAAAAAAADGpwAAjh0AAAAAAAAAAAAAIf8AAEH/AAAAAAAAAAAAACL/AABC/wAAAAAAAAAAAAAj/wAAQ/8AAAAAAAAAAAAAJP8AAET/AAAAAAAAAAAAACX/AABF/wAAAAAAAAAAAAAm/wAARv8AAAAAAAAAAAAAJ/8AAEf/AAAAAAAAAAAAACj/AABI/wAAAAAAAAAAAAAp/wAASf8AAAAAAAAAAAAAKv8AAEr/AAAAAAAAAAAAACv/AABL/wAAAAAAAAAAAAAs/wAATP8AAAAAAAAAAAAALf8AAE3/AAAAAAAAAAAAAC7/AABO/wAAAAAAAAAAAAAv/wAAT/8AAAAAAAAAAAAAMP8AAFD/AAAAAAAAAAAAADH/AABR/wAAAAAAAAAAAAAy/wAAUv8AAAAAAAAAAAAAM/8AAFP/AAAAAAAAAAAAADT/AABU/wAAAAAAAAAAAAA1/wAAVf8AAAAAAAAAAAAANv8AAFb/AAAAAAAAAAAAADf/AABX/wAAAAAAAAAAAAA4/wAAWP8AAAAAAAAAAAAAOf8AAFn/AAAAAAAAAAAAADr/AABa/wAAAAAAAAAAAAAABAEAKAQBAAAAAAAAAAAAAQQBACkEAQAAAAAAAAAAAAIEAQAqBAEAAAAAAAAAAAADBAEAKwQBAAAAAAAAAAAABAQBACwEAQAAAAAAAAAAAAUEAQAtBAEAAAAAAAAAAAAGBAEALgQBAAAAAAAAAAAABwQBAC8EAQAAAAAAAAAAAAgEAQAwBAEAAAAAAAAAAAAJBAEAMQQBAAAAAAAAAAAACgQBADIEAQAAAAAAAAAAAAsEAQAzBAEAAAAAAAAAAAAMBAEANAQBAAAAAAAAAAAADQQBADUEAQAAAAAAAAAAAA4EAQA2BAEAAAAAAAAAAAAPBAEANwQBAAAAAAAAAAAAEAQBADgEAQAAAAAAAAAAABEEAQA5BAEAAAAAAAAAAAASBAEAOgQBAAAAAAAAAAAAEwQBADsEAQAAAAAAAAAAABQEAQA8BAEAAAAAAAAAAAAVBAEAPQQBAAAAAAAAAAAAFgQBAD4EAQAAAAAAAAAAABcEAQA/BAEAAAAAAAAAAAAYBAEAQAQBAAAAAAAAAAAAGQQBAEEEAQAAAAAAAAAAABoEAQBCBAEAAAAAAAAAAAAbBAEAQwQBAAAAAAAAAAAAHAQBAEQEAQAAAAAAAAAAAB0EAQBFBAEAAAAAAAAAAAAeBAEARgQBAAAAAAAAAAAAHwQBAEcEAQAAAAAAAAAAACAEAQBIBAEAAAAAAAAAAAAhBAEASQQBAAAAAAAAAAAAIgQBAEoEAQAAAAAAAAAAACMEAQBLBAEAAAAAAAAAAAAkBAEATAQBAAAAAAAAAAAAJQQBAE0EAQAAAAAAAAAAACYEAQBOBAEAAAAAAAAAAAAnBAEATwQBAAAAAAAAAAAAsAQBANgEAQAAAAAAAAAAALEEAQDZBAEAAAAAAAAAAACyBAEA2gQBAAAAAAAAAAAAswQBANsEAQAAAAAAAAAAALQEAQDcBAEAAAAAAAAAAAC1BAEA3QQBAAAAAAAAAAAAtgQBAN4EAQAAAAAAAAAAALcEAQDfBAEAAAAAAAAAAAC4BAEA4AQBAAAAAAAAAAAAuQQBAOEEAQAAAAAAAAAAALoEAQDiBAEAAAAAAAAAAAC7BAEA4wQBAAAAAAAAAAAAvAQBAOQEAQAAAAAAAAAAAL0EAQDlBAEAAAAAAAAAAAC+BAEA5gQBAAAAAAAAAAAAvwQBAOcEAQAAAAAAAAAAAMAEAQDoBAEAAAAAAAAAAADBBAEA6QQBAAAAAAAAAAAAwgQBAOoEAQAAAAAAAAAAAMMEAQDrBAEAAAAAAAAAAADEBAEA7AQBAAAAAAAAAAAAxQQBAO0EAQAAAAAAAAAAAMYEAQDuBAEAAAAAAAAAAADHBAEA7wQBAAAAAAAAAAAAyAQBAPAEAQAAAAAAAAAAAMkEAQDxBAEAAAAAAAAAAADKBAEA8gQBAAAAAAAAAAAAywQBAPMEAQAAAAAAAAAAAMwEAQD0BAEAAAAAAAAAAADNBAEA9QQBAAAAAAAAAAAAzgQBAPYEAQAAAAAAAAAAAM8EAQD3BAEAAAAAAAAAAADQBAEA+AQBAAAAAAAAAAAA0QQBAPkEAQAAAAAAAAAAANIEAQD6BAEAAAAAAAAAAADTBAEA+wQBAAAAAAAAAAAAgAwBAMAMAQAAAAAAAAAAAIEMAQDBDAEAAAAAAAAAAACCDAEAwgwBAAAAAAAAAAAAgwwBAMMMAQAAAAAAAAAAAIQMAQDEDAEAAAAAAAAAAACFDAEAxQwBAAAAAAAAAAAAhgwBAMYMAQAAAAAAAAAAAIcMAQDHDAEAAAAAAAAAAACIDAEAyAwBAAAAAAAAAAAAiQwBAMkMAQAAAAAAAAAAAIoMAQDKDAEAAAAAAAAAAACLDAEAywwBAAAAAAAAAAAAjAwBAMwMAQAAAAAAAAAAAI0MAQDNDAEAAAAAAAAAAACODAEAzgwBAAAAAAAAAAAAjwwBAM8MAQAAAAAAAAAAAJAMAQDQDAEAAAAAAAAAAACRDAEA0QwBAAAAAAAAAAAAkgwBANIMAQAAAAAAAAAAAJMMAQDTDAEAAAAAAAAAAACUDAEA1AwBAAAAAAAAAAAAlQwBANUMAQAAAAAAAAAAAJYMAQDWDAEAAAAAAAAAAACXDAEA1wwBAAAAAAAAAAAAmAwBANgMAQAAAAAAAAAAAJkMAQDZDAEAAAAAAAAAAACaDAEA2gwBAAAAAAAAAAAAmwwBANsMAQAAAAAAAAAAAJwMAQDcDAEAAAAAAAAAAACdDAEA3QwBAAAAAAAAAAAAngwBAN4MAQAAAAAAAAAAAJ8MAQDfDAEAAAAAAAAAAACgDAEA4AwBAAAAAAAAAAAAoQwBAOEMAQAAAAAAAAAAAKIMAQDiDAEAAAAAAAAAAACjDAEA4wwBAAAAAAAAAAAApAwBAOQMAQAAAAAAAAAAAKUMAQDlDAEAAAAAAAAAAACmDAEA5gwBAAAAAAAAAAAApwwBAOcMAQAAAAAAAAAAAKgMAQDoDAEAAAAAAAAAAACpDAEA6QwBAAAAAAAAAAAAqgwBAOoMAQAAAAAAAAAAAKsMAQDrDAEAAAAAAAAAAACsDAEA7AwBAAAAAAAAAAAArQwBAO0MAQAAAAAAAAAAAK4MAQDuDAEAAAAAAAAAAACvDAEA7wwBAAAAAAAAAAAAsAwBAPAMAQAAAAAAAAAAALEMAQDxDAEAAAAAAAAAAACyDAEA8gwBAAAAAAAAAAAAoBgBAMAYAQAAAAAAAAAAAKEYAQDBGAEAAAAAAAAAAACiGAEAwhgBAAAAAAAAAAAAoxgBAMMYAQAAAAAAAAAAAKQYAQDEGAEAAAAAAAAAAAClGAEAxRgBAAAAAAAAAAAAphgBAMYYAQAAAAAAAAAAAKcYAQDHGAEAAAAAAAAAAACoGAEAyBgBAAAAAAAAAAAAqRgBAMkYAQAAAAAAAAAAAKoYAQDKGAEAAAAAAAAAAACrGAEAyxgBAAAAAAAAAAAArBgBAMwYAQAAAAAAAAAAAK0YAQDNGAEAAAAAAAAAAACuGAEAzhgBAAAAAAAAAAAArxgBAM8YAQAAAAAAAAAAALAYAQDQGAEAAAAAAAAAAACxGAEA0RgBAAAAAAAAAAAAshgBANIYAQAAAAAAAAAAALMYAQDTGAEAAAAAAAAAAAC0GAEA1BgBAAAAAAAAAAAAtRgBANUYAQAAAAAAAAAAALYYAQDWGAEAAAAAAAAAAAC3GAEA1xgBAAAAAAAAAAAAuBgBANgYAQAAAAAAAAAAALkYAQDZGAEAAAAAAAAAAAC6GAEA2hgBAAAAAAAAAAAAuxgBANsYAQAAAAAAAAAAALwYAQDcGAEAAAAAAAAAAAC9GAEA3RgBAAAAAAAAAAAAvhgBAN4YAQAAAAAAAAAAAL8YAQDfGAEAAAAAAAAAAABAbgEAYG4BAAAAAAAAAAAAQW4BAGFuAQAAAAAAAAAAAEJuAQBibgEAAAAAAAAAAABDbgEAY24BAAAAAAAAAAAARG4BAGRuAQAAAAAAAAAAAEVuAQBlbgEAAAAAAAAAAABGbgEAZm4BAAAAAAAAAAAAR24BAGduAQAAAAAAAAAAAEhuAQBobgEAAAAAAAAAAABJbgEAaW4BAAAAAAAAAAAASm4BAGpuAQAAAAAAAAAAAEtuAQBrbgEAAAAAAAAAAABMbgEAbG4BAAAAAAAAAAAATW4BAG1uAQAAAAAAAAAAAE5uAQBubgEAAAAAAAAAAABPbgEAb24BAAAAAAAAAAAAUG4BAHBuAQAAAAAAAAAAAFFuAQBxbgEAAAAAAAAAAABSbgEAcm4BAAAAAAAAAAAAU24BAHNuAQAAAAAAAAAAAFRuAQB0bgEAAAAAAAAAAABVbgEAdW4BAAAAAAAAAAAAVm4BAHZuAQAAAAAAAAAAAFduAQB3bgEAAAAAAAAAAABYbgEAeG4BAAAAAAAAAAAAWW4BAHluAQAAAAAAAAAAAFpuAQB6bgEAAAAAAAAAAABbbgEAe24BAAAAAAAAAAAAXG4BAHxuAQAAAAAAAAAAAF1uAQB9bgEAAAAAAAAAAABebgEAfm4BAAAAAAAAAAAAX24BAH9uAQAAAAAAAAAAAADpAQAi6QEAAAAAAAAAAAAB6QEAI+kBAAAAAAAAAAAAAukBACTpAQAAAAAAAAAAAAPpAQAl6QEAAAAAAAAAAAAE6QEAJukBAAAAAAAAAAAABekBACfpAQAAAAAAAAAAAAbpAQAo6QEAAAAAAAAAAAAH6QEAKekBAAAAAAAAAAAACOkBACrpAQAAAAAAAAAAAAnpAQAr6QEAAAAAAAAAAAAK6QEALOkBAAAAAAAAAAAAC+kBAC3pAQAAAAAAAAAAAAzpAQAu6QEAAAAAAAAAAAAN6QEAL+kBAAAAAAAAAAAADukBADDpAQAAAAAAAAAAAA/pAQAx6QEAAAAAAAAAAAAQ6QEAMukBAAAAAAAAAAAAEekBADPpAQAAAAAAAAAAABLpAQA06QEAAAAAAAAAAAAT6QEANekBAAAAAAAAAAAAFOkBADbpAQAAAAAAAAAAABXpAQA36QEAAAAAAAAAAAAW6QEAOOkBAAAAAAAAAAAAF+kBADnpAQAAAAAAAAAAABjpAQA66QEAAAAAAAAAAAAZ6QEAO+kBAAAAAAAAAAAAGukBADzpAQAAAAAAAAAAABvpAQA96QEAAAAAAAAAAAAc6QEAPukBAAAAAAAAAAAAHekBAD/pAQAAAAAAAAAAAB7pAQBA6QEAAAAAAAAAAAAf6QEAQekBAAAAAAAAAAAAIOkBAELpAQAAAAAAAAAAACHpAQBD6QEAAAAAAAAAAABMYXlvdXRFcnJwcml2YXRlAEH4+cIACzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "Contract": { + "account_id": "pulse.near", + "code": "AGFzbQEAAAABbBNgAX8Bf2ACf38Bf2AAAGADf39/AGABfwBgA39/fwF/YAABf2ABfgF/YAJ/fwBgAX4AYAJ+fgBgAn9/AX5gAX4BfmAEf39/fwBgA39/fgBgA39+fwBgBH9/f38Bf2AFf39/f38Bf2ACf34BfwJbBgNlbnYFYWJvcnQADQNlbnYFaW5wdXQACQNlbnYMcmVnaXN0ZXJfbGVuAAwDZW52BXBhbmljAAIDZW52DXJlYWRfcmVnaXN0ZXIACgNlbnYIbG9nX3V0ZjgACgOXAZUBBAEAAAIAAgIAAAICAgICAAIAAgIAAQIDBAUBAAECAgIJDAIBCgEAAQABAAQAAQQAAQAGAAMDAQMBAQEAAQARAQUBAQgFAwMIAwEAAAUFBQUEAAUBBRAAAAEBAQEAAAQABAAABgEEAAEAAwAIAQADABIHDgAABggAAAMEAAYBAQEGAAsLCwADBw8HBwcAAQEBAAQJAgIEBAFwAAEFAwEAAQaoAjV/AUEAC38BQQALfwFBAAt/AEEgC38AQcAAC38AQeAAC38AQYABC34AQoCAgIAIC38AQaABC38AQeABC38AQaACC38BQQALfwBB4AILfwBBgAMLfwBBkAQLfgFCAAt+AUIAC34BQgALfgFCAAt+AUIAC34BQgALfgFCAAt/AUEAC38AQQgLfwBBCQt/AEEGC38BQQALfwBBIAt/AEHAAAt/AEHgAAt/AEGAAQt+AEKAgICACAt/AEGgAQt/AEHgAQt/AEGgAgt/AUEAC38AQeACC38AQYADC38AQcAEC38AQeAEC38AQYAFC38BQQALfwFBAAt/AUEAC38BQQALfwBBAAt/AUEAC38BQQALfwBBfwt/AEF/C38BQQALfwBB8B4LfwBB1CALBy4DFF9fc2V0QXJndW1lbnRzTGVuZ3RoAFYGbWVtb3J5AgAJaGVhcnRiZWF0AJkBCAKaAQkGAQBBAQsACrlmlQFTAQV/PwAhASABQRB0IQIgACACSwRAIAAgAmtB//8DakH//wNBf3NxQRB2IQMgASIEIAMiBSAEIAVKG0AAQQBIBEAgA0AAQQBIBEAACwsLIAAkAQtdAQV/IABB8P///wNLBEAACyMBQRBqIQIgAEEPakEPQX9zcSIDQRAiBCADIARLGyEFIAIgBWoQBiACQRBrIQYgBiAFNgIAIAZBATYCBCAGIAE2AgggBiAANgIMIAILBAAgAAsUACAARQRAQQBBAxAHEAghAAsgAAsZACM0QQ9qQQ9Bf3NxJAAjACQBQQAQCSQCCxQAIABFBEBBAEEEEAcQCCEACyAACwgAQQAQCyQLCwYAEAoQDAsUACAARQRAQQBBBxAHEAghAAsgAAsWACAARQRAQQBBBhAHEAghAAsgABAOCwgAQQAQDyQWCwQAEBALBAAQEQsGABANEBILBAAQEwsUACAARQRAQQBBDhAHEAghAAsgAAsIAEEAEBUkGgsUACAARQRAQQBBDxAHEAghAAsgAAsIAEEAEBckIwsGABAWEBgLDQAgAEEQaygCDEEBdgsaACABIAAQGk8EQEF/DwsgACABQQF0ai8BAAsmAEGgBUEAEBskKUHABUEAEBskKkHgBUEAEBskK0GABkEAEBskLAvUAwIGfwF+AkAgACEFIAEhBCACIQMgA0UEQAwBCyAFIAQ6AAAgBSADakEBayAEOgAAIANBAk0EQAwBCyAFQQFqIAQ6AAAgBUECaiAEOgAAIAUgA2pBAmsgBDoAACAFIANqQQNrIAQ6AAAgA0EGTQRADAELIAVBA2ogBDoAACAFIANqQQRrIAQ6AAAgA0EITQRADAELQQAgBWtBA3EhBiAFIAZqIQUgAyAGa0F8cSEDQX9B/wFuIARB/wFxbCEHIAUgBzYCACAFIANqQQRrIAc2AgAgA0EITQRADAELIAVBBGogBzYCACAFQQhqIAc2AgAgBSADakEMayAHNgIAIAUgA2pBCGsgBzYCACADQRhNBEAMAQsgBUEMaiAHNgIAIAVBEGogBzYCACAFQRRqIAc2AgAgBUEYaiAHNgIAIAUgA2pBHGsgBzYCACAFIANqQRhrIAc2AgAgBSADakEUayAHNgIAIAUgA2pBEGsgBzYCAEEYIAVBBHFqIQYgBSAGaiEFIAMgBmshAyAHrSAHrUIghoQhCQNAIANBIE8EQCAFIAk3AwAgBUEIaiAJNwMAIAVBEGogCTcDACAFQRhqIAk3AwAgA0EgayEDIAVBIGohBQwBCwsLCwMAAQuMAQEEfyABQfD///8DIAJ2SwRAQaAGQdAGQRdBOBAAAAsgASACdCIBQQAQByEDIANBACABEB0gAEUEQEEMQQIQBxAIIQALIABBADYCACAAQQA2AgQgAEEANgIIIAAiBCADIgUgBCgCACIGRwRAIAUQCCEFIAYQHgsgBTYCACAAIAM2AgQgACABNgIIIAALKgAgAAR/IAAFQRBBERAHEAgLIAFBAhAfIQAgAEEANgIMIAAgATYCDCAACx8AIABFBEBBBEEQEAcQCCEACyAAQQBBABAgNgIAIAALUAEDfyABEAghASAARQRAQQhBEhAHEAghAAsgAEEANgIAIABBADYCBCAAIgIgASIDIAIoAgAiBEcEQCADEAghAyAEEB4LIAM2AgAgARAeIAALEABBABAhJC5BACMuECIkLwsGABAcECMLBgAQGRAkCwYAIAAQAQsGACAAEAILBAAQAwsYACAABH8gAAVBDEEUEAcQCAsgAUEAEB8LCAAgACABEAQLNgAgARAIIQEgAEUEQEEMQRMQBxAIIQALIABBkAc2AgAgAEEANgIEIAAgARAINgIIIAEQHiAACwcAIAAoAggLJgAgASAAKAIITwRAQaAHQeAHQZgBQSwQAAALIAAoAgQgAWotAAALLQAgACgCBCgCBCAAKAIEKAIIECxOBEBBfw8LIAAoAgQoAgggACgCBCgCBBAtCygAIAFBCUYEf0EBBSABQQpGCwR/QQEFIAFBDUYLBH9BAQUgAUEgRgsLSgEBfyAAKAIEKAIEIAAoAgQoAggQLEhFBEBBoAhB4AhB8ABBCBAAAAsgACgCBCgCCCAAKAIEIAAoAgQoAgQiAUEBajYCBCABEC0LGQEBfwNAIAAgABAuEC8EQCAAEDAaDAELCwsUACAARQRAQQBBCxAHEAghAAsgAAsxAQF/IAFB8P///wNLBEBBoAZB0AZBNkEqEAAACyABQQAQByECIAJBACABEB0gAhAIC0sBAn8gACIBQQBBEBAzIAEoAgAQHjYCACAAQQRBAWs2AgQgACICQQBBMBAzIAIoAggQHjYCCCAAQQQ2AgwgAEEANgIQIABBADYCFAtCACAARQRAQRhBDBAHEAghAAsgAEEANgIAIABBADYCBCAAQQA2AgggAEEANgIMIABBADYCECAAQQA2AhQgABA0IAALKgAgAAR/IAAFQRBBDRAHEAgLIAFBAhAfIQAgAEEANgIMIAAgATYCDCAAC04BAn8gAAR/IAAFQQhBChAHEAgLEDIhACAAQQA2AgAgAEEANgIEIAAiAUEAEDUgASgCABAeNgIAIAAiAkEAQQAQNiACKAIEEB42AgQgAAsGAEEAEDcLBwAgACgCDAuHDwEEfwNAIAIEfyABQQNxBUEACwRAIAAiBkEBaiEAIAYgASIGQQFqIQEgBi0AADoAACACQQFrIQIMAQsLIABBA3FBAEYEQANAIAJBEE8EQCAAIAEoAgA2AgAgAEEEaiABQQRqKAIANgIAIABBCGogAUEIaigCADYCACAAQQxqIAFBDGooAgA2AgAgAUEQaiEBIABBEGohACACQRBrIQIMAQsLIAJBCHEEQCAAIAEoAgA2AgAgAEEEaiABQQRqKAIANgIAIABBCGohACABQQhqIQELIAJBBHEEQCAAIAEoAgA2AgAgAEEEaiEAIAFBBGohAQsgAkECcQRAIAAgAS8BADsBACAAQQJqIQAgAUECaiEBCyACQQFxBEAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAACw8LIAJBIE8EQAJAAkACQAJAIABBA3EhBSAFQQFGDQAgBUECRg0BIAVBA0YNAgwDCyABKAIAIQMgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgAkEDayECA0AgAkERTwRAIAFBAWooAgAhBCAAIANBGHYgBEEIdHI2AgAgAUEFaigCACEDIABBBGogBEEYdiADQQh0cjYCACABQQlqKAIAIQQgAEEIaiADQRh2IARBCHRyNgIAIAFBDWooAgAhAyAAQQxqIARBGHYgA0EIdHI2AgAgAUEQaiEBIABBEGohACACQRBrIQIMAQsLDAILIAEoAgAhAyAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAJBAmshAgNAIAJBEk8EQCABQQJqKAIAIQQgACADQRB2IARBEHRyNgIAIAFBBmooAgAhAyAAQQRqIARBEHYgA0EQdHI2AgAgAUEKaigCACEEIABBCGogA0EQdiAEQRB0cjYCACABQQ5qKAIAIQMgAEEMaiAEQRB2IANBEHRyNgIAIAFBEGohASAAQRBqIQAgAkEQayECDAELCwwBCyABKAIAIQMgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAJBAWshAgNAIAJBE08EQCABQQNqKAIAIQQgACADQQh2IARBGHRyNgIAIAFBB2ooAgAhAyAAQQRqIARBCHYgA0EYdHI2AgAgAUELaigCACEEIABBCGogA0EIdiAEQRh0cjYCACABQQ9qKAIAIQMgAEEMaiAEQQh2IANBGHRyNgIAIAFBEGohASAAQRBqIQAgAkEQayECDAELCwwACwsgAkEQcQRAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAAAsgAkEIcQRAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAALIAJBBHEEQCAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAAIAAiBUEBaiEAIAUgASIFQQFqIQEgBS0AADoAACAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAALIAJBAnEEQCAAIgVBAWohACAFIAEiBUEBaiEBIAUtAAA6AAAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAACyACQQFxBEAgACIFQQFqIQAgBSABIgVBAWohASAFLQAAOgAACwvmAgEFfwJAIAAhBSABIQQgAiEDIAUgBEYEQAwBCyAEIANqIAVNBH9BAQUgBSADaiAETQsEQCAFIAQgAxA6DAELIAUgBEkEQCAEQQdxIAVBB3FGBEADQCAFQQdxBEAgA0UEQAwFCyADQQFrIQMgBSIHQQFqIQUgByAEIgdBAWohBCAHLQAAOgAADAELCwNAIANBCE8EQCAFIAQpAwA3AwAgA0EIayEDIAVBCGohBSAEQQhqIQQMAQsLCwNAIAMEQCAFIgdBAWohBSAHIAQiB0EBaiEEIActAAA6AAAgA0EBayEDDAELCwUgBEEHcSAFQQdxRgRAA0AgBSADakEHcQRAIANFBEAMBQsgBSADQQFrIgNqIAQgA2otAAA6AAAMAQsLA0AgA0EITwRAIANBCGshAyAFIANqIAQgA2opAwA3AwAMAQsLCwNAIAMEQCAFIANBAWsiA2ogBCADai0AADoAAAwBCwsLCwvWAQEGfyAAQQBHBH8gAEEPcUUFQQALRQRAQQBBkApBK0ECEAAACyAAQRBrIQIgAigCACEDIAIoAgRBAUZFBEBBAEGQCkEuQQ0QAAALIAAgA2ojAUYhBCABQQ9qQQ9Bf3NxIQUgASADSwRAIAQEQCABQfD///8DSwRAAAsgACAFahAGIAIgBTYCAAUgBSIGIANBAXQiByAGIAdLGyACKAIIEAchBiAGIAAgAigCDBA7IAYiAEEQayECCwUgBARAIAAgBWokASACIAU2AgALCyACIAE2AgwgAAtzAQR/IAAoAgghAyABIAMgAnZLBEAgAUHw////AyACdksEQEGgBkHgCUEOQS8QAAALIAAoAgAhBCABIAJ0IQUgBCAFEDwhBiAGIANqQQAgBSADaxAdIAYgBEcEQCAAIAY2AgAgACAGNgIECyAAIAU2AggLCz8BA38gARAIIQEgACgCDCECIAJBAWohAyAAIANBAhA9IAAoAgQgAkECdGogARAINgIAIAAgAzYCDCADIAEQHgsSACAAKAIEIAFBAnRqKAIAEAgLPwEBfyABIAAoAgxPBEBBoAdB4AlB3QBBKRAAAAsgACABED8hAiACRQRAIAIQHkHACkHgCUHhAEEnEAAACyACCxMAIAAoAgAgACgCABA5QQFrEEALPwEDfyAAQRBrKAIIIQIjMyEDIAIgAygCAE0EQANAIAIgAUYEQEEBDwsgA0EEaiACQQhsaigCBCICDQALC0EAC1QBBH8gABAIIQBBxbvyiHghASAAQQBHBEBBACECIAAQGkEBdCEDA0AgAiADSQRAIAEgACACai0AAHNBk4OACGwhASACQQFqIQIMAQsLCyABIAAQHgu/AQEGfyAAEAghACACEAghAiAAIAFBAXRqIQUgAiADQQF0aiEGIARBBE8EfyAFQQdxIAZBB3FyRQVBAAsEQAJAA0AgBSkDACAGKQMAUgRADAILIAVBCGohBSAGQQhqIQYgBEEEayEEIARBBE8NAAsLCwNAIAQiB0EBayEEIAcEQCAFLwEAIQggBi8BACEJIAggCUcEQCAIIAlrIAAQHiACEB4PCyAFQQJqIQUgBkECaiEGDAELC0EAIAAQHiACEB4LcQECfyAAEAghACABEAghASAAIAFGBEBBASAAEB4gARAeDwsgAEEARgR/QQEFIAFBAEYLBEBBACAAEB4gARAeDwsgABAaIQMgAyABEBpHBEBBACAAEB4gARAeDwsgAEEAIAFBACADEERFIAAQHiABEB4LXwEDfyABEAghASAAKAIAIAIgACgCBHFBBGxqKAIAIQMDQCADBEAgAygCCEEBcUUEfyADKAIAIAEQRQVBAAsEQCADIAEQHg8LIAMoAghBAUF/c3EhAwwBCwtBACABEB4LKgECfyABEAghASAAIAECfyABEAghAiACEEMgAhAeDAALEEZBAEcgARAeCz8BA38gARAIIQEgACgCDCECIAJBAWohAyAAIANBAhA9IAAoAgQgAkECdGogARAINgIAIAAgAzYCDCADIAEQHgugAgEMfyABQQFqIQJBACACQQRsEDMhAyACQQhsQQNtIQRBACAEQQxsEDMhBSAAKAIIIQYgBiAAKAIQQQxsaiEHIAUhCANAIAYgB0cEQCAGIQogCigCCEEBcUUEQCAIIQsgCyAKKAIANgIAIAsgCigCBDYCBAJ/IAooAgAQCCEMIAwQQyAMEB4MAAsgAXEhDCADIAxBBGxqIQ0gCyANKAIANgIIIA0gCDYCACAIQQxqIQgLIAZBDGohBgwBCwsgACILIAMiDCALKAIAIglHBEAgDBAIIQwgCRAeCyAMNgIAIAAgATYCBCAAIg0gBSIJIA0oAggiC0cEQCAJEAghCSALEB4LIAk2AgggACAENgIMIAAgACgCFDYCECADEB4gBRAeC/0BAQR/IAEQCCEBIAIQCCECAn8gARAIIQMgAxBDIAMQHgwACyEFIAAgASAFEEYhBiAGBEAgBigCBCEDIAIgA0cEQCAGIAIQCDYCBCADEB4LBSAAKAIQIAAoAgxGBEAgACAAKAIUIAAoAgxBA2xBBG1IBH8gACgCBAUgACgCBEEBdEEBcgsQSQsgACgCCBAIIQMgAyAAIAAoAhAiBEEBajYCECAEQQxsaiEGIAYgARAINgIAIAYgAhAINgIEIAAgACgCFEEBajYCFCAAKAIAIAUgACgCBHFBBGxqIQQgBiAEKAIANgIIIAQgBjYCACADEB4LIAAQCCABEB4gAhAeCzoAIAEQCCEBIAIQCCECIAAoAgAgARBHRQRAIAAoAgQgARBIGgsgACgCACABIAIQShAeIAEQHiACEB4LHwAgARAIIQEgAhAIIQIgACABIAIQSyABEB4gAhAeDwsWACABEAghASAAKAIAIAEQPhogARAeC84BAQR/IAEQCCEBIAIQCCECIAEQGkEARgR/IAAoAgAQOUEARgVBAAsEQCAAKAIAIAIQPhogARAeIAIQHg8LIAAQQSIDIgRFBH9BAAUgBEEKEEILBEAgABBBIgQiBUEKEEIEfyAFBUEAQbALQcoAQQcQAAALIAEgAhBMIAQQHgUgABBBIgQiBUUEf0EABSAFQRUQQgsEQCAAEEEiBSIGQRUQQgR/IAYFQQBBsAtBzQBBERAAAAsgAhBNIAUQHgsgBBAeCyADEB4gARAeIAIQHgsqAQJ/IAEQCCEBEDghAiAAIAEgAhBOIAAoAgAgAhA+GkEBIAIQHiABEB4LDQAgACgCBCAAKAIAawsYAQF/IAAQCCEAIAAoAgAgABBQaiAAEB4L7gIBCn8gACEDIAAgAWohBCAEIANPRQRAQQBBsA5B3wVBBhAAAAsgAUEBdEEBEAchBSAFIQYCQANAIAMgBEkEQCADLQAAIQggA0EBaiEDIAhBgAFxRQRAIAIgCEVxBEAMBAsgBiAIOwEABSAEIANGBEAMBAsgAy0AAEE/cSEJIANBAWohAyAIQeABcUHAAUYEQCAGIAhBH3FBBnQgCXI7AQAFIAQgA0YEQAwFCyADLQAAQT9xIQogA0EBaiEDIAhB8AFxQeABRgRAIAhBD3FBDHQgCUEGdHIgCnIhCAUgBCADRgRADAYLIAhBB3FBEnQgCUEMdHIgCkEGdHIgAy0AAEE/cXIhCCADQQFqIQMLIAhBgIAESQRAIAYgCDsBAAUgCEGAgARrIQggCEEKdkGAsANyIQsgCEH/B3FBgLgDciEMIAYgCyAMQRB0cjYCACAGQQJqIQYLCwsgBkECaiEGDAELCwsgBSAGIAVrEDwQCAseAQF/IAAQCCEAIAAQUSABaiACIAFrQQAQUiAAEB4LEAAgACgCCCABIAJBAWsQUwslAAJAAkACQCMyQQFrDgIBAgALAAsgACgCBCECCyAAIAEgAhBUCwYAIAAkMgsHACAAKAIMC/UCAQp/IAIQCCECIAFBAWshAyADQQBIBEBBkAcgAhAeDwsgA0UEQCAAKAIAIgQEfyAEEAgFQZAHCyACEB4PC0EAIQVBACEGQQAhBANAIAQgAUgEQCAAIARBAnRqKAIAIgggBiIJRwRAIAgQCCEIIAkQHgsgCCEGIAZBAEcEQCAFIAYQGmohBQsgBEEBaiEEDAELC0EAIQogAhAaIQsgBSALIANsakEBdEEBEAchDEEAIQQDQCAEIANIBEAgACAEQQJ0aigCACIJIAYiCEcEQCAJEAghCSAIEB4LIAkhBiAGQQBHBEAgBhAaIQkgDCAKQQF0aiAGIAlBAXQQOyAKIAlqIQoLIAsEQCAMIApBAXRqIAIgC0EBdBA7IAogC2ohCgsgBEEBaiEEDAELCyAAIANBAnRqKAIAIgggBiIERwRAIAgQCCEIIAQQHgsgCCEGIAZBAEcEQCAMIApBAXRqIAYgBhAaQQF0EDsLIAwQCCACEB4gBhAeCx0BA38gARAIIQEgACgCBCAAKAIMIAEQWCABEB4PCxsBAX8gACABEAchAyACBEAgAyACIAAQOwsgAws9AQN/QRAgAhAHIQQgACABdCEFIAVBACADEFohBiAEIAYQCDYCACAEIAY2AgQgBCAFNgIIIAQgADYCDCAEC48BAQR/IAAQMCEBIAEjKWshAiACQQlKBEAgASMra0EKaiECIAJBCkgEf0EBBSACQQ9KCwRAIAEjLGtBCmohAgsLQQJBAkEWQQAQWxAIIQMgAygCBCEEIAQgATYCACAEIAI2AgQgAyACQQBOBH8gAkEQSAVBAAtFBEBBwBFB4AhBjQJBBBAAAAsgAiEDEB4gAwtxAQR/IABB///DAE1FBEBBAEGwDkEhQQQQAAALIABB//8DSiEBQQIgAXRBARAHIQIgAUUEQCACIAA7AQAFIABBgIAEayEAIABB/wdxQYC4A3IhAyAAQQp2QYCwA3IhBCACIAQgA0EQdHI2AgALIAIQCAstAQJ/IAFBAEohAkECIAJ0QQEQByEDIAMgADsBACACBEAgAyABOwECCyADEAgLIAACQAJAAkAjMkEBaw4CAQIACwALQX8hAQsgACABEF4LewEGfyABEAghASABQQBGBEBBgAUiAiABIgNHBEAgAhAIIQIgAxAeCyACIQELIAAQGkEBdCEEIAEQGkEBdCEFIAQgBWohBiAGQQBGBEBBkAcQCCABEB4PCyAGQQEQBxAIIQcgByAAIAQQOyAHIARqIAEgBRA7IAcgARAeCycBAX8gABAIIQAgARAIIQEgAEGABSAAQQBHGyABEGAgABAeIAEQHgvqAQEGfyAAEDAhASABQfAMQQAQG0YEQEHwDA8LIAFB4A5BABAbRgRAQeAODwsgAUGAD0EAEBtGBEBBgA8PCyABQaAPQQAQG0YEQEHADw8LIAFB4A9BABAbRgRAQYAQDwsgAUGgEEEAEBtGBEBBwBAPCyABQeAQQQAQG0YEQEGAEQ8LIAFBoBFBABAbRgRAIAAQXCAAEFwhAyAAEFwhBCAAEFwhBUGAIGwgA0GAAmxqIARBEGxqIAVqEF0PC0EARQRAQYASQQEkMiABQQAQXyIGEGEiBUHgCEH/AUEEEAAAC0GQByAGEB4gBRAeC/oBAQZ/IAAQMEHwDEEAEBtGRQRAQZANQeAIQcUBQQQQAAALIAAoAgQoAgQhAUEAQQAQNiECA0BBAQRAIAAQMCEEIARBIE5FBEBB4A1B4AhBywFBBhAAAAsgBEHwDEEAEBtGBEBBASQyIAAoAgQgAUEAEFUhBSACEFdBAEYEQCAFIAIQHg8LIAIgBRBIGiACQZAHEFkgBRAeIAIQHg8FIARB4A5BABAbRgRAIAAoAgQoAgQgAUEBakoEQCACQQEkMiAAKAIEIAFBABBVIgUQSBogBRAeCyACIAAQYiIFEEgaIAAoAgQoAgQhASAFEB4LCwwBCwtBkAcgAhAeCz8BAn8gABAxIAAoAgQiASAAEGMgASgCABAeNgIAIAAQMSAAEDBB0BJBABAbRkUEQEHwEkHgCEGfAUEEEAAACws9AQJ/IAAoAgwhASABQQFIBEBB4BNB4AlBoAJBFBAAAAsgACgCBCABQQFrIgFBAnRqKAIAEAggACABNgIMCxgAIAAoAgAQOUEBSgRAIAAoAgAQZRAeCwvAAQEDfyAAEC5BwAlBABAbRwRAQQAPCyAAKAIEKAIAEAghASAAKAIEIgJBkAcgAigCABAeNgIAIAAoAgAgARBPBEAgABAwGiAAEDFBASECA0AgABAuQYAMQQAQG0cEQCACRQRAIAAQMEGgDEEAEBtGRQRAQcAMQeAIQY4BQQoQAAALBUEAIQILIAAQZCAAEH4aDAELCyAAEDBBgAxBABAbRkUEQEGgE0HgCEGVAUEGEAAACwsgACgCABBmQQEgARAeCzUBAn8gAAR/IAAFQQRBFRAHEAgLEDIhACAAQQA2AgAgACIBQQBBABAgIAEoAgAQHjYCACAACwYAQQAQaAtCAQJ/IAEQCCEBEGkhAiAAKAIAEDlBAEYEQCAAKAIAIAIQPhoFIAAgASACEE4gACgCACACED4aC0EBIAIQHiABEB4LGAAgACgCABA5QQFKBEAgACgCABBlEB4LC7wBAQN/IAAQLkGQFEEAEBtHBEBBAA8LIAAoAgQoAgAQCCEBIAAoAgQiAkGQByACKAIAEB42AgAgACgCACABEGoEQCAAEDAaIAAQMUEBIQIDQCAAEC5BsBRBABAbRwRAIAJFBEAgABAwQaAMQQAQG0ZFBEBBwAxB4AhBsAFBChAAAAsFQQAhAgsgABB+GgwBCwsgABAwQbAUQQAQG0ZFBEBB0BRB4AhBtgFBBhAAAAsLIAAoAgAQa0EBIAEQHgsrACABEAghASAABH8gAAVBBEEXEAcQCAsQMiEAIAAgARAINgIAIAEQHiAACxQBAX8gABAIIQBBACAAEG0gABAeCyoBAX8gARAIIQEgAhAIIQIgAhBuIQMgACABIAMQTiADEB4gARAeIAIQHgsxAQJ/IAAQLkHwDEEAEBtHBEBBAA8LIAAoAgAgACgCBCgCACAAEGMiARBvQQEgARAeC1oBBH8gARAIIQFBACECA0AgAiABEBpIBEAgASACEBsgABAwRkUEQEGQFSABEGEiBEHAFRBhIgVB4AhBwQJBBhAAAAsgBBAeIAUQHiACQQFqIQIMAQsLIAEQHgsfACAABH8gAAVBAUEYEAcQCAsQMiEAIAAgAToAACAACwgAQQAgABByCyABAX8gARAIIQEgAhBzIQMgACABIAMQTiADEB4gARAeC1YAIAAQLkHgBEEAEBtGBEAgACMnEHEgACgCACAAKAIEKAIAQQAQdEEBDwsgABAuQcAEQQAQG0YEQCAAIyYQcSAAKAIAIAAoAgQoAgBBARB0QQEPC0EACx8AIAAEfyAABUEIQRkQBxAICxAyIQAgACABNwMAIAALCABBACAAEHYLIAEBfyABEAghASACEHchAyAAIAEgAxBOIAMQHiABEB4LggECA38CfkIAIQRCASEFIAAQLkHgFUEAEBtGBEBCfyEFIAAQMBoLQQAhAQNAIykgABAuTAR/IAAQLiMqTAVBAAsEQCAAEDAhAyAEQgp+IAMjKWusfCEEIAFBAWohAQwBCwsgAUEASgRAIAAoAgAgACgCBCgCACAEIAV+EHhBAQ8LQQALFAAgAAR/IAAFQQBBGhAHEAgLEDILBgBBABB6Cx4BAX8gARAIIQEQeyECIAAgASACEE4gAhAeIAEQHgsrACAAEC5BgAVBABAbRgRAIAAjKBBxIAAoAgAgACgCBCgCABB8QQEPC0EAC0wBAX8gABAxIAAQZyIBBH8gAQUgABBsCyIBBH8gAQUgABBwCyIBBH8gAQUgABB1CyIBBH8gAQUgABB5CyIBBH8gAQUgABB9CyAAEDELcAEDfyABEAghASACEAghAiACQQBHBEAgACIDIAIiBCADKAIEIgVHBEAgBBAIIQQgBRAeCyAENgIEBSAAIgVBACABECsgBSgCBBAeNgIECyAAEH5BAEdFBEBBgBZB4AhB5ABBBBAAAAsgARAeIAIQHgsfAQF/A0AgACgCABA5QQBKBEAgACgCABBlEB4MAQsLC08BBH8gABAIIQBBACEBIAAiAiABIgNHBEAgAhAIIQIgAxAeCyACIQEjLyABQQAQfyMvKAIAEEEiAhAIIy8oAgAQgAEgAhAeIAAQHiABEB4LXwIDfwF+QgAQJkIAECchAyADQv////8PUQRAECgLQQAgA6cQKSEAQgAgACgCBK0QKiAAEAghASABEIEBIAEQHiIBQQoQQgR/IAEFQQBBwBZBwABBFBAAAAsQCCAAEB4LHwEBfyAAEAghACABEAghASAAIAEQRUUgABAeIAEQHgtIAQN/IAEQCCEBIAAgAQJ/IAEQCCECIAIQQyACEB4MAAsQRiEEIARFBEAgARAeQeAXQaAYQe8AQRAQAAALIAQoAgQQCCABEB4LLgEBfyABEAghASAAKAIAIAEQR0UEQEEAEAggARAeDwsgACgCACABEIQBIAEQHgscAEEABH9BAQVBAAsEf0EBBUEACwR/QQEFQQALC5UBAQF/IABBgC1JBEAgAEGAAXJBoAFGIABBCWtBDUEJa01yDwsgAEGAwABrQQpNBEBBAQ8LAkACQAJAAkACQAJAAkACQCAAIQEgAUGALUYNACABQajAAEYNASABQanAAEYNAiABQa/AAEYNAyABQd/AAEYNBCABQYDgAEYNBSABQf/9A0YNBgwHCwsLCwsLC0EBDwtBAAvaAwIEfwN+IAAQCCEAIAAQGiECIAJFBEBCACAAEB4PCyAAIQMgAy8BACEEQgEhBwNAIAQQhwEEQCADQQJqIgMvAQAhBCACQQFrIQIMAQsLIARBLUYEQCACQQFrIgJFBEBCACAAEB4PCyADQQJqIgMvAQAhBEJ/IQcFIARBK0YEQCACQQFrIgJFBEBCACAAEB4PCyADQQJqIgMvAQAhBAsLIAFFBEAgBEEwRgR/IAJBAkoFQQALBEACQAJAAkACQAJAIANBAmovAQBBIHIhBSAFQeIARg0AIAVB7wBGDQEgBUH4AEYNAgwDCyADQQRqIQMgAkECayECQQIhAQwDCyADQQRqIQMgAkECayECQQghAQwCCyADQQRqIQMgAkECayECQRAhAQwBC0EKIQELBUEKIQELBSABQQJIBH9BAQUgAUEkSgsEQEIAIAAQHg8LC0IAIQgCQANAIAIiBUEBayECIAUEQCADLwEAIQQgBEEwa0EKSQRAIARBMGshBAUgBEHBAGtBGU0EQCAEQcEAQQprayEEBSAEQeEAa0EZTQRAIARB4QBBCmtrIQQFDAULCwsgBCABTwRADAMLIAggAax+IAStfCEIIANBAmohAwwBCwsLIAcgCH4gABAeCxUBAX4gABAIIQAgACABEIgBIAAQHguvAwIHfwJ+IAAQCCEAIAEQCCEBIAAQCCECQQAhAyACIgRFBH9BAAUgBEEKEEILBH8gAUGQBxCDAQVBAAsEQCACIgRBChBCBH8gBAVBAEHAFkGcAUEkEAAACxAIIQQgBCABEIUBIQUgBUEARgRAEIYBBH9BAAVBAAsEQCAAEB4gARAeIAIQHiADEB4gBBAeIAUQHkHQGEHwGBBhIgZBkBkQYSIHQcAWQaEBQQoQAAAFIAAQHiABEB4gAhAeIAMQHiAEEB4gBRAeQdAYQfAYEGEiB0GQGRBhIgZBwBZBpwFBCBAAAAsACyAFIgcgAyIGRwRAIAcQCCEHIAYQHgsgByEDIAQQHiAFEB4FIAIiBiADIgVHBEAgBhAIIQYgBRAeCyAGIQMLIAMiBkUEf0EABSAGQRcQQgtFBEBBwBkgARBhIgZB8BkQYSIFQfAYEGEiBEGgGhBhIgdBwBZBtwFBBhAAAAsgAyIIQRcQQgR/IAgFQQBBwBZBuAFBGxAAAAsoAgAQCCEIIAhBABCJASAGEB4gBRAeIAQQHiAHEB4gCBAeIAAQHiABEB4gAhAeIAMQHg8LeAEBfyAAQaCNBkkEQCAAQeQASQRAQQFBAiAAQQpJGw8FQQRBBSAAQZDOAEkbIQFBAyABIABB6AdJGw8LAAUgAEGAreIESQRAQQZBByAAQcCEPUkbDwVBCUEKIABBgJTr3ANJGyEBQQggASAAQYDC1y9JGw8LAAsAC+4BAgd/An4DQCABQZDOAE8EQCABQZDOAG4gAUGQzgBwIQUhASAFQeQAbiEGIAVB5ABwIQdB4BsgBkECdGo1AgAhCkHgGyAHQQJ0ajUCACELIAJBBGshAiAAIAJBAXRqIAogC0IghoQ3AwAMAQsLIAFB5ABPBEAgAUHkAG4gAUHkAHAhCCEBIAJBAmshAkHgGyAIQQJ0aigCACEJIAAgAkEBdGogCTYCAAsgAUEKTwRAIAJBAmshAkHgGyABQQJ0aigCACEJIAAgAkEBdGogCTYCAAUgAkEBayECQTAgAWohCSAAIAJBAXRqIAk7AQALC7MBAQF/IABCgICapuqv4wFUBEAgAEKAoJSljR1UBEBBC0EMIABCgNDbw/QCVBshAUEKIAEgAEKAyK+gJVQbDwVBDkEPIABCgIDpg7HeFlQbIQFBDSABIABCgMDK84SjAlQbDwsABSAAQoCAqOyFr9GxAVQEQEEQQREgAEKAgIT+pt7hEVQbDwVBE0EUIABCgICgz8jgyOOKf1QbIQFBEiABIABCgICQu7rWrfANVBsPCwALAAvaAQIIfwN+A0AgAUKAwtcvWgRAIAFCgMLXL4AhCyABIAtCgMLXL359pyEEIAshASAEQZDOAG4hBSAEQZDOAHAhBiAFQeQAbiEHIAVB5ABwIQggBkHkAG4hCSAGQeQAcCEKQeAbIAlBAnRqNQIAIQxB4BsgCkECdGo1AgAhDSACQQRrIQIgACACQQF0aiAMIA1CIIaENwMAQeAbIAdBAnRqNQIAIQxB4BsgCEECdGo1AgAhDSACQQRrIQIgACACQQF0aiAMIA1CIIaENwMADAELCyAAIAGnIAIQjAELXwIGfwF+IABCAFJFBEBBoAUPCyAAQv////8PWARAIACnIQIgAhCLASEDIANBAXRBARAHIQEgASACIAMQjAEFIAAQjQEhAyADQQF0QQEQByEBIAEgACADEI4BCyABEAgLCAAgABCPAQ8LBwAgABCQAQsGACAAEAgLuQEBBX8gABAIIQAgACECIAIgAEEQaygCDGohAyABQQBHIQQCQANAIAIgA0kEQCACLwEAIQYgBkGAAUkEQCABIAZFcQRADAQLIARBAWohBAUgBkGAEEkEQCAEQQJqIQQFIAZBgPgDcUGAsANGBH8gAkECaiADSQVBAAsEQCACLwECQYD4A3FBgLgDRgRAIARBBGohBCACQQRqIQIMBQsLIARBA2ohBAsLIAJBAmohAgwBCwsLIAQgABAeC7IDAQ1/IAAQCCEAIAAhAiAAIABBEGsoAgxqIQMgACABEJMBIQQgBEEAEAchBSAFIARqIAFBAEdrIQYgBSEHA0AgByAGSQRAIAIvAQAhCSAJQYABSQRAIAcgCToAACAHQQFqIQcFIAlBgBBJBEAgCUEGdkHAAXIhCiAJQT9xQYABciELIAcgC0EIdCAKcjsBACAHQQJqIQcFIAlBgPgDcUGAsANGBH8gAkECaiADSQVBAAsEQCACLwECIQsgC0GA+ANxQYC4A0YEQEGAgAQgCUH/B3FBCnRqIAtB/wdxciEJIAlBEnZB8AFyIQogCUEMdkE/cUGAAXIhDCAJQQZ2QT9xQYABciENIAlBP3FBgAFyIQ4gByAOQRh0IA1BEHRyIAxBCHRyIApyNgIAIAdBBGohByACQQRqIQIMBQsLIAlBDHZB4AFyIQsgCUEGdkE/cUGAAXIhDiAJQT9xQYABciENIAcgDkEIdCALcjsBACAHIA06AAIgB0EDaiEHCwsgAkECaiECDAELCyACIANNRQRAQQBBsA5BzwVBBhAAAAsgAQRAIAdBADoAAAsgBRAIIAAQHgsbAQJ/IAAQCCEAIAAgARCUASICIAIQHiAAEB4LNAEDfyAAEAghACAAQQEQkwFBAWshAUEAIAEQKSECIAIoAgQgAEEAEJUBIAEQOyACIAAQHgs6AQN/IAAQCCEAQQAhASAAEJIBIQIgAhCWASABEB4hASACEB4gASgCCKwgASgCBK0QBSABEB4gABAeCx0BAn9BsBsgABCRASIBEGEiAhCXASABEB4gAhAeCxgBAX8QggEhACAAQbAXEIoBEJgBIAAQHgsGABAUECULC/EeSQBBEAsYCAAAAAEAAAABAAAACAAAADoAbABlAG4AAEEwCxwMAAAAAQAAAAEAAAAMAAAAOgBmAHIAbwBuAHQAAEHQAAsaCgAAAAEAAAABAAAACgAAADoAYgBhAGMAawAAQfAACxQEAAAAAQAAAAEAAAAEAAAAOgA6AABBkAELOioAAAABAAAAAQAAACoAAABiAGwAbwBjAGsAXwBpAG4AZABlAHgAXwBzAGUAZQBkAGUAZABfAGEAdAAAQdABCzIiAAAAAQAAAAEAAAAiAAAAcgBhAG4AZABvAG0AXwBiAHUAZgBmAGUAcgBfAGsAZQB5AABBkAILPi4AAAABAAAAAQAAAC4AAAByAGEAbgBkAG8AbQBfAGIAdQBmAGYAZQByAF8AaQBuAGQAZQB4AF8AawBlAHkAAEHQAgsSAgAAAAEAAAABAAAAAgAAAD0AAEHwAguQAYAAAAABAAAAAQAAAIAAAABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6ADAAMQAyADMANAA1ADYANwA4ADkAKwAvAABBgAQLMCAAAAABAAAAAQAAACAAAAAwADEAMgAzADQANQA2ADcAOAA5AGEAYgBjAGQAZQBmAABBsAQLGAgAAAABAAAAAQAAAAgAAAB0AHIAdQBlAABB0AQLGgoAAAABAAAAAQAAAAoAAABmAGEAbABzAGUAAEHwBAsYCAAAAAEAAAABAAAACAAAAG4AdQBsAGwAAEGQBQsSAgAAAAEAAAABAAAAAgAAADAAAEGwBQsSAgAAAAEAAAABAAAAAgAAADkAAEHQBQsSAgAAAAEAAAABAAAAAgAAAEEAAEHwBQsSAgAAAAEAAAABAAAAAgAAAGEAAEGQBgssHAAAAAEAAAABAAAAHAAAAEkAbgB2AGEAbABpAGQAIABsAGUAbgBnAHQAaAAAQcAGCzYmAAAAAQAAAAEAAAAmAAAAfgBsAGkAYgAvAGEAcgByAGEAeQBiAHUAZgBmAGUAcgAuAHQAcwAAQYAHCxAAAAAAAQAAAAEAAAAAAAAAAEGQBws0JAAAAAEAAAABAAAAJAAAAEkAbgBkAGUAeAAgAG8AdQB0ACAAbwBmACAAcgBhAG4AZwBlAABB0AcLNCQAAAABAAAAAQAAACQAAAB+AGwAaQBiAC8AdAB5AHAAZQBkAGEAcgByAGEAeQAuAHQAcwAAQZAICzgoAAAAAQAAAAEAAAAoAAAAVQBuAGUAeABwAGUAYwB0AGUAZAAgAGkAbgBwAHUAdAAgAGUAbgBkAABB0AgLVkYAAAABAAAAAQAAAEYAAAB+AGwAaQBiAC8AYQBzAHMAZQBtAGIAbAB5AHMAYwByAGkAcAB0AC0AagBzAG8AbgAvAGQAZQBjAG8AZABlAHIALgB0AHMAAEGwCQsSAgAAAAEAAAABAAAAAgAAAHsAAEHQCQsqGgAAAAEAAAABAAAAGgAAAH4AbABpAGIALwBhAHIAcgBhAHkALgB0AHMAAEGACgsuHgAAAAEAAAABAAAAHgAAAH4AbABpAGIALwByAHQALwBzAHQAdQBiAC4AdABzAABBsAoLbl4AAAABAAAAAQAAAF4AAABFAGwAZQBtAGUAbgB0ACAAdAB5AHAAZQAgAG0AdQBzAHQAIABiAGUAIABuAHUAbABsAGEAYgBsAGUAIABpAGYAIABhAHIAcgBhAHkAIABpAHMAIABoAG8AbABlAHkAAEGgCwtQQAAAAAEAAAABAAAAQAAAAH4AbABpAGIALwBhAHMAcwBlAG0AYgBsAHkAcwBjAHIAaQBwAHQALQBqAHMAbwBuAC8ASgBTAE8ATgAuAHQAcwAAQfALCxICAAAAAQAAAAEAAAACAAAAfQAAQZAMCxICAAAAAQAAAAEAAAACAAAALAAAQbAMCygYAAAAAQAAAAEAAAAYAAAARQB4AHAAZQBjAHQAZQBkACAAJwAsACcAAEHgDAsSAgAAAAEAAAABAAAAAgAAACIAAEGADQtKOgAAAAEAAAABAAAAOgAAAEUAeABwAGUAYwB0AGUAZAAgAGQAbwB1AGIAbABlAC0AcQB1AG8AdABlAGQAIABzAHQAcgBpAG4AZwAAQdANC0g4AAAAAQAAAAEAAAA4AAAAVQBuAGUAeABwAGUAYwB0AGUAZAAgAGMAbwBuAHQAcgBvAGwAIABjAGgAYQByAGEAYwB0AGUAcgAAQaAOCywcAAAAAQAAAAEAAAAcAAAAfgBsAGkAYgAvAHMAdAByAGkAbgBnAC4AdABzAABB0A4LEgIAAAABAAAAAQAAAAIAAABcAABB8A4LEgIAAAABAAAAAQAAAAIAAAAvAABBkA8LEgIAAAABAAAAAQAAAAIAAABiAABBsA8LEgIAAAABAAAAAQAAAAIAAAAIAABB0A8LEgIAAAABAAAAAQAAAAIAAABuAABB8A8LEgIAAAABAAAAAQAAAAIAAAAKAABBkBALEgIAAAABAAAAAQAAAAIAAAByAABBsBALEgIAAAABAAAAAQAAAAIAAAANAABB0BALEgIAAAABAAAAAQAAAAIAAAB0AABB8BALEgIAAAABAAAAAQAAAAIAAAAJAABBkBELEgIAAAABAAAAAQAAAAIAAAB1AABBsBELNiYAAAABAAAAAQAAACYAAABVAG4AZQB4AHAAZQBjAHQAZQBkACAAXAB1ACAAZABpAGcAaQB0AABB8BELTDwAAAABAAAAAQAAADwAAABVAG4AZQB4AHAAZQBjAHQAZQBkACAAZQBzAGMAYQBwAGUAZAAgAGMAaABhAHIAYQBjAHQAZQByADoAIAAAQcASCxICAAAAAQAAAAEAAAACAAAAOgAAQeASCygYAAAAAQAAAAEAAAAYAAAARQB4AHAAZQBjAHQAZQBkACAAJwA6ACcAAEGQEwtAMAAAAAEAAAABAAAAMAAAAFUAbgBlAHgAcABlAGMAdABlAGQAIABlAG4AZAAgAG8AZgAgAG8AYgBqAGUAYwB0AABB0BMLLBwAAAABAAAAAQAAABwAAABBAHIAcgBhAHkAIABpAHMAIABlAG0AcAB0AHkAAEGAFAsSAgAAAAEAAAABAAAAAgAAAFsAAEGgFAsSAgAAAAEAAAABAAAAAgAAAF0AAEHAFAs+LgAAAAEAAAABAAAALgAAAFUAbgBlAHgAcABlAGMAdABlAGQAIABlAG4AZAAgAG8AZgAgAGEAcgByAGEAeQAAQYAVCyQUAAAAAQAAAAEAAAAUAAAARQB4AHAAZQBjAHQAZQBkACAAJwAAQbAVCxICAAAAAQAAAAEAAAACAAAAJwAAQdAVCxICAAAAAQAAAAEAAAACAAAALQAAQfAVCzIiAAAAAQAAAAEAAAAiAAAAQwBhAG4AbgBvAHQAIABwAGEAcgBzAGUAIABKAFMATwBOAABBsBYLaFgAAAABAAAAAQAAAFgAAABuAG8AZABlAF8AbQBvAGQAdQBsAGUAcwAvAG4AZQBhAHIALQBzAGQAawAtAGEAcwAvAGEAcwBzAGUAbQBiAGwAeQAvAGIAaQBuAGQAZwBlAG4ALgB0AHMAAEGgFwsiEgAAAAEAAAABAAAAEgAAAHQAaQBtAGUAcwB0AGEAbQBwAABB0BcLNCQAAAABAAAAAQAAACQAAABLAGUAeQAgAGQAbwBlAHMAIABuAG8AdAAgAGUAeABpAHMAdAAAQZAYCyYWAAAAAQAAAAEAAAAWAAAAfgBsAGkAYgAvAG0AYQBwAC4AdABzAABBwBgLGgoAAAABAAAAAQAAAAoAAAB0AHkAcABlACAAAEHgGAsWBgAAAAEAAAABAAAABgAAAHUANgA0AABBgBkLMCAAAAABAAAAAQAAACAAAAAgAGMAYQBuAG4AbwB0ACAAYgBlACAAbgB1AGwAbAAuAABBsBkLMCAAAAABAAAAAQAAACAAAABWAGEAbAB1AGUAIAB3AGkAdABoACAASwBlAHkAOgAgAABB4BkLJhYAAAABAAAAAQAAABYAAAAgAHcAaQB0AGgAIAB0AHkAcABlACAAAEGQGguOAX4AAAABAAAAAQAAAH4AAAAgAGkAcwAgAGEAbgAgADYANAAtAGIAaQB0ACAAaQBuAHQAZQBnAGUAcgAgAGEAbgBkACAAaQBzACAAZQB4AHAAZQBjAHQAZQBkACAAdABvACAAYgBlACAAZQBuAGMAbwBkAGUAZAAgAGEAcwAgAGEAIABzAHQAcgBpAG4AZwAAQaAbCyYWAAAAAQAAAAEAAAAWAAAAaABlAGEAcgB0AGIAZQBhAHQAOgAgAABB0BsLoAOQAQAAAQAAABsAAACQAQAAMAAwADAAMQAwADIAMAAzADAANAAwADUAMAA2ADAANwAwADgAMAA5ADEAMAAxADEAMQAyADEAMwAxADQAMQA1ADEANgAxADcAMQA4ADEAOQAyADAAMgAxADIAMgAyADMAMgA0ADIANQAyADYAMgA3ADIAOAAyADkAMwAwADMAMQAzADIAMwAzADMANAAzADUAMwA2ADMANwAzADgAMwA5ADQAMAA0ADEANAAyADQAMwA0ADQANAA1ADQANgA0ADcANAA4ADQAOQA1ADAANQAxADUAMgA1ADMANQA0ADUANQA1ADYANQA3ADUAOAA1ADkANgAwADYAMQA2ADIANgAzADYANAA2ADUANgA2ADYANwA2ADgANgA5ADcAMAA3ADEANwAyADcAMwA3ADQANwA1ADcANgA3ADcANwA4ADcAOQA4ADAAOAAxADgAMgA4ADMAOAA0ADgANQA4ADYAOAA3ADgAOAA4ADkAOQAwADkAMQA5ADIAOQAzADkANAA5ADUAOQA2ADkANwA5ADgAOQA5AABB8B4L5AEcAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAcAAAAQAAAAAAAAABAAAAAHAAAAEAAAAAcAAAAQAAAACwAAABAAAAAAAAAAmCBBAAAAAACTIAAAAgAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAJMgAAACAAAAEAAAAAAAAAAQAAAAAAAAADEAAAACAAAAEAAAAAsAAACTBAAAAgAAABAAAAALAAAAEAAAAAsAAAAQAAAACwAAABAAAAALAAAAEAAAAAAAAAAAhT8EbmFtZQH9PpsBABN+bGliL2J1aWx0aW5zL2Fib3J0AT9ub2RlX21vZHVsZXMvbmVhci1zZGstYXMvYXNzZW1ibHkvcnVudGltZS9lbnYvaW1wb3J0cy9lbnYuaW5wdXQCRm5vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9ydW50aW1lL2Vudi9pbXBvcnRzL2Vudi5yZWdpc3Rlcl9sZW4DP25vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9ydW50aW1lL2Vudi9pbXBvcnRzL2Vudi5wYW5pYwRHbm9kZV9tb2R1bGVzL25lYXItc2RrLWFzL2Fzc2VtYmx5L3J1bnRpbWUvZW52L2ltcG9ydHMvZW52LnJlYWRfcmVnaXN0ZXIFMX5saWIvbmVhci1zZGstYXMvcnVudGltZS9lbnYvaW1wb3J0cy9lbnYubG9nX3V0ZjgGHH5saWIvcnQvc3R1Yi9tYXliZUdyb3dNZW1vcnkHFH5saWIvcnQvc3R1Yi9fX2FsbG9jCBV+bGliL3J0L3N0dWIvX19yZXRhaW4JNH5saWIvbmVhci1zZGstYXMvcnVudGltZS9zdG9yYWdlL1N0b3JhZ2UjY29uc3RydWN0b3IKJnN0YXJ0On5saWIvbmVhci1zZGstYXMvcnVudGltZS9zdG9yYWdlCzV+bGliL25lYXItc2RrLWFzL3J1bnRpbWUvY29udHJhY3QvQ29udGV4dCNjb25zdHJ1Y3Rvcgwnc3RhcnQ6fmxpYi9uZWFyLXNkay1hcy9ydW50aW1lL2NvbnRyYWN0DSRzdGFydDp+bGliL25lYXItc2RrLWFzL3J1bnRpbWUvaW5kZXgOMn5saWIvbmVhci1zZGstYXMvdm0vb3V0Y29tZS9SZXR1cm5EYXRhI2NvbnN0cnVjdG9yDyx+bGliL25lYXItc2RrLWFzL3ZtL291dGNvbWUvTm9uZSNjb25zdHJ1Y3RvchAhc3RhcnQ6fmxpYi9uZWFyLXNkay1hcy92bS9vdXRjb21lERxzdGFydDp+bGliL25lYXItc2RrLWFzL3ZtL3ZtEh9zdGFydDp+bGliL25lYXItc2RrLWFzL3ZtL2luZGV4ExxzdGFydDp+bGliL25lYXItc2RrLWFzL2luZGV4FBNzdGFydDphc3NlbWJseS9tYWluFUVub2RlX21vZHVsZXMvbmVhci1zZGstYXMvYXNzZW1ibHkvcnVudGltZS9zdG9yYWdlL1N0b3JhZ2UjY29uc3RydWN0b3IWN3N0YXJ0Om5vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9ydW50aW1lL3N0b3JhZ2UXRm5vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9ydW50aW1lL2NvbnRyYWN0L0NvbnRleHQjY29uc3RydWN0b3IYOHN0YXJ0Om5vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9ydW50aW1lL2NvbnRyYWN0GTVzdGFydDpub2RlX21vZHVsZXMvbmVhci1zZGstYXMvYXNzZW1ibHkvcnVudGltZS9pbmRleBodfmxpYi9zdHJpbmcvU3RyaW5nI2dldDpsZW5ndGgbHX5saWIvc3RyaW5nL1N0cmluZyNjaGFyQ29kZUF0HCZzdGFydDp+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vZGVjb2Rlch0XfmxpYi9tZW1vcnkvbWVtb3J5LmZpbGweFn5saWIvcnQvc3R1Yi9fX3JlbGVhc2UfLH5saWIvYXJyYXlidWZmZXIvQXJyYXlCdWZmZXJWaWV3I2NvbnN0cnVjdG9yIEZ+bGliL2FycmF5L0FycmF5PH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWU+I2NvbnN0cnVjdG9yITF+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyI2NvbnN0cnVjdG9yIl9+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vZGVjb2Rlci9KU09ORGVjb2Rlcjx+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyPiNjb25zdHJ1Y3RvciMjc3RhcnQ6fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04kJHN0YXJ0On5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9pbmRleCUvc3RhcnQ6bm9kZV9tb2R1bGVzL25lYXItc2RrLWFzL2Fzc2VtYmx5L2JpbmRnZW4mL25vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9iaW5kZ2VuL2lucHV0JzZub2RlX21vZHVsZXMvbmVhci1zZGstYXMvYXNzZW1ibHkvYmluZGdlbi9yZWdpc3Rlcl9sZW4oL25vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9iaW5kZ2VuL3BhbmljKSZ+bGliL3R5cGVkYXJyYXkvVWludDhBcnJheSNjb25zdHJ1Y3Rvcio3bm9kZV9tb2R1bGVzL25lYXItc2RrLWFzL2Fzc2VtYmx5L2JpbmRnZW4vcmVhZF9yZWdpc3Rlcis5fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvRGVjb2RlclN0YXRlI2NvbnN0cnVjdG9yLCV+bGliL3R5cGVkYXJyYXkvVWludDhBcnJheSNnZXQ6bGVuZ3RoLSB+bGliL3R5cGVkYXJyYXkvVWludDhBcnJheSNfX2dldC5cfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcGVla0NoYXIvYH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I2lzV2hpdGVzcGFjZTBcfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcmVhZENoYXIxYn5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I3NraXBXaGl0ZXNwYWNlMjR+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlI2NvbnN0cnVjdG9yMyh+bGliL2FycmF5YnVmZmVyL0FycmF5QnVmZmVyI2NvbnN0cnVjdG9yNE9+bGliL21hcC9NYXA8fmxpYi9zdHJpbmcvU3RyaW5nLH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWU+I2NsZWFyNVV+bGliL21hcC9NYXA8fmxpYi9zdHJpbmcvU3RyaW5nLH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWU+I2NvbnN0cnVjdG9yNjB+bGliL2FycmF5L0FycmF5PH5saWIvc3RyaW5nL1N0cmluZz4jY29uc3RydWN0b3I3Mn5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uT2JqI2NvbnN0cnVjdG9yOC9+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlLk9iamVjdDlFfmxpYi9hcnJheS9BcnJheTx+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlPiNnZXQ6bGVuZ3RoOhd+bGliL3V0aWwvbWVtb3J5L21lbWNweTsXfmxpYi9tZW1vcnkvbWVtb3J5LmNvcHk8Fn5saWIvcnQvc3R1Yi9fX3JlYWxsb2M9FX5saWIvYXJyYXkvZW5zdXJlU2l6ZT4/fmxpYi9hcnJheS9BcnJheTx+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlPiNwdXNoP0p+bGliL2FycmF5L0FycmF5PH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWU+I19fdW5jaGVja2VkX2dldEBAfmxpYi9hcnJheS9BcnJheTx+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlPiNfX2dldEEufmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlciNnZXQ6cGVla0IUfmxpYi9ydC9fX2luc3RhbmNlb2ZDFn5saWIvdXRpbC9oYXNoL2hhc2hTdHJEHH5saWIvdXRpbC9zdHJpbmcvY29tcGFyZUltcGxFF35saWIvc3RyaW5nL1N0cmluZy5fX2VxRk5+bGliL21hcC9NYXA8fmxpYi9zdHJpbmcvU3RyaW5nLH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWU+I2ZpbmRHTX5saWIvbWFwL01hcDx+bGliL3N0cmluZy9TdHJpbmcsfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5WYWx1ZT4jaGFzSCl+bGliL2FycmF5L0FycmF5PH5saWIvc3RyaW5nL1N0cmluZz4jcHVzaElQfmxpYi9tYXAvTWFwPH5saWIvc3RyaW5nL1N0cmluZyx+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlPiNyZWhhc2hKTX5saWIvbWFwL01hcDx+bGliL3N0cmluZy9TdHJpbmcsfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5WYWx1ZT4jc2V0Syt+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLk9iaiNfc2V0TFR+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLk9iaiNzZXQ8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5WYWx1ZT5NK35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uQXJyI3B1c2hOLn5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXIjYWRkVmFsdWVPMH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXIjcHVzaE9iamVjdFAvfmxpYi9hcnJheWJ1ZmZlci9BcnJheUJ1ZmZlclZpZXcjZ2V0OmJ5dGVPZmZzZXRRNX5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi91dGlsL2luZGV4L0J1ZmZlci5nZXREYXRhUHRyUiR+bGliL3N0cmluZy9TdHJpbmcuVVRGOC5kZWNvZGVVbnNhZmVTNX5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi91dGlsL2luZGV4L0J1ZmZlci5yZWFkU3RyaW5nVDh+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vZGVjb2Rlci9EZWNvZGVyU3RhdGUjcmVhZFN0cmluZ1VDfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvRGVjb2RlclN0YXRlI3JlYWRTdHJpbmd8dHJhbXBvbGluZVYTfnNldEFyZ3VtZW50c0xlbmd0aFcvfmxpYi9hcnJheS9BcnJheTx+bGliL3N0cmluZy9TdHJpbmc+I2dldDpsZW5ndGhYIH5saWIvdXRpbC9zdHJpbmcvam9pblN0cmluZ0FycmF5WSl+bGliL2FycmF5L0FycmF5PH5saWIvc3RyaW5nL1N0cmluZz4jam9pbloVfmxpYi9ydC9fX2FsbG9jQnVmZmVyWxR+bGliL3J0L19fYWxsb2NBcnJheVxgfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcmVhZEhleERpZ2l0XSB+bGliL3N0cmluZy9TdHJpbmcuZnJvbUNvZGVQb2ludF4ffmxpYi9zdHJpbmcvU3RyaW5nLmZyb21DaGFyQ29kZV8qfmxpYi9zdHJpbmcvU3RyaW5nLmZyb21DaGFyQ29kZXx0cmFtcG9saW5lYBl+bGliL3N0cmluZy9TdHJpbmcjY29uY2F0YRt+bGliL3N0cmluZy9TdHJpbmcuX19jb25jYXRiY35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I3JlYWRFc2NhcGVkQ2hhcmNefmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcmVhZFN0cmluZ2RcfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcGFyc2VLZXllPn5saWIvYXJyYXkvQXJyYXk8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5WYWx1ZT4jcG9wZi9+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyI3BvcE9iamVjdGdffmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcGFyc2VPYmplY3RoMn5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uQXJyI2NvbnN0cnVjdG9yaS5+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLlZhbHVlLkFycmF5ai9+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyI3B1c2hBcnJheWsufmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlciNwb3BBcnJheWxefmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcGFyc2VBcnJheW0yfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5TdHIjY29uc3RydWN0b3JuL35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWUuU3RyaW5nby9+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyI3NldFN0cmluZ3BffmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL2RlY29kZXIvSlNPTkRlY29kZXI8fmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlcj4jcGFyc2VTdHJpbmdxYX5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I3JlYWRBbmRBc3NlcnRyM35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uQm9vbCNjb25zdHJ1Y3RvcnMtfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5WYWx1ZS5Cb29sdDB+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyI3NldEJvb2xlYW51YH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I3BhcnNlQm9vbGVhbnYyfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSlNPTi5OdW0jY29uc3RydWN0b3J3L35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWUuTnVtYmVyeDB+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyI3NldEludGVnZXJ5X35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I3BhcnNlTnVtYmVyejN+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9KU09OLk51bGwjY29uc3RydWN0b3J7LX5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWUuTnVsbHwtfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlciNzZXROdWxsfV1+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vZGVjb2Rlci9KU09ORGVjb2Rlcjx+bGliL2Fzc2VtYmx5c2NyaXB0LWpzb24vSlNPTi9IYW5kbGVyPiNwYXJzZU51bGx+Xn5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I3BhcnNlVmFsdWV/X35saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9kZWNvZGVyL0pTT05EZWNvZGVyPH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0hhbmRsZXI+I2Rlc2VyaWFsaXplgAErfmxpYi9hc3NlbWJseXNjcmlwdC1qc29uL0pTT04vSGFuZGxlciNyZXNldIEBRX5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL19KU09OLnBhcnNlPH5saWIvdHlwZWRhcnJheS9VaW50OEFycmF5PoIBMm5vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9iaW5kZ2VuL2dldElucHV0gwEXfmxpYi9zdHJpbmcvU3RyaW5nLl9fbmWEAU1+bGliL21hcC9NYXA8fmxpYi9zdHJpbmcvU3RyaW5nLH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uVmFsdWU+I2dldIUBKn5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uT2JqI2dldIYBP25vZGVfbW9kdWxlcy9uZWFyLXNkay1hcy9hc3NlbWJseS9iaW5kZ2VuL2lzUmVhbGx5TnVsbGFibGU8dTY0PocBGH5saWIvdXRpbC9zdHJpbmcvaXNTcGFjZYgBHH5saWIvdXRpbC9zdHJpbmcvc3RydG9sPGk2ND6JARh+bGliL251bWJlci9VNjQucGFyc2VJbnSKAVxub2RlX21vZHVsZXMvbmVhci1zZGstYXMvYXNzZW1ibHkvYmluZGdlbi9kZWNvZGU8dTY0LH5saWIvYXNzZW1ibHlzY3JpcHQtanNvbi9KU09OL0pTT04uT2JqPosBH35saWIvdXRpbC9udW1iZXIvZGVjaW1hbENvdW50MzKMARt+bGliL3V0aWwvbnVtYmVyL3V0b2EzMl9sdXSNAR9+bGliL3V0aWwvbnVtYmVyL2RlY2ltYWxDb3VudDY0jgEbfmxpYi91dGlsL251bWJlci91dG9hNjRfbHV0jwEXfmxpYi91dGlsL251bWJlci91dG9hNjSQARp+bGliL3V0aWwvbnVtYmVyL2l0b2E8dTY0PpEBGH5saWIvbnVtYmVyL1U2NCN0b1N0cmluZ5IBG35saWIvc3RyaW5nL1N0cmluZyN0b1N0cmluZ5MBIn5saWIvc3RyaW5nL1N0cmluZy5VVEY4LmJ5dGVMZW5ndGiUAR5+bGliL3N0cmluZy9TdHJpbmcuVVRGOC5lbmNvZGWVASl+bGliL25lYXItc2RrLWFzL3J1bnRpbWUvdXRpbC91dGlsLnRvVVRGOJYBMH5saWIvbmVhci1zZGstYXMvcnVudGltZS91dGlsL3V0aWwuc3RyaW5nVG9CeXRlc5cBQH5saWIvbmVhci1zZGstYXMvcnVudGltZS9sb2dnaW5nL2xvZ2dpbmcubG9nPH5saWIvc3RyaW5nL1N0cmluZz6YARdhc3NlbWJseS9tYWluL2hlYXJ0YmVhdJkBIWFzc2VtYmx5L21haW4vX193cmFwcGVyX2hlYXJ0YmVhdJoBBn5zdGFydA==" + } + }, + { + "AccessKey": { + "account_id": "01.near", + "public_key": "ed25519:6GxYiNnRLoKkjGeKA68hrfyrJC9tYSamGND5d23aXqRx", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "01.near", + "public_key": "ed25519:E837NUYQLFgP9cLQou3nBSYzqFFhGffhYQLVzbwL5jtY", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "alex.near", + "public_key": "ed25519:8fohZQ3DwXgVUXKJSoU9vi6iPyXKUKKff1T7sw4xj4wW", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "bo.near", + "public_key": "ed25519:C5kXZP86M3DoWjPUwYr2QXkP7RoLj1hcF3kPFyoYcC4h", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "bot.pulse.near", + "public_key": "ed25519:9x5kkFynLRojfwoVGbuZPSoRHEP5urze5xAbkybXHFBS", + "access_key": { + "nonce": 422638, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "bowen.near", + "public_key": "ed25519:5LaQTGEqGZMrSQuypgR8zS3fQJRhVLgMtjFw7qBmWb8X", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "contributors.near", + "public_key": "ed25519:BCCMGbV9FzTMTcwS67QCW1TrTmjuwFR1SrFPiG744kio", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "erik.near", + "public_key": "ed25519:8fohZQ3DwXgVUXKJSoU9vi6iPyXKUKKff1T7sw4xj4wW", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "foundation.near", + "public_key": "ed25519:GmtTh6yhWz6BmkA9AfnoQESKanDbBJDGfWVpW5wq9Uz", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "illia.near", + "public_key": "ed25519:dQMV9776YrjHYLysrpQ7abkUmqiA5XLupvveVofYnvy", + "access_key": { + "nonce": 0, + "permission": { + "FunctionCall": { + "allowance": "0", + "receiver_id": "illia.near", + "method_names": [ + "__wallet__metadata" + ] + } + } + } + } + }, + { + "AccessKey": { + "account_id": "illia.near", + "public_key": "ed25519:8fohZQ3DwXgVUXKJSoU9vi6iPyXKUKKff1T7sw4xj4wW", + "access_key": { + "nonce": 11, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "kendall.near", + "public_key": "ed25519:3Puiccgti9iExBUucUxEdbVgeecRibK5FgENQVLHTg5t", + "access_key": { + "nonce": 0, + "permission": { + "FunctionCall": { + "allowance": "0", + "receiver_id": "kendall.near", + "method_names": [ + "__wallet__metadata" + ] + } + } + } + } + }, + { + "AccessKey": { + "account_id": "kendall.near", + "public_key": "ed25519:DvabrRhC1TKXG8hWTGG2U3Ra5E4YXAF1azHdwSc61fs9", + "access_key": { + "nonce": 5, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "kendall.near", + "public_key": "ed25519:J7PuMuFm34c19f324gFSQwMkaBG1DmwPaSEVZEbZw1nX", + "access_key": { + "nonce": 0, + "permission": { + "FunctionCall": { + "allowance": "0", + "receiver_id": "kendall.near", + "method_names": [ + "__wallet__metadata" + ] + } + } + } + } + }, + { + "AccessKey": { + "account_id": "ledger.vlad.near", + "public_key": "ed25519:8g7GvgccAaub68HeSrmp6Aw2vYAvRYbLQZdEa6hZiG9X", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "ledger.vlad.near", + "public_key": "ed25519:GgK5WqBhrhdwYDUgqsjKwDpnFWad4BgpJSNfH2VPs94v", + "access_key": { + "nonce": 0, + "permission": { + "FunctionCall": { + "allowance": "0", + "receiver_id": "ledger.vlad.near", + "method_names": [ + "__wallet__metadata" + ] + } + } + } + } + }, + { + "AccessKey": { + "account_id": "mike.near", + "public_key": "ed25519:AhiKooGnQsw8S8WZ2V2xRGvpbZDY3yHFcTp4iCHYP8jo", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "mikemikemikemikemikemikemikemike", + "public_key": "ed25519:AhiKooGnQsw8S8WZ2V2xRGvpbZDY3yHFcTp4iCHYP8jo", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "near", + "public_key": "ed25519:5zset1JX4qp4PcR3N9KDSY6ATdgkrbBW5wFBGWC4ZjnU", + "access_key": { + "nonce": 8, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "nfvalidator1.near", + "public_key": "ed25519:Fd2TW6TtTDL5hiY58pbTVYfTBSNyWLgHGxiD9mcHgQ92", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "nfvalidator2.near", + "public_key": "ed25519:4rg9rmbxuSM7bX8z8989LTmBiM6JNnE4w9LZ8KkuCcfq", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "nfvalidator3.near", + "public_key": "ed25519:EVyX7KE6e2KD3CzpoN1kvzJATsS5KxkjbMCCYHbM3vRr", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "nfvalidator4.near", + "public_key": "ed25519:CrLQzMvfSDWnTYzfbEzcJ3hdetnpYdsQnvbhuzwHBtAG", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "patrick.near", + "public_key": "ed25519:8MPLjkG12V5AQfCogZhjrWe5k6PoRzNtLUb2eD1r7fyU", + "access_key": { + "nonce": 3, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "patrick.near", + "public_key": "ed25519:BHTmjrvg2UWxBjzSwDyhkc2FYJseSduWVe7YXBS2Rms1", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "peter.near", + "public_key": "ed25519:HDybq3JWgmbaiCKtE27T75iYVkEoA8cH6rfnut77ZVY1", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "pulse.near", + "public_key": "ed25519:3BWDipnJmNfWT7YSBGZu63dkfMBoZDUqWJsctNGBDinE", + "access_key": { + "nonce": 3, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "registrar", + "public_key": "ed25519:Fm9g4GQeQrnwknCVexuPvn3owgrYvMbZhPRoXKpj2wX6", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "treasury.near", + "public_key": "ed25519:CzAXM8NcumuHPYJYnjq5tUX5v92GHdbYZfmfKFwDNzBZ", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "vlad.near", + "public_key": "ed25519:2nE29FtYYZrT2owygL3FN9CLVBs9wdUy1r6pdpuScazs", + "access_key": { + "nonce": 2, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "vlad.near", + "public_key": "ed25519:4GnS8L8hnCNWh4saWPPAVxto1VFtVdmY27mkrXLeSxgp", + "access_key": { + "nonce": 1, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "vlad.near", + "public_key": "ed25519:9xLURZGus8bU4Qnf9AC3jmJhHNBo7Ydh17w7nJAY2L78", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "wallet.pulse.near", + "public_key": "ed25519:9783PHB4mZXYFopqXcypm4TCv2LoAbAdmj24AA9YJ2C6", + "access_key": { + "nonce": 2, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "wallet.pulse.near", + "public_key": "ed25519:BJ3wDgNtiMa22d8iCKmzbGA7YTiSWv9J33NTftekUcoZ", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "AccessKey": { + "account_id": "yifang.near", + "public_key": "ed25519:CKUb9VneyN1XFMXcvEc55aKiDpirdDim8Dd4cAyzefF1", + "access_key": { + "nonce": 0, + "permission": "FullAccess" + } + } + }, + { + "Data": { + "account_id": "near", + "data_key": "U1RBVEU=", + "value": "CQAAAAAAAAAAAAAAaQAAAAAAAAAACQAAAAAAAAAAAAAAawAAAAAAAAAACQAAAAAAAAAAAAAAdg==" + } + } + ] +} \ No newline at end of file diff --git a/pytest/lib/branches.py b/pytest/lib/branches.py index 09fd857a0bb..3d435773811 100644 --- a/pytest/lib/branches.py +++ b/pytest/lib/branches.py @@ -63,7 +63,7 @@ class Executables(typing.NamedTuple): def node_config(self) -> typing.Dict[str, typing.Any]: return { 'local': True, - 'neard_root': self.root, + 'near_root': self.root, 'binary_name': self.neard.name } diff --git a/pytest/lib/cluster.py b/pytest/lib/cluster.py index d346d0e1225..1b9dddf5687 100644 --- a/pytest/lib/cluster.py +++ b/pytest/lib/cluster.py @@ -4,7 +4,7 @@ import os import pathlib import rc -import requests +from geventhttpclient import Session import shutil import signal import subprocess @@ -31,6 +31,22 @@ cleanup_remote_nodes_atexit_registered = False +# Return the session object that can be used for making http requests. +# +# Please note that if the request is consistently failing the default parameters +# mean that the calls will take connection_timeout + timeout * (1 + max_retries) ~ 1 minute. +# +# The return value is a context manager that should be used in a with statement. +# e.g. +# with session() as s: +# r = s.get("http://example.com") +def session(timeout=9, max_retries=5) -> Session: + return Session(connection_timeout=6, + network_timeout=timeout, + max_retries=max_retries, + retry_delay=0.1) + + class DownloadException(Exception): pass @@ -209,17 +225,20 @@ def addr_with_pk(self) -> str: def wait_for_rpc(self, timeout=1): nretry(lambda: self.get_status(), timeout=timeout) - def json_rpc(self, method, params, timeout=2): + # Send the given JSON-RPC request to the node and return the response. + # + # Please note that if the request is consistently failing the default parameters + # mean that the call will take connection_timeout + timeout * (1 + max_retries) ~ 1 minute. + def json_rpc(self, method, params, timeout=9, max_retries=5): j = { 'method': method, 'params': params, 'id': 'dontcare', 'jsonrpc': '2.0' } - r = requests.post("http://%s:%s" % self.rpc_addr(), - json=j, - timeout=timeout) - r.raise_for_status() + with session(timeout, max_retries) as s: + r = s.post("http://%s:%s" % self.rpc_addr(), json=j) + r.raise_for_status() return json.loads(r.content) def send_tx(self, signed_tx): @@ -235,10 +254,10 @@ def get_status(self, check_storage: bool = True, timeout: float = 4, verbose: bool = False): - r = requests.get("http://%s:%s/status" % self.rpc_addr(), - timeout=timeout) - r.raise_for_status() - status = json.loads(r.content) + with session(timeout) as s: + r = s.get("http://%s:%s/status" % self.rpc_addr()) + r.raise_for_status() + status = json.loads(r.content) if verbose: logger.info(f'Status: {status}') if check_storage and status['sync_info']['syncing'] == False: @@ -249,9 +268,9 @@ def get_status(self, return status def get_metrics(self, timeout: float = 4): - r = requests.get("http://%s:%s/metrics" % self.rpc_addr(), - timeout=timeout) - r.raise_for_status() + with session(timeout) as s: + r = s.get("http://%s:%s/metrics" % self.rpc_addr()) + r.raise_for_status() return r.content def get_latest_block(self, **kw) -> BlockId: @@ -287,12 +306,22 @@ def get_validators(self, epoch_id=None): args = {'epoch_id': epoch_id} return self.json_rpc('validators', args) - def get_account(self, acc, finality='optimistic', do_assert=True, **kwargs): - res = self.json_rpc('query', { + def get_account(self, + acc, + finality='optimistic', + block=None, + do_assert=True, + **kwargs): + query = { "request_type": "view_account", "account_id": acc, - "finality": finality - }, **kwargs) + } + if block is not None: + # this can be either height or hash + query["block_id"] = block + else: + query["finality"] = finality + res = self.json_rpc('query', query, **kwargs) if do_assert: assert 'error' not in res, res @@ -350,8 +379,19 @@ def get_final_block(self, **kwargs): def get_chunk(self, chunk_id): return self.json_rpc('chunk', [chunk_id]) - def get_tx(self, tx_hash, tx_recipient_id): - return self.json_rpc('tx', [tx_hash, tx_recipient_id]) + # Get the transaction status. + # + # The default timeout is quite high - 15s - so that is is longer than the + # node's default polling_timeout. It's done this way to differentiate + # between the case when the transaction is not found on the node and when + # the node is dead or not responding. + def get_tx(self, tx_hash, tx_recipient_id, timeout=15): + return self.json_rpc( + 'tx', + [tx_hash, tx_recipient_id], + timeout=timeout, + max_retries=0, + ) def get_changes_in_block(self, changes_in_block_request): return self.json_rpc('EXPERIMENTAL_changes_in_block', @@ -533,6 +573,11 @@ def kill(self, *, gentle=False): self._process.wait(5) self._process = None + def reload_updateable_config(self): + logger.info(f"Reloading updateable config for node {self.ordinal}.") + """Sends SIGHUP signal to the process in order to trigger updateable config reload.""" + self._process.send_signal(signal.SIGHUP) + def reset_data(self): shutil.rmtree(os.path.join(self.node_dir, "data")) @@ -682,10 +727,12 @@ def cleanup(self): def json_rpc(self, method, params, timeout=15): return super().json_rpc(method, params, timeout=timeout) + def get_status_impl(self): + with session(timeout=15) as s: + return s.get("http://%s:%s/status" % self.rpc_addr()) + def get_status(self): - r = nretry(lambda: requests.get("http://%s:%s/status" % self.rpc_addr(), - timeout=15), - timeout=45) + r = nretry(lambda: self.get_status_impl, timeout=45) r.raise_for_status() return json.loads(r.content) @@ -715,7 +762,7 @@ def spin_up_node(config, blacklist=[], proxy=None, skip_starting_proxy=False, - single_node=False): + single_node=False) -> BaseNode: is_local = config['local'] args = make_boot_nodes_arg(boot_node) @@ -727,9 +774,14 @@ def spin_up_node(config, "127.0.0.1:%s" % (24567 + 10 + bl_ordinal) for bl_ordinal in blacklist ] - node = LocalNode(24567 + 10 + ordinal, 3030 + 10 + ordinal, - near_root, node_dir, blacklist, - config.get('binary_name'), single_node) + node = LocalNode(24567 + 10 + ordinal, + 3030 + 10 + ordinal, + near_root, + node_dir, + blacklist, + config.get('binary_name'), + single_node, + ordinal=ordinal) else: # TODO: Figure out how to know IP address beforehand for remote deployment. assert len( @@ -923,14 +975,16 @@ def start_cluster(num_nodes, def spin_up_node_and_push(i, boot_node: BootNode): single_node = (num_nodes == 1) and (num_observers == 0) - node = spin_up_node(config, - near_root, - node_dirs[i], - i, - boot_node=boot_node, - proxy=proxy, - skip_starting_proxy=True, - single_node=single_node) + node = spin_up_node( + config, + near_root, + node_dirs[i], + ordinal=i, + boot_node=boot_node, + proxy=proxy, + skip_starting_proxy=True, + single_node=single_node, + ) ret.append((i, node)) return node diff --git a/pytest/lib/key.py b/pytest/lib/key.py index bb0f51fad63..008a8198220 100644 --- a/pytest/lib/key.py +++ b/pytest/lib/key.py @@ -3,7 +3,6 @@ import os import typing -import ed25519 from nacl.signing import SigningKey @@ -20,14 +19,13 @@ def __init__(self, account_id: str, pk: str, sk: str) -> None: @classmethod def from_random(cls, account_id: str) -> 'Key': - keys = ed25519.create_keypair(entropy=os.urandom) - return cls.from_keypair(account_id, keys) + return cls.from_keypair(account_id, SigningKey(os.urandom(32))) @classmethod def implicit_account(cls) -> 'Key': - keys = ed25519.create_keypair(entropy=os.urandom) - account_id = keys[1].to_bytes().hex() - return cls.from_keypair(account_id, keys) + key = SigningKey(os.urandom(32)) + account_id = bytes(key.verify_key).hex() + return cls.from_keypair(account_id, key) @classmethod def from_json(cls, j: typing.Dict[str, str]): @@ -50,13 +48,13 @@ def from_seed_testonly(cls, account_id: str, seed: str = None) -> 'Key': # use the repeated seed string as secret key by injecting fake entropy fake_entropy = lambda length: (seed * (1 + int(length / len(seed))) ).encode()[:length] - keys = ed25519.create_keypair(entropy=fake_entropy) - return cls.from_keypair(account_id, keys) + return cls.from_keypair(account_id, SigningKey(fake_entropy(32))) @classmethod - def from_keypair(cls, account_id, keys): - sk = 'ed25519:' + base58.b58encode(keys[0].to_bytes()).decode('ascii') - pk = 'ed25519:' + base58.b58encode(keys[1].to_bytes()).decode('ascii') + def from_keypair(cls, account_id, key: SigningKey): + sk = 'ed25519:' + base58.b58encode(bytes(key)).decode('ascii') + pk = 'ed25519:' + base58.b58encode(bytes( + key.verify_key)).decode('ascii') return cls(account_id, pk, sk) def decoded_pk(self) -> bytes: diff --git a/pytest/lib/mocknet.py b/pytest/lib/mocknet.py index 1cfec940d42..e629bfad834 100644 --- a/pytest/lib/mocknet.py +++ b/pytest/lib/mocknet.py @@ -89,21 +89,21 @@ } -def get_node(hostname): +def get_node(hostname, project=PROJECT): instance_name = hostname n = GCloudNode( instance_name, username=NODE_USERNAME, - project=PROJECT, + project=project, ssh_key_path=NODE_SSH_KEY_PATH, ) return n -def get_nodes(pattern=None): +def get_nodes(pattern=None, project=PROJECT): machines = gcloud.list( pattern=pattern, - project=PROJECT, + project=project, username=NODE_USERNAME, ssh_key_path=NODE_SSH_KEY_PATH, ) @@ -111,7 +111,7 @@ def get_nodes(pattern=None): lambda machine: GCloudNode( machine.name, username=NODE_USERNAME, - project=PROJECT, + project=project, ssh_key_path=NODE_SSH_KEY_PATH, ), machines, diff --git a/pytest/lib/peer.py b/pytest/lib/peer.py index 71015e9a98c..ec3607a851b 100644 --- a/pytest/lib/peer.py +++ b/pytest/lib/peer.py @@ -2,6 +2,7 @@ import concurrent import hashlib import struct +import time import base58 @@ -127,7 +128,11 @@ def create_handshake(my_key_pair_nacl, handshake.chain_info.genesis_id.chain_id = 'moo' handshake.chain_info.genesis_id.hash = bytes([0] * 32) - handshake.edge_info.nonce = 1 + nonce = int(time.time()) + if nonce % 2 == 0: + nonce += 1 + handshake.edge_info.nonce = nonce + handshake.edge_info.signature = Signature() handshake.edge_info.signature.keyType = 0 diff --git a/pytest/lib/utils.py b/pytest/lib/utils.py index 1cf0af15f65..c44131065c3 100644 --- a/pytest/lib/utils.py +++ b/pytest/lib/utils.py @@ -17,7 +17,8 @@ import cluster from configured_logger import logger -from transaction import sign_payment_tx +import key +import transaction class TxContext: @@ -54,7 +55,7 @@ def send_moar_txs(self, last_block_hash, num, use_routing): if self.expected_balances[from_] >= amt: logger.info("Sending a tx from %s to %s for %s" % (from_, to, amt)) - tx = sign_payment_tx( + tx = transaction.sign_payment_tx( self.nodes[from_].signer_key, 'test%s' % to, amt, self.next_nonce, base58.b58decode(last_block_hash.encode('utf8'))) @@ -418,7 +419,7 @@ def poll_blocks(node: cluster.LocalNode, sent to the node. kw: Keyword arguments passed to `BaseDone.get_latest_block` method. Yields: - A `cluster.BlockId` object for each each time node’s latest block + A `cluster.BlockId` object for each time node’s latest block changes including the first block when function starts. Note that there is no guarantee that there will be no skipped blocks. Raises: diff --git a/pytest/requirements.txt b/pytest/requirements.txt index 9e3b6a7c64f..49153401800 100644 --- a/pytest/requirements.txt +++ b/pytest/requirements.txt @@ -1,9 +1,11 @@ PyGithub base58 +cachetools cython deepdiff ed25519 -locust +locust>=2.28 +geventhttpclient>=2.3.1 nearup numpy prometheus-client diff --git a/pytest/tests/loadtest/README.md b/pytest/tests/loadtest/README.md index 649e666e15d..644223980c3 100644 --- a/pytest/tests/loadtest/README.md +++ b/pytest/tests/loadtest/README.md @@ -1,45 +1,3 @@ # Loadtest -This test requires a few steps. Firstly, build the binary: - -```shell -make neard-release -``` - -Secondly, initialise your own localnet: - -```shell -./target/release/neard --home ~/.near_tmp init --chain-id localnet --num-shards=5 -``` - -Thirdly, create accounts and deploy the contract: - -```shell -python3 pytest/tests/loadtest/setup.py --home ~/.near_tmp --num_accounts=5 -``` - -And lastly, run the test: - -```shell -python3 pytest/tests/loadtest/loadtest.py --home ~/.near_tmp --num_accounts=5 --num_requests=1000 -``` - -# Load Test version 2 - -The newer loadtest2.py script currently runs an intense load test with the FT contract. - -Much like with the earlier version you will want to build a `neard`. This script can set up a (2 -node) cluster for you (nice for testing): - -``` -env NEAR_ROOT=../target/release/ python3 tests/loadtest/loadtest2.py --fungible-token-wasm=$PWD/../../FT/res/fungible_token.wasm --setup-cluster --accounts=1000 --executors=4 -``` - -Or, you can set up a network yourself, and point the script at your local node’s RPC endpoint: - -``` -env NEAR_ROOT=../target/release/ python3 tests/stress/perf_ft_transfer.py --fungible-token-wasm=$PWD/../../FT/res/fungible_token.wasm --accounts=1000 --executors=4 --contract-key=~/.near/node.json -``` - -As seen in commands above, you will need a fungible token contract to test with. There's one you -can get from the `near/near-examples` repository. +This folder contains tooling to run load tests for NEAR network. diff --git a/pytest/tests/loadtest/contract/Cargo.lock b/pytest/tests/loadtest/contract/Cargo.lock deleted file mode 100644 index 4bf48d94896..00000000000 --- a/pytest/tests/loadtest/contract/Cargo.lock +++ /dev/null @@ -1,611 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - -[[package]] -name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "borsh" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74" -dependencies = [ - "borsh-derive", - "hashbrown 0.9.1", -] - -[[package]] -name = "borsh-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate", - "proc-macro2", - "syn", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cpufeatures" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" -dependencies = [ - "libc", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "indexmap" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" -dependencies = [ - "autocfg", - "hashbrown 0.11.2", -] - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "keccak" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" - -[[package]] -name = "loadtest-contract" -version = "0.1.0" -dependencies = [ - "near-sdk", -] - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "near-primitives-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2b3fb5acf3a494aed4e848446ef2d6ebb47dbe91c681105d4d1786c2ee63e52" -dependencies = [ - "base64", - "borsh", - "bs58", - "derive_more", - "hex", - "lazy_static", - "num-rational", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "near-rpc-error-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", -] - -[[package]] -name = "near-rpc-error-macro" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9" -dependencies = [ - "near-rpc-error-core", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", -] - -[[package]] -name = "near-runtime-utils" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a48d80c4ca1d4cf99bc16490e1e3d49826c150dfc4410ac498918e45c7d98e07" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "near-sdk" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7383e242d3e07bf0951e8589d6eebd7f18bb1c1fc5fbec3fad796041a6aebd1" -dependencies = [ - "base64", - "borsh", - "bs58", - "near-primitives-core", - "near-sdk-macros", - "near-vm-logic", - "serde", - "serde_json", - "wee_alloc", -] - -[[package]] -name = "near-sdk-core" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284a78d9eb8eda58330462fa0023a6d7014c941df1f0387095e7dfd1dc0f2bce" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "near-sdk-macros" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2037337438f97d1ce5f7c896cf229dc56dacd5c01142d1ef95a7d778cde6ce7d" -dependencies = [ - "near-sdk-core", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "near-vm-errors" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e281d8730ed8cb0e3e69fb689acee6b93cdb43824cd69a8ffd7e1bfcbd1177d7" -dependencies = [ - "borsh", - "hex", - "near-rpc-error-macro", - "serde", -] - -[[package]] -name = "near-vm-logic" -version = "4.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11cb28a2d07f37680efdaf860f4c9802828c44fc50c08009e7884de75d982c5" -dependencies = [ - "base64", - "borsh", - "bs58", - "byteorder", - "near-primitives-core", - "near-runtime-utils", - "near-vm-errors", - "serde", - "sha2", - "sha3", -] - -[[package]] -name = "num-bigint" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "semver" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" - -[[package]] -name = "serde" -version = "1.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer", - "cfg-if 1.0.0", - "cpufeatures", - "digest", - "opaque-debug", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer", - "digest", - "keccak", - "opaque-debug", -] - -[[package]] -name = "syn" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/pytest/tests/loadtest/contract/Cargo.toml b/pytest/tests/loadtest/contract/Cargo.toml deleted file mode 100644 index c0e52d83616..00000000000 --- a/pytest/tests/loadtest/contract/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "loadtest-contract" -version = "0.1.0" -authors = ["Near Inc "] -edition = "2018" - -[lints] -workspace = true - -[workspace] -members = [] - - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -near-sdk = "3.1.0" - -[profile.release] -codegen-units = 1 -# Tell `rustc` to optimize for small code size. -opt-level = "z" -lto = true -debug = false -panic = "abort" -# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 -overflow-checks = true diff --git a/pytest/tests/loadtest/contract/build.sh b/pytest/tests/loadtest/contract/build.sh deleted file mode 100755 index 95f50570daf..00000000000 --- a/pytest/tests/loadtest/contract/build.sh +++ /dev/null @@ -1 +0,0 @@ -cargo build --target wasm32-unknown-unknown --release diff --git a/pytest/tests/loadtest/contract/src/lib.rs b/pytest/tests/loadtest/contract/src/lib.rs deleted file mode 100644 index d0f0255cb6d..00000000000 --- a/pytest/tests/loadtest/contract/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Contract that can be used for different types of loadtesting. -//! Based on the rust-counter example. - -use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::collections::LookupMap; -use near_sdk::{env, near_bindgen}; - -near_sdk::setup_alloc!(); - -#[near_bindgen] -#[derive(BorshDeserialize, BorshSerialize)] -pub struct Counter { - val: u64, - records: LookupMap, -} - -impl Default for Counter { - fn default() -> Self { - Self { val: 0, records: LookupMap::new(b"r".to_vec()) } - } -} -#[near_bindgen] -impl Counter { - pub fn get_num(&self) -> u64 { - return self.val; - } - - pub fn increment(&mut self) { - self.val += 1; - let log_message = format!("Increased number to {}", self.val); - env::log(log_message.as_bytes()); - } - - pub fn reset(&mut self) { - self.val = 0; - } - - fn get_previous_val(&self, i: u64) -> u64 { - match self.records.get(&i.to_string()) { - Some(value) => value.parse::().unwrap(), - None => 0, - } - } - - // Similar to the methods above, but updating many fields (therefore using a lot more gas). - pub fn increment_many(&mut self, how_many: u64) { - for i in 1..how_many { - let previous_val = self.get_previous_val(i); - self.records.insert(&i.to_string(), &(previous_val + 1).to_string()); - } - } - - pub fn reset_increment_many(&mut self, how_many: u64) { - for i in 1..how_many { - self.records.insert(&i.to_string(), &(0).to_string()); - } - } - pub fn get_increment_many(&self) -> u64 { - self.get_previous_val(1) - } - - pub fn infinite_loop(&self) { - loop {} - } - - pub fn write_many(&mut self, how_many: u64) { - for i in self.val..self.val + how_many { - self.records.insert(&i.to_string(), &"a".to_string()); - } - self.val += how_many; - } -} diff --git a/pytest/tests/loadtest/loadtest.py b/pytest/tests/loadtest/loadtest.py deleted file mode 100644 index 106d167e645..00000000000 --- a/pytest/tests/loadtest/loadtest.py +++ /dev/null @@ -1,104 +0,0 @@ -from tqdm import tqdm -import mocknet_helpers -import account -import key -import base64 -import argparse -from os.path import join - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Setup loadtest') - - parser.add_argument('--home', type=str, required=True) - parser.add_argument( - '--sender_key_file', - type=str, - default="validator_key.json", - help= - "File in home directory that contains the key for the account that should be used to send all the requests." - ) - parser.add_argument( - '--num_accounts', - type=int, - default=5, - help= - "Accounts that contain the contract to run (either set the --num_accounts or comma separated list in --account_ids)" - ) - parser.add_argument('--account_ids', type=str, default=None) - - parser.add_argument('--num_requests', type=int, default=50) - parser.add_argument('--host', type=str, default='127.0.0.1') - # Contract types: - # - storage - tries to do maximum amount of reads & writes (increments a large vector) - # - compute - tries to do maximum amount of compute (infinite loop) - parser.add_argument('--contract_type', - type=str, - default='storage', - help="""# Contract types: - # - storage - tries to do maximum amount of reads & writes (increments a large vector) - # - compute - tries to do maximum amount of compute (infinite loop) - # - write - tries to write a lot of data to the contract state.""") - args = parser.parse_args() - - accounts = [args.account_ids.split(",")] if args.account_ids else [ - f"shard{i}" for i in range(args.num_accounts) - ] - - validator_key = key.Key.from_json_file(join(args.home, - args.sender_key_file)) - - base_block_hash = mocknet_helpers.get_latest_block_hash(addr=args.host) - nonce = mocknet_helpers.get_nonce_for_key(validator_key, addr=args.host) - - my_account = account.Account(validator_key, - init_nonce=nonce, - base_block_hash=base_block_hash, - rpc_infos=[(args.host, "3030")]) - - # First - 'reset' the counters in the contract. - for y in accounts: - my_account.send_call_contract_raw_tx( - contract_id=y, - method_name="reset_increment_many", - args=f'{{"how_many": 400}}'.encode("utf-8"), - deposit=0) - - results = [] - - contract_type = args.contract_type - assert (contract_type in ['storage', 'compute', 'write']) - - if contract_type == "storage": - method_name = "increment_many" - if contract_type == "write": - method_name = "write_many" - if contract_type == "compute": - method_name = "infinite_loop" - - for i in tqdm(range(args.num_requests)): - for y in accounts: - result = my_account.send_call_contract_raw_tx( - contract_id=y, - method_name=method_name, - args=f'{{"how_many": {min(400 + i, 400)}}}'.encode("utf-8"), - deposit=0) - results.append(result) - - if contract_type == 'storage': - # For 'storage' contracts - we can also check that all were executed successfully. - for y in accounts: - res = my_account.send_call_contract_raw_tx( - contract_id=y, - method_name="get_increment_many", - args='', - deposit=0) - print(f"Shard {y} asking for result: {res}") - result = mocknet_helpers.tx_result(res["result"], - validator_key.account_id, - addr=args.host, - wait_for_success=True) - outcome = base64.b64decode(result['status']['SuccessValue']) - if int(outcome) == args.num_requests: - print(f"Shard {y}: PASS") - else: - print(f"Shard {y} : FAIL {outcome} vs {args.num_requests}") diff --git a/pytest/tests/loadtest/loadtest2.py b/pytest/tests/loadtest/loadtest2.py deleted file mode 100644 index f7bcdee2c89..00000000000 --- a/pytest/tests/loadtest/loadtest2.py +++ /dev/null @@ -1,525 +0,0 @@ -#!/usr/bin/env python3 -""" -This is a benchmark in which a network with a single fungible_token contract is -deployed, and then a variable number of users (see `N_ACCOUNTS`) send each other -those fungible tokens. - -At the time this benchmark is written, the intent is to observe the node metrics -and traces for the block duration, potentially adding any additional -instrumentation as needed. - -To run: - -``` -env NEAR_ROOT=../target/release/ \ - python3 tests/loadtest/loadtest2.py \ - --fungible-token-wasm=$PWD/../../FT/res/fungible_token.wasm \ - --setup-cluster --accounts=1000 --executors=4 -``` -""" - -import argparse -import sys -import os -import time -import pathlib -import base58 -import requests -import random -import logging -import json -import multiprocessing -import multiprocessing.queues -import ctypes -import ed25519 -import queue -import string - -sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) - -import cluster -import utils -import account -import transaction -import key -import account -import mocknet_helpers -from configured_logger import new_logger - -DEFAULT_TRANSACTION_TTL_SECONDS = 10 -MAX_INFLIGHT_TRANSACTIONS_PER_EXECUTOR = 1000 -SEED = random.uniform(0, 0xFFFFFFFF) -logger = new_logger(level=logging.INFO) - - -class Transaction: - """ - A transaction future. - """ - - ID = 0 - - def __init__(self): - self.id = Transaction.ID - Transaction.ID += 1 - - # Number of times we are going to check this transaction for completion before retrying - # submission - self.ttl = 0 - self.expiration = 0 - # The transaction id hash - # - # str if the transaction has been submitted and may eventually conclude. - self.transaction_id = None - # The transaction caller (used for checking the transaction status. - # - # `str` if the transaction has been submitted and may eventually conclude. - self.caller = None - # The outcome of a successful transaction execution - self.outcome = None - - def poll(self, node, block_hash): - """ - Returns True if transaction has completed. - """ - if self.is_complete(): - return True - # Send the transaction if the previous expired or we didn't send one in the first place. - if self.transaction_id is None or self.ttl <= 0: - if self.transaction_id is not None: - logger.warning( - f"transaction {self.transaction_id} expired, submitting a new one!" - ) - (self.transaction_id, self.caller) = self.send(node, block_hash) - self.expiration = time.time() + DEFAULT_TRANSACTION_TTL_SECONDS - self.ttl = DEFAULT_TRANSACTION_TTL_SECONDS - return False # almost guaranteed to not produce any results right now. - caller = ACCOUNTS[self.caller] - logger.debug( - f"checking {self.transaction_id} from {caller.key.account_id}") - tx_result = node.json_rpc('tx', - [self.transaction_id, caller.key.account_id]) - if self.is_success(tx_result): - self.outcome = tx_result - return True - return False - - def send(self, block_hash): - return (self.transaction_id, self.caller) - - def is_complete(self): - return self.outcome is not None - - def is_success(self, tx_result): - success = 'error' not in tx_result - if not success: - logger.debug( - f"transaction {self.transaction_id} for {self.caller} is not successful: {tx_result}" - ) - # only set TTL if we managed to check for success or failure... - self.ttl = self.expiration - time.time() - return success - - -class DeployFT(Transaction): - - def __init__(self, account, contract): - super().__init__() - self.account = account - self.contract = contract - - def send(self, node, block_hash): - account = ACCOUNTS[self.account] - logger.warning(f"deploying FT to {account.key.account_id}") - wasm_binary = utils.load_binary_file(self.contract) - tx = transaction.sign_deploy_contract_tx(account.key, wasm_binary, - account.use_nonce(), - block_hash) - result = node.send_tx(tx) - return (result["result"], self.account) - - -class TransferFT(Transaction): - - def __init__(self, ft, sender, recipient, how_much=1, tgas=300): - super().__init__() - self.ft = ft - self.sender = sender - self.recipient = recipient - self.how_much = how_much - self.tgas = tgas - - def send(self, node, block_hash): - (ft, sender, recipient - ) = ACCOUNTS[self.ft], ACCOUNTS[self.sender], ACCOUNTS[self.recipient] - logger.debug( - f"sending {self.how_much} FT from {sender.key.account_id} to {recipient.key.account_id}" - ) - args = { - "receiver_id": recipient.key.account_id, - "amount": str(int(self.how_much)), - } - tx = transaction.sign_function_call_tx( - sender.key, - ft.key.account_id, - "ft_transfer", - json.dumps(args).encode('utf-8'), - # About enough gas per call to fit N such transactions into an average block. - self.tgas * account.TGAS, - # Gotta attach exactly 1 yoctoNEAR according to NEP-141 to avoid calls from restricted access keys - 1, - sender.use_nonce(), - block_hash) - result = node.send_tx(tx) - return (result["result"], self.sender) - - -class TransferNear(Transaction): - - def __init__(self, sender, recipient_id, how_much=2.0): - super().__init__() - self.recipient_id = recipient_id - self.sender = sender - self.how_much = how_much - - def send(self, node, block_hash): - sender = ACCOUNTS[self.sender] - logger.debug( - f"sending {self.how_much} NEAR from {sender.key.account_id} to {self.recipient_id}" - ) - tx = transaction.sign_payment_tx(sender.key, self.recipient_id, - int(self.how_much * 1E24), - sender.use_nonce(), block_hash) - result = node.send_tx(tx) - return (result["result"], self.sender) - - -class CreateSubAccount(Transaction): - - def __init__(self, sender, sub, balance=50.0): - super().__init__() - self.sender = sender - self.sub = sub - self.balance = balance - - def send(self, node, block_hash): - sender = ACCOUNTS[self.sender] - sub = ACCOUNTS[self.sub] - new_account_id = f"{sub.key.account_id}.{sender.key.account_id}" - logger.debug(f"creating {new_account_id}") - tx = transaction.sign_create_account_with_full_access_key_and_balance_tx( - sender.key, sub.key.account_id, sub.key, int(self.balance * 1E24), - sender.use_nonce(), block_hash) - result = node.send_tx(tx) - return (result["result"], self.sender) - - -class InitFT(Transaction): - - def __init__(self, contract): - super().__init__() - self.contract = contract - - def send(self, node, block_hash): - contract = ACCOUNTS[self.contract] - args = json.dumps({ - "owner_id": contract.key.account_id, - "total_supply": str(10**33) - }) - tx = transaction.sign_function_call_tx(contract.key, - contract.key.account_id, - "new_default_meta", - args.encode('utf-8'), int(3E14), - 0, contract.use_nonce(), - block_hash) - result = node.send_tx(tx) - return (result["result"], self.contract) - - -class InitFTAccount(Transaction): - - def __init__(self, contract, account): - super().__init__() - self.contract = contract - self.account = account - - def send(self, node, block_hash): - contract, account = ACCOUNTS[self.contract], ACCOUNTS[self.account] - args = json.dumps({"account_id": account.key.account_id}) - tx = transaction.sign_function_call_tx(contract.key, - contract.key.account_id, - "storage_deposit", - args.encode('utf-8'), int(3E14), - int(1E23), contract.use_nonce(), - block_hash) - result = node.send_tx(tx) - return (result["result"], self.contract) - - -class TxQueue(multiprocessing.queues.Queue): - - def __init__(self, size, *args, **kwargs): - super().__init__(size, - ctx=multiprocessing.get_context(), - *args, - **kwargs) - self.pending = multiprocessing.Value(ctypes.c_ulong, 0) - - def add(self, tx): - with self.pending.get_lock(): - self.pending.value += 1 - self.put(tx) - - def complete(self): - with self.pending.get_lock(): - self.pending.value -= 1 - - -class Account: - - def __init__(self, key): - self.key = key - self.nonce = multiprocessing.Value(ctypes.c_ulong, 0) - - def refresh_nonce(self, node): - with self.nonce.get_lock(): - self.nonce.value = mocknet_helpers.get_nonce_for_key( - self.key, - addr=node.rpc_addr()[0], - port=node.rpc_addr()[1], - ) - - def use_nonce(self): - with self.nonce.get_lock(): - new_nonce = self.nonce.value + 1 - self.nonce.value = new_nonce - return new_nonce - - -def transaction_executor(nodes, tx_queue, accounts): - global ACCOUNTS - ACCOUNTS = accounts - last_block_hash_update = 0 - my_transactions = queue.SimpleQueue() - rng = random.Random() - while True: - node = rng.choice(nodes) - try: - now = time.time() - if now - last_block_hash_update >= 0.5: - block_hash = base58.b58decode(node.get_latest_block().hash) - last_block_hash_update = now - except (requests.exceptions.ReadTimeout, - requests.exceptions.ConnectionError): - continue - - while my_transactions.qsize() < MAX_INFLIGHT_TRANSACTIONS_PER_EXECUTOR: - try: - tx = tx_queue.get_nowait() - except queue.Empty: - break - # Send out the transaction immediately. - try: - tx.poll(node, block_hash) - except (requests.exceptions.ReadTimeout, - requests.exceptions.ConnectionError): - pass - my_transactions.put(tx) - - try: - tx = my_transactions.get_nowait() - except queue.Empty: - time.sleep(0.1) - continue - try: - poll_result = tx.poll(node, block_hash) - except (requests.exceptions.ReadTimeout, - requests.exceptions.ConnectionError): - my_transactions.put(tx) - continue - if not poll_result: - my_transactions.put(tx) - if tx.ttl != DEFAULT_TRANSACTION_TTL_SECONDS: - time.sleep(0.1) # don't spam RPC too hard... - else: - tx_queue.complete() - - -def main(): - parser = argparse.ArgumentParser(description='FT transfer benchmark.') - parser.add_argument('--fungible-token-wasm', - required=True, - help='Path to the compiled Fungible Token contract') - parser.add_argument( - '--setup-cluster', - default=False, - help= - 'Whether to start a dedicated cluster instead of connecting to an existing local node', - action='store_true') - parser.add_argument( - '--contracts', - default='0,2,4,6,8,a,c,e', - help= - 'Number of contract accounts, or alternatively list of subnames, separated by commas' - ) - parser.add_argument( - '--contract-key', - required='--setup-cluster' not in sys.argv, - help= - 'account to deploy contract to and use as source of NEAR for account creation' - ) - parser.add_argument('--accounts', - default=1000, - help='Number of accounts to use') - parser.add_argument( - '--no-account-topup', - default=False, - action='store_true', - help='Fill accounts with additional NEAR prior to testing') - parser.add_argument('--shards', default=10, help='number of shards') - parser.add_argument('--executors', - default=2, - help='number of transaction executors') - parser.add_argument('--tx-tgas', - default=30, - help='amount of Tgas to attach to each transaction') - args = parser.parse_args() - - logger.warning(f"SEED is {SEED}") - rng = random.Random(SEED) - - if args.setup_cluster: - config = cluster.load_config() - nodes = cluster.start_cluster( - 2, 0, args.shards, config, [["epoch_length", 100]], { - shard: { - "tracked_shards": list(range(args.shards)) - } for shard in range(args.shards + 1) - }) - if args.contract_key is None: - signer_key = nodes[0].signer_key - else: - signer_key = key.Key.from_json_file(args.contract_key) - - else: - nodes = [ - cluster.RpcNode("127.0.0.1", 3030), - ] - # The `nearup` localnet setup stores the keys in this directory. - key_path = args.contract_key - signer_key = key.Key.from_json_file(key_path) - - ACCOUNTS = [] - ACCOUNTS.append(Account(signer_key)) - ACCOUNTS[0].refresh_nonce(nodes[0]) - funding_account = 0 - start_of_accounts = len(ACCOUNTS) - 1 - contract_accounts = [] - - try: - for sub in sorted( - rng.sample(string.ascii_lowercase + string.digits, - k=int(args.contracts))): - funding_key = ACCOUNTS[funding_account].key - sub_key = key.Key(f"{sub}.{funding_key.account_id}", funding_key.pk, - funding_key.sk) - contract_accounts.append(len(ACCOUNTS)) - ACCOUNTS.append(Account(sub_key)) - except ValueError: - for sub in args.contracts.split(","): - funding_key = ACCOUNTS[funding_account].key - sub_key = key.Key(f"{sub}.{funding_key.account_id}", funding_key.pk, - funding_key.sk) - contract_accounts.append(len(ACCOUNTS)) - ACCOUNTS.append(Account(sub_key)) - - for i in range(int(args.accounts)): - keys = ed25519.create_keypair(entropy=rng.randbytes) - account_id = keys[1].to_bytes().hex() - sk = 'ed25519:' + base58.b58encode(keys[0].to_bytes()).decode('ascii') - pk = 'ed25519:' + base58.b58encode(keys[1].to_bytes()).decode('ascii') - ACCOUNTS.append(Account(key.Key(account_id, pk, sk))) - - executors = int(args.executors) - queue_size = 16 + max(MAX_INFLIGHT_TRANSACTIONS_PER_EXECUTOR, - int(args.accounts) * len(contract_accounts)) - tx_queue = TxQueue(queue_size) - subargs = ( - nodes, - tx_queue, - ACCOUNTS, - ) - for executor in range(executors): - multiprocessing.Process(target=transaction_executor, - args=subargs, - daemon=True).start() - - for contract_account in contract_accounts: - tx_queue.add(CreateSubAccount(funding_account, contract_account)) - wait_empty(tx_queue, "creating contract sub accounts") - for contract_account in contract_accounts: - ACCOUNTS[contract_account].refresh_nonce(nodes[0]) - tx_queue.add(DeployFT(contract_account, args.fungible_token_wasm)) - wait_empty(tx_queue, "deployment") - for contract_account in contract_accounts: - tx_queue.add(InitFT(contract_account)) - wait_empty(tx_queue, "contract initialization") - - if not args.no_account_topup: - for test_account in ACCOUNTS[start_of_accounts:]: - tx_queue.add( - TransferNear(funding_account, test_account.key.account_id, 2.0)) - wait_empty(tx_queue, "account creation and top-up") - - for contract_account in contract_accounts: - for test_account_idx in range(start_of_accounts, len(ACCOUNTS)): - # Initialize nonces for all real accounts. But we only want to do that for the first - # iteration... Otherwise there's a risk of a race. And O(n^2) doesn't help... - if contract_account == contract_accounts[0]: - ACCOUNTS[test_account_idx].refresh_nonce(nodes[0]) - tx_queue.add(InitFTAccount(contract_account, test_account_idx)) - wait_empty(tx_queue, "registeration of accounts with the FT contracts") - - for contract_account in contract_accounts: - for test_account_idx in range(start_of_accounts, len(ACCOUNTS)): - tx_queue.add( - TransferFT(contract_account, - contract_account, - test_account_idx, - how_much=1E8)) - wait_empty(tx_queue, "distribution of initial FT") - - transfers = 0 - while True: - sender_idx, receiver_idx = rng.sample(range(start_of_accounts, - len(ACCOUNTS)), - k=2) - ft_contract = rng.choice(contract_accounts) - tgas = int(args.tx_tgas) - tx_queue.add( - TransferFT(ft_contract, - sender_idx, - receiver_idx, - how_much=1, - tgas=tgas)) - transfers += 1 - if transfers % 10000 == 0: - logger.info( - f"{transfers} so far ({tx_queue.pending.value} pending)") - while tx_queue.pending.value >= MAX_INFLIGHT_TRANSACTIONS_PER_EXECUTOR * executors: - time.sleep(0.25) - - -def wait_empty(queue, why): - with queue.pending.get_lock(): - remaining = queue.pending.value - while remaining != 0: - logger.info(f"waiting for {why} ({remaining} remain)") - time.sleep(0.5) - with queue.pending.get_lock(): - remaining = queue.pending.value - logger.info(f"wait for {why} completed!") - - -if __name__ == "__main__": - main() diff --git a/pytest/tests/loadtest/locust/README.md b/pytest/tests/loadtest/locust/README.md index d5d3201993d..3eed9228d76 100644 --- a/pytest/tests/loadtest/locust/README.md +++ b/pytest/tests/loadtest/locust/README.md @@ -8,8 +8,7 @@ only about generating the load. ## Install ```sh -pip3 install locust -# Run in nearcore directory. +# Run in nearcore directory. Locust is installed as a part of these dependencies. pip3 install -r pytest/requirements.txt ``` @@ -54,11 +53,23 @@ hundred of users. In the Locust UI, check the "Workers" tab to see CPU and memory usage. If this approaches anything close to 100%, you should use more workers. -Luckily, locust has the ability to swarm the load generation across many processes. -To use it, start one process with the `--master` argument and as many as you -like with `--worker`. (If they run on different machines, you also need to -provide `--master-host` and `--master-port`, if running on the same machine it -will work automagically.) +Luckily, Locust has the ability to swarm the load generation across many processes. + +The simplest way to do this on a single machine is to use `--processes` argument: +```sh +locust -H 127.0.0.1:3030 \ + -f locustfiles/ft.py \ + --funding-key=$KEY \ + --processes 8 +``` + +This will spawn 8 Locust Python processes, each capable of fully utilizing one CPU core. +According to the current measurements, Locust on a single CPU core can send 500 transactions per +second, and this number linearly scales with the number of processes. + +To scale further to multiple machines, start one process with the `--master` argument and as many as +you like with `--worker`. (If they run on different machines, you also need to provide +`--master-host` and `--master-port`, if running on the same machine it will work automagically.) Start the master: diff --git a/pytest/tests/loadtest/locust/common/base.py b/pytest/tests/loadtest/locust/common/base.py index ce4d37cb256..6d697085de9 100644 --- a/pytest/tests/loadtest/locust/common/base.py +++ b/pytest/tests/loadtest/locust/common/base.py @@ -9,12 +9,13 @@ import logging import multiprocessing import pathlib -import requests +from geventhttpclient import Session import sys import threading import time import typing import unittest +from cachetools import cached, TTLCache, LRUCache sys.path.append(str(pathlib.Path(__file__).resolve().parents[4] / 'lib')) @@ -30,6 +31,10 @@ DEFAULT_TRANSACTION_TTL = timedelta(minutes=30) logger = new_logger(level=logging.WARN) +# This is used to make the specific tests wait for the do_on_locust_init function +# to initialize the funding account, before initializating the users. +INIT_DONE = threading.Event() + def is_key_error(exception): return isinstance(exception, KeyError) @@ -128,11 +133,18 @@ def args(self) -> typing.Union[dict, typing.List[dict]]: Return a single dict for `FunctionCall` but a list of dict for `MultiFunctionCall`. """ + @abc.abstractmethod + def attached_gas(self) -> int: + """ + How much gas will be attached to this function call. + """ + return 300 * TGAS + def sign(self, block_hash) -> transaction.SignedTransaction: return transaction.sign_function_call_transaction( self.sender.key, self.receiver_id, self.method, - json.dumps(self.args()).encode('utf-8'), 300 * TGAS, self.balance, - self.sender.use_nonce(), block_hash) + json.dumps(self.args()).encode('utf-8'), self.attached_gas(), + self.balance, self.sender.use_nonce(), block_hash) def sender_account(self) -> Account: return self.sender @@ -152,7 +164,7 @@ def __init__(self, def sign(self, block_hash) -> transaction.SignedTransaction: all_args = self.args() - gas = 300 * TGAS // len(all_args) + gas = self.attached_gas() // len(all_args) def create_action(args): return transaction.create_function_call_action( @@ -228,6 +240,11 @@ def sender_account(self) -> Account: return self.sender +@cached(cache=LRUCache(maxsize=100)) +def get_near_node_proxy(environment): + return NearNodeProxy(environment) + + class NearNodeProxy: """ Wrapper around a RPC node connection that tracks requests on locust. @@ -237,7 +254,6 @@ def __init__(self, environment): self.request_event = environment.events.request [url, port] = environment.host.rsplit(":", 1) self.node = cluster.RpcNode(url, port) - self.session = requests.Session() def send_tx_retry(self, tx: Transaction, locust_name) -> dict: """ @@ -289,6 +305,7 @@ def send_tx(self, tx: Transaction, locust_name: str) -> dict: "EXECUTED_OPTIMISTIC" }) evaluate_rpc_result(result.json()) + except TxUnknownError as err: # This means we time out in one way or another. # In that case, the stateless transaction validation was @@ -312,7 +329,9 @@ def send_tx(self, tx: Transaction, locust_name: str) -> dict: # using retrying lib here to poll until a response is ready self.poll_tx_result(meta, tx) except NearError as err: - logging.warn(f"marking an error {err.message}, {err.details}") + logging.warn( + f"marking an error for transaction '{locust_name}' (id={signed_tx.id}): {err.message}, {err.details}" + ) meta["exception"] = err meta["response_time"] = (time.perf_counter() - @@ -349,7 +368,9 @@ def send_tx_async(self, tx: Transaction, locust_name: str) -> dict: details=submit_response) meta["response"] = submit_response.content except NearError as err: - logging.warn(f"marking an error {err.message}, {err.details}") + logging.warn( + f"marking an error for transaction '{locust_name}' (id={signed_tx.id}): {err.message}, {err.details}" + ) meta["exception"] = err meta["response_time"] = (time.perf_counter() - @@ -359,6 +380,9 @@ def send_tx_async(self, tx: Transaction, locust_name: str) -> dict: self.request_event.fire(**meta) return meta + # It's ok to use a slightly out-of-date block hash and this avoids one additional RPC request on + # every transaction. + @cached(cache=TTLCache(maxsize=1, ttl=1)) def final_block_hash(self): return base58.b58decode( self.node.get_final_block()['result']['header']['hash']) @@ -382,8 +406,16 @@ def post_json(self, method: str, params: typing.Dict[str, str]): "id": "dontcare", "jsonrpc": "2.0" } - return self.session.post(url="http://%s:%s" % self.node.rpc_addr(), - json=j) + try: + # Create a new session each time to allow parallel requests through the same node proxy. + session = Session(connection_timeout=6, + network_timeout=9, + max_retries=5, + retry_delay=0.1) + return session.post(url="http://%s:%s" % self.node.rpc_addr(), + json=j) + except Exception as e: + raise RpcError(details=e) @retry(wait_fixed=500, stop_max_delay=DEFAULT_TRANSACTION_TTL / timedelta(milliseconds=1), @@ -541,22 +573,25 @@ def generate_account_id(cls, account_generator, id) -> str: def __init__(self, environment): super().__init__(environment) assert self.host is not None, "Near user requires the RPC node address" - self.node = NearNodeProxy(environment) + self.node = get_near_node_proxy(environment) self.id = NearUser.get_next_id() - user_suffix = f"{self.id}_run{environment.parsed_options.run_id}" - self.account_id = NearUser.generate_account_id( - environment.account_generator, user_suffix) + self.user_suffix = f"{self.id}_run{environment.parsed_options.run_id}" + self.account_generator = environment.account_generator def on_start(self): """ Called once per user, creating the account on chain """ + # Wait for NearUser.funding_account to be initialized. + INIT_DONE.wait() + self.account_id = NearUser.generate_account_id(self.account_generator, + self.user_suffix) self.account = Account(key.Key.from_random(self.account_id)) if not self.node.account_exists(self.account_id): - self.send_tx_retry( - CreateSubAccount(NearUser.funding_account, - self.account.key, - balance=NearUser.INIT_BALANCE)) + self.send_tx_retry(CreateSubAccount(NearUser.funding_account, + self.account.key, + balance=NearUser.INIT_BALANCE), + locust_name="Init NearUser") self.account.refresh_nonce(self.node.node) def send_tx(self, tx: Transaction, locust_name="generic send_tx"): @@ -624,6 +659,38 @@ def __init__( self.ak_nonce = ak_nonce +class ShardCongestedError(RpcError): + + def __init__( + self, + shard_id, + congestion_level, + ): + super().__init__( + message="Shard congested", + details= + f"Shard {shard_id} is currently at congestion level {congestion_level} and rejects new transactions" + ) + self.shard_id = shard_id + self.congestion_level = congestion_level + + +class ShardStuckError(RpcError): + + def __init__( + self, + shard_id, + missed_chunks, + ): + super().__init__( + message="Shard stuck", + details= + f"Shard {shard_id} missed {missed_chunks} chunks and rejects new transactions" + ) + self.shard_id = shard_id + self.missed_chunks = missed_chunks + + class TxError(NearError): def __init__(self, @@ -673,6 +740,14 @@ def evaluate_rpc_result(rpc_result): raise InvalidNonceError( err_description["InvalidNonce"]["tx_nonce"], err_description["InvalidNonce"]["ak_nonce"]) + elif "ShardCongested" in err_description: + raise ShardCongestedError( + err_description["ShardCongested"]["shard_id"], + err_description["ShardCongested"]["congestion_level"]) + elif "ShardStuck" in err_description: + raise ShardStuckError( + err_description["ShardStuck"]["shard_id"], + err_description["ShardStuck"]["missed_chunks"]) raise RpcError(details=rpc_result["error"]) result = rpc_result["result"] @@ -742,6 +817,7 @@ def init_account_generator(parsed_options): if parsed_options.shard_layout_file is not None: with open(parsed_options.shard_layout_file, 'r') as f: shard_layout = json.load(f) + shard_layout_version = "V1" if "V1" in shard_layout else "V0" elif parsed_options.shard_layout_chain_id is not None: if parsed_options.shard_layout_chain_id not in ['mainnet', 'testnet']: sys.exit( @@ -757,8 +833,32 @@ def init_account_generator(parsed_options): "shards_split_map": [[0, 1, 2, 3]], "to_parent_shard_map": [0, 0, 0, 0], "version": 1 + }, + "V2": { + "fixed_shards": [], + "boundary_accounts": [ + "aurora", "aurora-0", "kkuuue2akv_1630967379.near", + "tge-lockup.sweat" + ], + "shards_split_map": [[0, 1, 2, 3, 4]], + "to_parent_shard_map": [0, 0, 0, 0, 0], + "version": 2 + }, + "V3": { + "fixed_shards": [], + "boundary_accounts": [ + "aurora", + "aurora-0", + "game.hot.tg", + "kkuuue2akv_1630967379.near", + "tge-lockup.sweat", + ], + "shards_split_map": [[0, 1, 2, 3, 4, 5]], + "to_parent_shard_map": [0, 0, 0, 0, 0, 0], + "version": 3 } } + shard_layout_version = parsed_options.shard_layout_version.upper() else: shard_layout = { "V0": { @@ -766,13 +866,13 @@ def init_account_generator(parsed_options): "version": 0, }, } - - return AccountGenerator(shard_layout) + shard_layout_version = "V0" + return AccountGenerator(shard_layout, shard_layout_version) # called once per process before user initialization def do_on_locust_init(environment): - node = NearNodeProxy(environment) + node = get_near_node_proxy(environment) master_funding_key = key.Key.from_json_file( environment.parsed_options.funding_key) @@ -790,17 +890,14 @@ def do_on_locust_init(environment): # every worker needs a funding account to create its users, eagerly create them in the master if isinstance(environment.runner, runners.MasterRunner): num_funding_accounts = environment.parsed_options.max_workers - funding_balance = 10000 * NearUser.INIT_BALANCE + funding_balance = 100000 * NearUser.INIT_BALANCE - def create_account(id): - account_id = f"funds_worker_{id}.{master_funding_account.key.account_id}" - return Account(key.Key.from_seed_testonly(account_id)) + for index in range(num_funding_accounts): + account_id = f"funds_worker_{index}.{master_funding_account.key.account_id}" + account = Account(key.Key.from_seed_testonly(account_id)) + node.prepare_account(account, master_funding_account, + funding_balance, "create funding account") - funding_accounts = [ - create_account(id) for id in range(num_funding_accounts) - ] - node.prepare_accounts(funding_accounts, master_funding_account, - funding_balance, "create funding account") funding_account = master_funding_account elif isinstance(environment.runner, runners.WorkerRunner): worker_id = environment.runner.worker_index @@ -818,9 +915,6 @@ def create_account(id): environment.master_funding_account = master_funding_account -INIT_DONE = threading.Event() - - @events.init.add_listener def on_locust_init(environment, **kwargs): do_on_locust_init(environment) @@ -850,6 +944,12 @@ def _(parser): help= "chain ID whose shard layout we should consult when generating account IDs. Convenience option to avoid using --shard-layout-file for mainnet and testnet" ) + parser.add_argument( + "--shard-layout-version", + required=False, + help= + "Version of the shard layout. Only works with --shard-layout-chain-id. Should be one of V0, V1, V2, or V3." + ) parser.add_argument( "--run-id", default="", diff --git a/pytest/tests/loadtest/locust/common/ft.py b/pytest/tests/loadtest/locust/common/ft.py index 9630638f5c1..fc70fe79d01 100644 --- a/pytest/tests/loadtest/locust/common/ft.py +++ b/pytest/tests/loadtest/locust/common/ft.py @@ -1,3 +1,5 @@ +import logging +from concurrent import futures import random import string import sys @@ -8,6 +10,7 @@ sys.path.append(str(pathlib.Path(__file__).resolve().parents[4] / 'lib')) import key +from account import TGAS from common.base import Account, Deploy, NearNodeProxy, NearUser, FunctionCall, INIT_DONE @@ -52,7 +55,7 @@ def register_passive_user(self, node: NearNodeProxy, account: Account): """ Passive users are only used as receiver, not as signer. """ - node.send_tx_retry(InitFTAccount(self.account, account), + node.send_tx_async(InitFTAccount(self.account, account), locust_name="Init FT Account") self.registered_users.append(account.key.account_id) @@ -84,21 +87,30 @@ def create_passive_users(self, assert prefix_len > 4, f"user key {parent.key.account_id} is too long" chars = string.ascii_lowercase + string.digits - def create_account(): - prefix = ''.join(random.choices(chars, k=prefix_len)) + def create_account(i): + prefix = ''.join(random.Random(i).choices(chars, k=prefix_len)) account_id = f"{prefix}.{parent.key.account_id}" return Account(key.Key.from_seed_testonly(account_id)) - accounts = [create_account() for _ in range(num)] - node.prepare_accounts(accounts, - parent, - balance=0.3, - msg="create passive user") - # TODO: this could also be done in parallel, actually in very simple - # ways since there are no nonce conflicts (transactions are signed by - # different users) - for account in accounts: - self.register_passive_user(node, account) + with futures.ThreadPoolExecutor(max_workers=4) as executor: + batch_size = 500 + num_batches = (num + batch_size - 1) // batch_size + for i in range(num_batches): + accounts = [ + create_account(i) + for i in range(i * batch_size, min((i + 1) * + batch_size, num)) + ] + node.prepare_accounts(accounts, + parent, + balance=1, + msg="create passive user") + futures.wait( + executor.submit(self.register_passive_user, node, account) + for account in accounts) + logging.info( + f"{parent.key.account_id}: Processed batch {i + 1}/{num_batches}, created {(i + 1) * batch_size} users" + ) class TransferFT(FunctionCall): @@ -121,6 +133,15 @@ def args(self) -> dict: "amount": str(int(self.how_much)), } + def attached_gas(self) -> int: + """ + We overwrite this setting to minimize effects on congestion control that relies on attached + gas to determine the capacity of delayed receipt queues. See + https://near.zulipchat.com/#narrow/stream/295306-contract-runtime/topic/ft_transfer.20benchmark/near/448814523 + for more details. + """ + return 10 * TGAS + def sender_account(self) -> Account: return self.sender @@ -137,6 +158,12 @@ def args(self) -> dict: "total_supply": str(10**33) } + def attached_gas(self) -> int: + """ + Avoid attaching excess gas to prevent triggering false-positive congestion control. + """ + return 10 * TGAS + def sender_account(self) -> Account: return self.contract @@ -154,6 +181,12 @@ def __init__(self, contract: Account, account: Account): def args(self) -> dict: return {"account_id": self.account.key.account_id} + def attached_gas(self) -> int: + """ + Avoid attaching excess gas to prevent triggering false-positive congestion control. + """ + return 10 * TGAS + def sender_account(self) -> Account: return self.account @@ -166,19 +199,29 @@ def on_locust_init(environment, **kwargs): num_ft_contracts = environment.parsed_options.num_ft_contracts funding_account = NearUser.funding_account parent_id = funding_account.key.account_id + run_id = environment.parsed_options.run_id funding_account.refresh_nonce(node.node) environment.ft_contracts = [] # TODO: Create accounts in parallel for i in range(num_ft_contracts): - account_id = environment.account_generator.random_account_id( - parent_id, '_ft') - contract_key = key.Key.from_random(account_id) + if environment.parsed_options.fixed_contract_names: + account_id = f"ft{run_id}_{i}.{parent_id}" + contract_key = key.Key.from_seed_testonly(account_id) + else: + account_id = environment.account_generator.random_account_id( + parent_id, '_ft') + contract_key = key.Key.from_random(account_id) ft_account = Account(contract_key) ft_contract = FTContract(ft_account, ft_account, ft_contract_code) ft_contract.install(node, funding_account) + if environment.parsed_options.num_passive_users > 0: + ft_contract.create_passive_users( + environment.parsed_options.num_passive_users, node, + funding_account) environment.ft_contracts.append(ft_contract) + logging.info(f"Finished setup for account {i} on worker {parent_id}") # FT specific CLI args @@ -190,8 +233,18 @@ def _(parser): parser.add_argument( "--num-ft-contracts", type=int, - required=False, default=4, help= "How many different FT contracts to spawn from this worker (FT contracts are never shared between workers)" ) + parser.add_argument( + "--fixed-contract-names", + action='store_true', + help= + "Whether the names of FT contracts will deterministically based on worker id and run id." + ) + parser.add_argument( + "--num-passive-users", + type=int, + default=0, + help="Number of passive users to create in each FT contract.") diff --git a/pytest/tests/loadtest/locust/common/sharding.py b/pytest/tests/loadtest/locust/common/sharding.py index 2c1e9c0cd9a..45022484ae4 100644 --- a/pytest/tests/loadtest/locust/common/sharding.py +++ b/pytest/tests/loadtest/locust/common/sharding.py @@ -5,11 +5,13 @@ For account naming rules and conventions see https://nomicon.io/DataStructures/Account """ -import os -import sys +import logging import random import re import unittest +from configured_logger import new_logger + +logger = new_logger(level=logging.WARN) def char_range(lower, upper, upper_inclusive=True): @@ -276,17 +278,19 @@ def random_account_between(base_name, suffix, lower, upper): # Given a shard layout, generates accounts distributed evenly across the shards class AccountGenerator: - def __init__(self, shard_layout): - assert len(shard_layout) == 1 - assert 'V0' in shard_layout or 'V1' in shard_layout + def __init__(self, available_shard_layouts, shard_layout_version): + assert shard_layout_version in available_shard_layouts, "Shard layout version not found in available versions: " + str( + available_shard_layouts.keys()) + logger.info(f"Using shard layout version {shard_layout_version}") self.shard_map = {} # If the shard layout is V0, we can just skip this, and random_account_id() # will see an empty self.shard_map and generate a random prefix, which should # distribute the accounts evenly across shards in that case - shard_layout_v1 = shard_layout.get('V1') - if shard_layout_v1 is not None: + if shard_layout_version != "V0": + selected_shard_layout = available_shard_layouts.get( + shard_layout_version) # taken from a comment in core/account-id/src/lib.rs account_regex = re.compile( r'^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$') @@ -294,8 +298,8 @@ def __init__(self, shard_layout): # picking one at random, and not actually doing anything with the shard ID itself, but # add the right offset to the shard IDs below just for cleanliness, and in case we # want to print out shard IDs or something - shard_offset = len(shard_layout_v1['fixed_shards']) - accounts = shard_layout_v1['boundary_accounts'] + shard_offset = len(selected_shard_layout['fixed_shards']) + accounts = selected_shard_layout['boundary_accounts'] if len(accounts) == 0: self.shard_map[shard_offset] = (None, None) return diff --git a/pytest/tests/loadtest/locust/common/sweat.py b/pytest/tests/loadtest/locust/common/sweat.py index f6cf288842d..fb3fb50b097 100644 --- a/pytest/tests/loadtest/locust/common/sweat.py +++ b/pytest/tests/loadtest/locust/common/sweat.py @@ -231,21 +231,15 @@ def on_locust_init(environment, **kwargs): # on master, register oracles for workers if isinstance(environment.runner, locust.runners.MasterRunner): num_oracles = int(environment.parsed_options.max_workers) - oracle_accounts = [ - Account( - key.Key.from_seed_testonly( - worker_oracle_id(id, run_id, - environment.master_funding_account))) - for id in range(num_oracles) - ] - node.prepare_accounts(oracle_accounts, - environment.master_funding_account, 100000, - "create contract account") - for oracle in oracle_accounts: - id = oracle.key.account_id - environment.sweat.top_up(node, id) - environment.sweat.register_oracle(node, id) - node.send_tx_retry(SweatAddOracle(sweat_claim_account, id), + for index in range(num_oracles): + account_id = worker_oracle_id(index, run_id, + environment.master_funding_account) + account = Account(key.Key.from_seed_testonly(account_id)) + node.prepare_account(account, environment.master_funding_account, + 100000, "create contract account") + environment.sweat.top_up(node, account_id) + environment.sweat.register_oracle(node, account_id) + node.send_tx_retry(SweatAddOracle(sweat_claim_account, account_id), "add sweat.claim oracle") diff --git a/pytest/tests/loadtest/locust/locustfiles/ft-state-builder.py b/pytest/tests/loadtest/locust/locustfiles/ft-state-builder.py new file mode 100644 index 00000000000..b28f637c0c3 --- /dev/null +++ b/pytest/tests/loadtest/locust/locustfiles/ft-state-builder.py @@ -0,0 +1,47 @@ +""" +A workload to prepare the state for Fungible Token operations. + +Suggested run command: +``` +locust -H 127.0.0.1:3030 -f locustfiles/ft-state-builder.py --funding-key=$KEY --users 500 --headless +``` + +In particular: +- Not using a multi-worker setup, to avoid balance issues +- 500 users was the best performing number when testing on the own-mainnet-provided machine +""" + +import logging +import pathlib +import random +import sys + +sys.path.append(str(pathlib.Path(__file__).resolve().parents[4] / 'lib')) + +from configured_logger import new_logger +from locust import constant_throughput, task +from common.base import NearUser +from common.ft import TransferFT + +logger = new_logger(level=logging.WARN) + + +class FTStateBuilder(NearUser): + """ + Registers itself on an FT contract in the setup phase, then creates lots of passive users + """ + # Each Locust user will try to send one transaction per second. + # See https://docs.locust.io/en/stable/api.html#locust.wait_time.constant_throughput. + wait_time = constant_throughput(1.0) + + @task + def create_user(self): + self.ft.create_passive_users(100, self.node, self.funding_account) + + def on_start(self): + super().on_start() + self.ft = random.choice(self.environment.ft_contracts) + self.ft.register_user(self) + logger.debug( + f"{self.account_id} ready to use FT contract {self.ft.account.key.account_id}" + ) diff --git a/pytest/tests/loadtest/setup.py b/pytest/tests/loadtest/setup.py deleted file mode 100644 index abb7cb814e1..00000000000 --- a/pytest/tests/loadtest/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -import subprocess -import mocknet_helpers -import account -import key -import argparse -from os.path import join - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Setup loadtest') - - parser.add_argument('--home', type=str, required=True) - parser.add_argument('--num_accounts', type=int, default=5) - parser.add_argument('--host', type=str, default='127.0.0.1') - parser.add_argument('--account_id', type=str, default=None) - parser.add_argument('--contract_dir', - type=str, - default='pytest/tests/loadtest/contract') - args = parser.parse_args() - - print("Compiling contract") - subprocess.check_call(args=[ - "cargo", "build", "--target", "wasm32-unknown-unknown", "--release" - ], - cwd=args.contract_dir) - - for i in range(args.num_accounts): - account_name = args.account_id or f"shard{i}" - - shard_key = key.Key.from_json_file( - join(args.home, f"{account_name}_key.json")) - - base_block_hash = mocknet_helpers.get_latest_block_hash(addr=args.host) - nonce = mocknet_helpers.get_nonce_for_key(shard_key, addr=args.host) - - shard_account = account.Account(shard_key, - init_nonce=nonce, - base_block_hash=base_block_hash, - rpc_infos=[(args.host, "3030")]) - - shard_account.send_deploy_contract_tx( - join( - args.contract_dir, - "target/wasm32-unknown-unknown/release/loadtest_contract.wasm")) diff --git a/pytest/tests/mocknet/helpers/neard_runner.py b/pytest/tests/mocknet/helpers/neard_runner.py index f24449e685d..3789e33ab00 100644 --- a/pytest/tests/mocknet/helpers/neard_runner.py +++ b/pytest/tests/mocknet/helpers/neard_runner.py @@ -245,7 +245,11 @@ def is_traffic_generator(self): def save_data(self): with open(self.home_path('data.json'), 'w') as f: - json.dump(self.data, f) + json.dump(self.data, f, indent=2) + + def save_config(self): + with open(self.home_path('config.json'), 'w') as f: + json.dump(self.config, f, indent=2) def parse_binaries_config(self): if 'binaries' not in self.config: @@ -665,9 +669,58 @@ def do_ls_backups(self): with self.lock: return self.data.get('backups', {}) - def do_update_binaries(self): + # Updates the URL for the given epoch height or binary idx. adds a new one if the epoch height does not exit + def update_binaries_url(self, neard_binary_url, epoch_height, binary_idx): + if neard_binary_url is not None and ((epoch_height is None) + != (binary_idx is None)): + logging.info( + f'Updating binary list for height:{epoch_height} or idx:{binary_idx} with ' + f'url: {neard_binary_url}') + else: + logging.error( + f'Update binaries failed. Wrong params: url: {neard_binary_url}, height:{epoch_height}, idx:{binary_idx}' + ) + raise jsonrpc.exceptions.JSONRPCInvalidParams() + + if 'binaries' not in self.config: + self.config['binaries'] = [] + + if not isinstance(self.config['binaries'], list): + self.config['binaries'] = [] + + if epoch_height is not None: + binary = next((b for b in self.config['binaries'] + if b['epoch_height'] == epoch_height), None) + if binary: + binary['url'] = neard_binary_url + else: + self.config['binaries'].append({ + 'url': neard_binary_url, + 'epoch_height': epoch_height + }) + self.config['binaries'].sort( + key=lambda binary: binary['epoch_height']) + if binary_idx is not None: + binaries_number = len(self.config['binaries']) + if binary_idx >= binaries_number: + logging.error( + f'idx {binary_idx} is out of bounds for the binary list of length {binaries_number}' + ) + raise jsonrpc.exceptions.JSONRPCInvalidParams( + message= + f'Invalid binary idx. Out of bounds for list of length {binaries_number}' + ) + self.config['binaries'][binary_idx]['url'] = neard_binary_url + + def do_update_binaries(self, neard_binary_url, epoch_height, binary_idx): with self.lock: logging.info('update binaries') + if any(arg is not None + for arg in [neard_binary_url, epoch_height, binary_idx]): + self.update_binaries_url(neard_binary_url, epoch_height, + binary_idx) + self.save_config() + try: self.download_binaries(force=True) except ValueError as e: diff --git a/pytest/tests/mocknet/mirror.py b/pytest/tests/mocknet/mirror.py index 448e4f339a2..de5bd3bc551 100755 --- a/pytest/tests/mocknet/mirror.py +++ b/pytest/tests/mocknet/mirror.py @@ -2,7 +2,7 @@ """ """ -from argparse import ArgumentParser, BooleanOptionalAction +from argparse import ArgumentParser, Action import datetime import pathlib import json @@ -11,6 +11,7 @@ import re import sys import time +import numpy as np sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) @@ -433,6 +434,13 @@ def update_binaries_cmd(args, traffic_generator, nodes): nodes + to_list(traffic_generator)) +def amend_binaries_cmd(args, traffic_generator, nodes): + pmap( + lambda node: node.neard_runner_update_binaries( + args.neard_binary_url, args.epoch_height, args.binary_idx), + nodes + to_list(traffic_generator)) + + def run_remote_cmd(args, traffic_generator, nodes): targeted = nodes + to_list(traffic_generator) logger.info(f'Running cmd on {"".join([h.name() for h in targeted ])}') @@ -466,9 +474,32 @@ def filter_hosts(args, traffic_generator, nodes): logger.error(f'No hosts selected. Change filters and try again.') exit(1) + if args.select_partition is not None: + i, n = args.select_partition + + if len(nodes) < n and traffic_generator == None: + logger.error( + f'Partitioning {len(nodes)} nodes in {n} groups will result in empty groups.' + ) + exit(1) + nodes.sort(key=lambda node: node.name()) + nodes = np.array_split(nodes, n)[i - 1] + return traffic_generator, nodes +class ParseFraction(Action): + + def __call__(self, parser, namespace, values, option_string=None): + pattern = r"(\d+)/(\d+)" + match = re.match(pattern, values) + if not match: + parser.error(f"Invalid input '{values}'. Expected format 'i/n'.") + numerator = int(match.group(1)) + denominator = int(match.group(2)) + setattr(namespace, self.dest, (numerator, denominator)) + + if __name__ == '__main__': parser = ArgumentParser(description='Control a mocknet instance') parser.add_argument('--chain-id', type=str) @@ -483,6 +514,15 @@ def filter_hosts(args, traffic_generator, nodes): parser.add_argument('--host-filter', type=str, help='Filter through the selected nodes using regex.') + parser.add_argument('--select-partition', + action=ParseFraction, + type=str, + help=''' + Input should be in the form of "i/n" where 0 < i <= n. + Select a group of hosts based on the division provided. + For i/n, it will split the selected hosts into n groups and select the i-th group. + Use this if you want to target just a partition of the hosts.''' + ) subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', @@ -604,13 +644,43 @@ def filter_hosts(args, traffic_generator, nodes): # nearcore-release buildkite and urls in the following format without commit # but only with the branch name: # https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore/Linux//neard" - update_binaries_parser = subparsers.add_parser( - 'update-binaries', - help= - 'Update the neard binaries by re-downloading them. The same urls are used.' - ) + update_binaries_parser = subparsers.add_parser('update-binaries', + help=''' + Update the neard binaries by re-downloading them. The same urls are used. + If you plan to restart the network multiple times, it is recommended to use + URLs that only depend on the branch name. This way, every time you build, + you will not need to amend the URL but just run update-binaries.''') update_binaries_parser.set_defaults(func=update_binaries_cmd) + amend_binaries_parsers = subparsers.add_parser('amend-binaries', + help=''' + Add or override the neard URLs by specifying the epoch height or index if you have multiple binaries. + + If the network was started with 2 binaries, the epoch height for the second binary can be randomly assigned + on each host. Use caution when updating --epoch-height so that it will not add a binary in between the upgrade + window for another binary.''') + + amend_binaries_parsers.add_argument('--neard-binary-url', + type=str, + required=True, + help='URL to the neard binary.') + group = amend_binaries_parsers.add_mutually_exclusive_group(required=True) + group.add_argument('--epoch-height', + type=int, + help=''' + The epoch height where this binary will begin to run. + If a binary already exists on the host for this epoch height, the old one will be replaced. + Otherwise a new binary will be added with this epoch height. + ''') + group.add_argument('--binary-idx', + type=int, + help=''' + 0 based indexing. + The index in the binary list that you want to replace. + If the index does not exist on the host this operation will not do anything. + ''') + amend_binaries_parsers.set_defaults(func=amend_binaries_cmd) + run_cmd_parser = subparsers.add_parser('run-cmd', help='''Run the cmd on the hosts.''') run_cmd_parser.add_argument('--cmd', type=str) diff --git a/pytest/tests/mocknet/node_handle.py b/pytest/tests/mocknet/node_handle.py index 2d64143d786..ae9522f6916 100644 --- a/pytest/tests/mocknet/node_handle.py +++ b/pytest/tests/mocknet/node_handle.py @@ -140,8 +140,17 @@ def neard_runner_reset(self, backup_id=None): return self.neard_runner_jsonrpc('reset', params={'backup_id': backup_id}) - def neard_runner_update_binaries(self): - return self.neard_runner_jsonrpc('update_binaries') + def neard_runner_update_binaries(self, + neard_binary_url=None, + epoch_height=None, + binary_idx=None): + return self.neard_runner_jsonrpc( + 'update_binaries', + params={ + 'neard_binary_url': neard_binary_url, + 'epoch_height': epoch_height, + 'binary_idx': binary_idx, + }) def neard_update_config(self, key_value): return self.neard_runner_jsonrpc( diff --git a/pytest/tests/sanity/congestion_control.py b/pytest/tests/sanity/congestion_control.py index 930f24251a8..20bd61b2fef 100644 --- a/pytest/tests/sanity/congestion_control.py +++ b/pytest/tests/sanity/congestion_control.py @@ -15,7 +15,7 @@ sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) from configured_logger import logger -from cluster import init_cluster, load_config, spin_up_node, Key +from cluster import BaseNode, init_cluster, load_config, spin_up_node, Key from utils import load_test_contract, poll_blocks, wait_for_blocks from transaction import sign_create_account_with_full_access_key_and_balance_tx, sign_deploy_contract_tx, sign_function_call_tx @@ -24,6 +24,8 @@ ACCESS_KEY_NONCE_RANGE_MULTIPLIER = 1_000_000 +GOOD_FINAL_EXECUTION_STATUS = ['FINAL', 'EXECUTED', 'EXECUTED_OPTIMISTIC'] + # Shard layout with 5 roughly equal size shards for convenience. SHARD_LAYOUT = { "V1": { @@ -61,6 +63,9 @@ class CongestionControlTest(unittest.TestCase): + def setUp(self): + self.threads = [] + def tearDown(self): self.__stop_load() @@ -155,18 +160,22 @@ def __run_after_congestion(self, node): self.assertEqual(int(congestion_info['delayed_receipts_gas']), 0) self.assertEqual(congestion_info['receipt_bytes'], 0) - def __check_txs(self, node): + def __check_txs(self, node: BaseNode): logger.info("Checking transactions. This is slow.") accepted_count = 0 rejected_count = 0 + total = len(self.txs) + checked = 0 for (tx_sender, tx_hash) in self.txs: try: - result = node.get_tx(tx_hash, tx_sender) + # The transactions should be long done by now. Set a short + # timeout to speed up the test. + # TODO It would be better to check the transactions in parallel. + result = node.get_tx(tx_hash, tx_sender, timeout=1) status = result['result']['final_execution_status'] - self.assertIn(status, - ['FINAL', 'EXECUTED', 'EXECUTED_OPTIMISTIC']) + self.assertIn(status, GOOD_FINAL_EXECUTION_STATUS) status = result['result']['status'] self.assertIn('SuccessValue', status) @@ -175,15 +184,19 @@ def __check_txs(self, node): except: rejected_count += 1 + checked += 1 + if checked % 10 == 0: + logger.info( + f"Checking transactions under way, {checked}/{total}") + logger.info( f"Checking transactions done, total {len(self.txs)}, accepted {accepted_count}, rejected {rejected_count}" ) self.assertGreater(accepted_count, 0) self.assertGreater(rejected_count, 0) - def __start_load(self, node, accounts): + def __start_load(self, node: BaseNode, accounts): logger.info("Starting load threads") - self.threads = [] self.finished = False self.lock = threading.Lock() @@ -204,7 +217,7 @@ def __stop_load(self): for thread in self.threads: thread.join() - def __load(self, node, sender_account, target_account): + def __load(self, node: BaseNode, sender_account, target_account): logger.debug( f"Starting load thread {sender_account.account_id} -> {target_account.account_id}" ) @@ -223,7 +236,7 @@ def __load(self, node, sender_account, target_account): f"Stopped load thread {sender_account.account_id} -> {target_account.account_id}" ) - def __setup_node(self): + def __setup_node(self) -> BaseNode: logger.info("Setting up the node") epoch_length = 100 config = load_config() @@ -258,7 +271,7 @@ def __prepare_accounts(self): accounts.append(account_key) return accounts - def __create_accounts(self, node, accounts: list[Key]): + def __create_accounts(self, node: BaseNode, accounts: list[Key]): logger.info("Creating accounts") create_account_tx_list = [] @@ -269,7 +282,7 @@ def __create_accounts(self, node, accounts: list[Key]): self.__wait_for_txs(node, create_account_tx_list) - def __deploy_contracts(self, node, accounts: list[Key]): + def __deploy_contracts(self, node: BaseNode, accounts: list[Key]): logger.info("Deploying contracts") deploy_contract_tx_list = list() @@ -279,7 +292,7 @@ def __deploy_contracts(self, node, accounts: list[Key]): self.__wait_for_txs(node, deploy_contract_tx_list) - def __create_account(self, node, account_key, balance): + def __create_account(self, node: BaseNode, account_key, balance): block_hash = node.get_latest_block().hash_bytes new_signer_key = Key( account_key.account_id, @@ -300,7 +313,7 @@ def __create_account(self, node, account_key, balance): logger.debug(f"Create account {account_key.account_id}: {result}") return result['result'] - def __deploy_contract(self, node, account_key): + def __deploy_contract(self, node: BaseNode, account_key): logger.debug("Deploying contract.") block_hash = node.get_latest_block().hash_bytes @@ -317,7 +330,7 @@ def __deploy_contract(self, node, account_key): self.assertIn('result', result, result) return result['result'] - def __call_contract(self, node, sender: Key, receiver: Key): + def __call_contract(self, node: BaseNode, sender: Key, receiver: Key): logger.debug( f"Calling contract. {sender.account_id} -> {receiver.account_id}") @@ -341,20 +354,21 @@ def __call_contract(self, node, sender: Key, receiver: Key): self.assertIn('result', result, result) return result['result'] - def __wait_for_txs(self, node, tx_list: list[AccountId, TxHash]): + def __wait_for_txs(self, node: BaseNode, tx_list: list[AccountId, TxHash]): (height, _) = wait_for_blocks(node, count=3) self.nonce = ACCESS_KEY_NONCE_RANGE_MULTIPLIER * height + 1 for (tx_sender, tx_hash) in tx_list: result = node.get_tx(tx_hash, tx_sender) + self.assertIn('result', result, result) status = result['result']['final_execution_status'] - self.assertIn(status, ['FINAL', 'EXECUTED', 'EXECUTED_OPTIMISTIC']) + self.assertIn(status, GOOD_FINAL_EXECUTION_STATUS) status = result['result']['status'] self.assertIn('SuccessValue', status) - def __get_chunk(self, node, block_hash, shard_id): + def __get_chunk(self, node: BaseNode, block_hash, shard_id): result = node.json_rpc("chunk", { "block_id": block_hash, "shard_id": shard_id diff --git a/pytest/tests/sanity/congestion_control_genesis_bootstrap.py b/pytest/tests/sanity/congestion_control_genesis_bootstrap.py new file mode 100644 index 00000000000..e0946b5ff55 --- /dev/null +++ b/pytest/tests/sanity/congestion_control_genesis_bootstrap.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import unittest +import pathlib +import sys +import time + +sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) + +from configured_logger import logger +import cluster +import utils + +EPOCH_LENGTH = 5 +NUM_SHARDS = 4 + + +class CongestionControlBootstrapTest(unittest.TestCase): + """Tests congestion control bootstrapping in the case of node restart. + + It runs the chain multiple epochs so that garbage collection kicks in. + Then it restarts the nodes to ensure that congestion control bootstrapping does not fail.""" + + def test(self): + self.start_nodes() + self.check_congestion_info() + # Now wait until there are enough epochs to pass + # for GC to kick in and then restart the nodes. + self.wait_for_multiple_epochs() + self.restart_nodes() + self.check_congestion_info() + + def start_nodes(self): + self.nodes = cluster.start_cluster( + num_nodes=4, + num_observers=0, + num_shards=NUM_SHARDS, + config=None, + genesis_config_changes=[["epoch_length", EPOCH_LENGTH], + ["block_producer_kickout_threshold", 0], + ["chunk_producer_kickout_threshold", 0]], + client_config_changes={ + i: { + "tracked_shards": [0], + "gc_num_epochs_to_keep": 2 + } for i in range(4) + }) + utils.wait_for_blocks(self.nodes[0], count=3) + + def wait_for_multiple_epochs(self): + utils.wait_for_blocks(self.nodes[0], count=5 * EPOCH_LENGTH) + + def restart_nodes(self): + for i in range(len(self.nodes)): + self.nodes[i].kill() + time.sleep(2) + self.nodes[i].start(boot_node=None if i == 0 else self.nodes[0]) + + def check_congestion_info(self): + (_, block_hash) = self.nodes[0].get_latest_block() + for s in range(NUM_SHARDS): + result = self.nodes[0].json_rpc("chunk", { + "block_id": block_hash, + "shard_id": s + }) + self.assertIn('result', result, result) + chunk = result['result'] + congestion_info = chunk['header']['congestion_info'] + self.assertEqual(int(congestion_info['buffered_receipts_gas']), 0) + self.assertEqual(int(congestion_info['delayed_receipts_gas']), 0) + self.assertEqual(congestion_info['receipt_bytes'], 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/pytest/tests/sanity/db_migration.py b/pytest/tests/sanity/db_migration.py index d887efac60c..47a6d5dfaf2 100755 --- a/pytest/tests/sanity/db_migration.py +++ b/pytest/tests/sanity/db_migration.py @@ -5,24 +5,27 @@ Makes sure that the node can still produce blocks. """ -import json import logging -import os import sys -import time -import subprocess -import base58 import pathlib sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) import branches import cluster -from transaction import sign_deploy_contract_tx, sign_function_call_tx +from transaction import sign_deploy_contract_tx, sign_function_call_tx, sign_staking_tx import utils logging.basicConfig(level=logging.INFO) +NUM_SHARDS = 4 +EPOCH_LENGTH = 5 + +# Config to track all shards. +node_config = { + "tracked_shards": list(range(NUM_SHARDS)), +} + def deploy_contract(node): hash_ = node.get_latest_block().hash_bytes @@ -51,6 +54,36 @@ def send_some_tx(node): utils.wait_for_blocks(node, count=3) +# Unstake and restake validator running `node` to ensure that some validator +# kickout is recorded on DB. +# Reproduces issue #11569. +def unstake_and_stake(node, tx_sender_node): + account = tx_sender_node.get_account(node.signer_key.account_id)['result'] + full_balance = int(account['amount']) + int(account['locked']) + + logging.info(f'Unstaking {node.signer_key.account_id}...') + nonce = tx_sender_node.get_nonce_for_pk(node.signer_key.account_id, + node.signer_key.pk) + 10 + + hash_ = tx_sender_node.get_latest_block().hash_bytes + tx = sign_staking_tx(node.signer_key, node.validator_key, 0, nonce, hash_) + + nonce += 10 + res = tx_sender_node.send_tx_and_wait(tx, timeout=15) + assert 'error' not in res, res + assert 'Failure' not in res['result']['status'], res + utils.wait_for_blocks(tx_sender_node, count=EPOCH_LENGTH * 2) + + logging.info(f'Restaking {node.signer_key.account_id}...') + tx = sign_staking_tx(node.signer_key, node.validator_key, full_balance // 2, + nonce, hash_) + nonce += 10 + res = tx_sender_node.send_tx_and_wait(tx, timeout=15) + assert 'error' not in res, res + assert 'Failure' not in res['result']['status'], res + utils.wait_for_blocks(tx_sender_node, count=EPOCH_LENGTH * 2) + + def main(): executables = branches.prepare_ab_test() node_root = utils.get_near_tempdir('db_migration', clean=True) @@ -58,35 +91,31 @@ def main(): logging.info(f"The near root is {executables.stable.root}...") logging.info(f"The node root is {node_root}...") - # Init local node - subprocess.call(( - executables.stable.neard, - "--home=%s" % node_root, - "init", - "--fast", - )) - - # Adjust changes required since #7486. This is needed because current - # stable release populates the deprecated migration configuration options. - # TODO(mina86): Remove this once we get stable release which doesn’t - # populate those fields by default. - config_path = node_root / 'config.json' - data = json.loads(config_path.read_text(encoding='utf-8')) - data.pop('db_migration_snapshot_path', None) - data.pop('use_db_migration_snapshot', None) - config_path.write_text(json.dumps(data), encoding='utf-8') - - # Run stable node for few blocks. - logging.info("Starting the stable node...") config = executables.stable.node_config() - node = cluster.spin_up_node(config, executables.stable.root, str(node_root), - 0) + logging.info("Starting stable nodes...") + nodes = cluster.start_cluster( + 2, + 0, + NUM_SHARDS, + config, + [['epoch_length', EPOCH_LENGTH], [ + "block_producer_kickout_threshold", 0 + ], ["chunk_producer_kickout_threshold", 0]], + # Make sure nodes track all shards to: + # 1. Avoid state sync after restaking + # 2. Respond to all view queries + { + 0: node_config, + 1: node_config, + }) + node = nodes[0] logging.info("Running the stable node...") - utils.wait_for_blocks(node, count=20) + utils.wait_for_blocks(node, count=EPOCH_LENGTH) logging.info("Blocks are being produced, sending some tx...") deploy_contract(node) send_some_tx(node) + unstake_and_stake(nodes[1], node) node.kill() @@ -95,25 +124,24 @@ def main(): # Run new node and verify it runs for a few more blocks. logging.info("Starting the current node...") - config = executables.current.node_config() node.near_root = executables.current.root node.binary_name = executables.current.neard node.start(boot_node=node) logging.info("Running the current node...") - utils.wait_for_blocks(node, count=20) + utils.wait_for_blocks(node, count=EPOCH_LENGTH * 4) logging.info("Blocks are being produced, sending some tx...") send_some_tx(node) logging.info( - "Currnet node has produced blocks... Stopping the current node... ") + "Current node has produced blocks... Stopping the current node... ") node.kill() logging.info("Restarting the current node...") node.start(boot_node=node) - utils.wait_for_blocks(node, count=20) + utils.wait_for_blocks(node, count=EPOCH_LENGTH * 4) if __name__ == "__main__": diff --git a/pytest/tests/sanity/memtrie_disktrie_switch.py b/pytest/tests/sanity/memtrie_disktrie_switch.py new file mode 100644 index 00000000000..ffddd4a31d3 --- /dev/null +++ b/pytest/tests/sanity/memtrie_disktrie_switch.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +# Spins up 4 validating nodes and 1 non-validating node. There are four shards in this test. +# Tests the following scenario and checks if the network can progress over a few epochs. +# 1. Starts with memtries enabled. +# 2. Restarts 2 of the validator nodes with memtries disabled. +# 3. Restarts the remaining 2 nodes with memtries disabled. +# Sends random transactions between shards at each step. + +import unittest +import pathlib +import random +import sys +import time + +sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) + +from configured_logger import logger +import cluster +import key +import state_sync_lib +import transaction +import utils + +EPOCH_LENGTH = 10 + +ONE_NEAR = 10**24 +TGAS = 10**12 + +GOOD_FINAL_EXECUTION_STATUS = ['FINAL', 'EXECUTED', 'EXECUTED_OPTIMISTIC'] + +# Shard layout with 5 roughly equal size shards for convenience. +SHARD_LAYOUT = { + "V1": { + "boundary_accounts": [ + "fff", + "kkk", + "ppp", + "uuu", + ], + "version": 2, + "shards_split_map": [], + "to_parent_shard_map": [], + } +} + +NUM_SHARDS = len(SHARD_LAYOUT["V1"]["boundary_accounts"]) + 1 + +ALL_ACCOUNTS = [ + "aaa.test0", + "ggg.test0", + "lll.test0", + "rrr.test0", + "vvv.test0", +] + +TxHash = str +AccountId = str + + +def random_u64(): + return bytes(random.randint(0, 255) for _ in range(8)) + + +class MemtrieDiskTrieSwitchTest(unittest.TestCase): + + def setUp(self): + self.nonces = {} + self.keys = [] + self.txs = [] + + def test(self): + + node_config_dump, node_config_sync = state_sync_lib.get_state_sync_configs_pair( + ) + + # Validator node configs: Enable single-shard tracking with memtries enabled. + node_config_sync["tracked_shards"] = [] + node_config_sync["store.load_mem_tries_for_tracked_shards"] = True + configs = {x: node_config_sync for x in range(4)} + + # Dumper node config: Enable tracking all shards with memtries enabled. + node_config_dump["tracked_shards"] = [0] + node_config_dump["store.load_mem_tries_for_tracked_shards"] = True + configs[4] = node_config_dump + + self.nodes = cluster.start_cluster( + num_nodes=4, + num_observers=1, + num_shards=NUM_SHARDS, + config=None, + genesis_config_changes=[ + ["epoch_length", EPOCH_LENGTH], ["shard_layout", SHARD_LAYOUT], + ["shuffle_shard_assignment_for_chunk_producers", True], + ["block_producer_kickout_threshold", 0], + ["chunk_producer_kickout_threshold", 0] + ], + client_config_changes=configs) + self.assertEqual(5, len(self.nodes)) + + # Use the dumper node as the RPC node for sending the transactions. + self.rpc_node = self.nodes[4] + + self.__wait_for_blocks(3) + + self.__create_accounts() + + self.__deploy_contracts() + + target_height = self.__next_target_height(num_epochs=1) + logger.info( + f"Step 1: Running with memtries enabled until height {target_height}" + ) + self.__random_workload_until(target_height) + + target_height = self.__next_target_height(num_epochs=1) + logger.info( + f"Step 2: Restarting nodes with memtries disabled until height {target_height}" + ) + self.__restart_nodes(enable_memtries=False) + self.__random_workload_until(target_height) + + target_height = self.__next_target_height(num_epochs=1) + logger.info( + f"Step 3: Restarting nodes with memtries enabled until height {target_height}" + ) + self.__restart_nodes(enable_memtries=True) + self.__random_workload_until(target_height) + + self.__wait_for_txs(self.txs, assert_all_accepted=False) + logger.info("Test ended") + + def __next_target_height(self, num_epochs): + """Returns a next target height until which we will send the transactions.""" + current_height = self.__wait_for_blocks(1) + stop_height = random.randint(1, EPOCH_LENGTH) + return current_height + num_epochs * EPOCH_LENGTH + stop_height + + def next_nonce(self, signer_key): + """Returns the next nonce to use for sending transactions for the given signing key.""" + assert signer_key in self.nonces + nonce = self.nonces[signer_key] + self.nonces[signer_key] = nonce + 42 + return nonce + + def __restart_nodes(self, enable_memtries): + """Stops and restarts the nodes with the config that enables/disables memtries. + + It restarts only the validator nodes and does NOT restart the RPC node.""" + boot_node = self.rpc_node + for i in range(0, 4): + self.nodes[i].kill() + time.sleep(2) + self.nodes[i].change_config( + {"store.load_mem_tries_for_tracked_shards": enable_memtries}) + self.nodes[i].start(boot_node=None if i == 0 else boot_node) + + def __random_workload_until(self, target_height): + """Generates traffic to make transfers between accounts.""" + last_height = -1 + while True: + last_block = self.rpc_node.get_latest_block() + height = last_block.height + if height > target_height: + break + if height != last_height: + logger.info( + f'@{height}, epoch_height: {state_sync_lib.approximate_epoch_height(height, EPOCH_LENGTH)}' + ) + last_height = height + last_block_hash = last_block.hash_bytes + if random.random() < 0.5: + # Make a transfer between accounts. + # The goal is to generate cross-shard receipts. + from_account_key = random.choice(self.account_keys) + to_account_id = random.choice([ + account_key.account_id + for account_key in self.account_keys + if account_key.account_id != from_account_key.account_id + ] + ["near"]) + payment_tx = transaction.sign_payment_tx( + from_account_key, to_account_id, 1, + self.next_nonce(from_account_key), last_block_hash) + result = self.rpc_node.send_tx(payment_tx) + assert 'result' in result and 'error' not in result, ( + 'Expected "result" and no "error" in response, got: {}'. + format(result)) + logger.debug("Transfer: {}".format(result)) + tx_hash = result['result'] + self.txs.append((from_account_key.account_id, tx_hash)) + elif len(self.keys) > 10 and random.random() < 0.5: + # Do some storage reads, but only if we have enough keys populated. + key = self.keys[random.randint(0, len(self.keys) - 1)] + for account_key in self.account_keys: + tx = transaction.sign_function_call_tx( + account_key, account_key.account_id, + 'read_value', key, 300 * TGAS, 0, + self.next_nonce(account_key), last_block_hash) + result = self.rpc_node.send_tx(tx) + assert 'result' in result and 'error' not in result, ( + 'Expected "result" and no "error" in response, got: {}'. + format(result)) + logger.debug("Read value: {}".format(result)) + tx_hash = result['result'] + self.txs.append((account_key.account_id, tx_hash)) + else: + # Generate some data for storage reads + key = random_u64() + self.keys.append(key) + for account_key in self.account_keys: + tx = transaction.sign_function_call_tx( + account_key, account_key.account_id, 'write_key_value', + key + random_u64(), 300 * TGAS, 0, + self.next_nonce(account_key), last_block_hash) + result = self.rpc_node.send_tx(tx) + assert 'result' in result and 'error' not in result, ( + 'Expected "result" and no "error" in response, got: {}'. + format(result)) + logger.debug("Wrote value: {}".format(result)) + tx_hash = result['result'] + self.txs.append((account_key.account_id, tx_hash)) + time.sleep(0.5) + + def __deploy_contracts(self): + """Deploys test contract for each test account. + + Waits for the deploy-contract transactions to complete.""" + deploy_contract_tx_list = [] + for account_key in self.account_keys: + contract = utils.load_test_contract() + last_block_hash = self.rpc_node.get_latest_block().hash_bytes + deploy_contract_tx = transaction.sign_deploy_contract_tx( + account_key, contract, self.next_nonce(account_key), + last_block_hash) + result = self.rpc_node.send_tx(deploy_contract_tx) + assert 'result' in result and 'error' not in result, ( + 'Expected "result" and no "error" in response, got: {}'.format( + result)) + tx_hash = result['result'] + deploy_contract_tx_list.append((account_key.account_id, tx_hash)) + logger.info( + f"Deploying contract for account: {account_key.account_id}, tx: {tx_hash}" + ) + self.__wait_for_txs(deploy_contract_tx_list) + + def __create_accounts(self): + """Creates the test accounts. + + Waits for the create-account transactions to complete.""" + account_keys = [] + for account_id in ALL_ACCOUNTS: + account_key = key.Key.from_random(account_id) + account_keys.append(account_key) + + # Use the first validator node to sign the transactions. + signer_key = self.nodes[0].signer_key + # Update nonce of the signer account using the access key nonce. + signer_nonce = self.rpc_node.get_nonce_for_pk(signer_key.account_id, + signer_key.pk) + 42 + + create_account_tx_list = [] + for account_key in account_keys: + tx_hash = self.__create_account(account_key, 1000 * ONE_NEAR, + signer_key, signer_nonce) + signer_nonce += 1 + create_account_tx_list.append((signer_key.account_id, tx_hash)) + logger.info( + f"Creating account: {account_key.account_id}, tx: {tx_hash}") + self.__wait_for_txs(create_account_tx_list) + + # Update nonces for the newly created accounts using the access key nonces. + for account_key in account_keys: + nonce = self.rpc_node.get_nonce_for_pk(account_key.account_id, + account_key.pk) + 42 + self.nonces[account_key] = nonce + + self.account_keys = account_keys + + def __create_account(self, account_key, balance, signer_key, signer_nonce): + block_hash = self.rpc_node.get_latest_block().hash_bytes + new_signer_key = key.Key( + account_key.account_id, + account_key.pk, + account_key.sk, + ) + create_account_tx = transaction.sign_create_account_with_full_access_key_and_balance_tx( + signer_key, + account_key.account_id, + new_signer_key, + balance, + signer_nonce, + block_hash, + ) + result = self.rpc_node.send_tx(create_account_tx) + self.assertIn('result', result, result) + tx_hash = result['result'] + return tx_hash + + def __wait_for_txs(self, + tx_list: list[(AccountId, TxHash)], + assert_all_accepted=True): + """Waits for the transactions to be accepted. + + If assert_all_accepted is True, it will assert that all transactions were accepted. + Otherwise, it asserts that at least 1 of the transactions were accepted.""" + self.assertGreater(len(tx_list), 0) + self.__wait_for_blocks(3) + logger.info(f"Checking status of {len(tx_list)} transactions") + accepted = 0 + rejected = 0 + for (tx_sender, tx_hash) in tx_list: + if self.__get_tx_status(tx_hash, tx_sender): + accepted += 1 + if not assert_all_accepted: + break + else: + rejected += 1 + if assert_all_accepted: + self.assertEqual(accepted, len(tx_list)) + else: + self.assertGreater(accepted, 0) + + def __get_tx_status(self, tx_hash, tx_sender) -> bool: + """Checks the status of the transaction and returns true if it is accepted.""" + result = self.rpc_node.get_tx(tx_hash, tx_sender, timeout=10) + if 'result' not in result: + self.assertIn('error', result, result) + return False + + status = result['result']['final_execution_status'] + self.assertIn(status, GOOD_FINAL_EXECUTION_STATUS, result) + + status = result['result']['status'] + self.assertIn('SuccessValue', status, result) + + return True + + def __wait_for_blocks(self, num_blocks): + height, _ = utils.wait_for_blocks(self.rpc_node, count=num_blocks) + return height + + +if __name__ == '__main__': + unittest.main() diff --git a/pytest/tests/sanity/rpc_tx_status.py b/pytest/tests/sanity/rpc_tx_status.py index 946d5e49b37..99720eb0449 100755 --- a/pytest/tests/sanity/rpc_tx_status.py +++ b/pytest/tests/sanity/rpc_tx_status.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -import base58 -import json import struct import sys import pathlib @@ -52,7 +50,7 @@ def test_tx_status(nodes, *, nonce_offset: int = 0): signer_key, signer_key.account_id, 'write_key_value', struct.pack(' int: logger.info(f'Got protocol {test_proto} in testnet release {test_release}.') logger.info(f'Got protocol {head_proto} on master branch.') - ok = (head_proto in (test_proto, test_proto + 1) and - test_proto in (main_proto, main_proto + 1)) + if head_proto == 69: + # In the congestion control and stateless validation release allow + # increasing the protocol version by 2. + ok = (head_proto in (test_proto, test_proto + 1, test_proto + 2) and + test_proto in (main_proto, main_proto + 1, main_proto + 2)) + else: + # Otherwise only allow increasing the protocol version by 1. + ok = (head_proto in (test_proto, test_proto + 1) and + test_proto in (main_proto, main_proto + 1)) assert ok, ('If changed, protocol version of a new release can increase by ' 'at most one.') diff --git a/pytest/tests/sanity/validator_switch_key_quick.py b/pytest/tests/sanity/validator_switch_key_quick.py new file mode 100755 index 00000000000..bf693ca8ff5 --- /dev/null +++ b/pytest/tests/sanity/validator_switch_key_quick.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# Starts three validating nodes and one non-validating node +# Set a new validator key that has the same account id as one of +# the validating nodes. Stake that account with the new key +# and make sure that the network doesn't stall even after +# the non-validating node becomes a validator. + +import unittest +import sys, time +import pathlib + +sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / 'lib')) + +from configured_logger import logger +from cluster import start_cluster +from utils import wait_for_blocks + +EPOCH_LENGTH = 20 +TIMEOUT = 100 + + +class ValidatorSwitchKeyQuickTest(unittest.TestCase): + + def test_validator_switch_key_quick(self): + # It is important for the non-validating node to already track shards + # that it will be assigned to when becoming a validator. + config_map = { + 2: { + "tracked_shadow_validator": "test0", + "store.load_mem_tries_for_tracked_shards": True, + } + } + + # Key will be moved from old_validator to new_validator, + # while the other_validator remains untouched. + [ + other_validator, + old_validator, + new_validator, + ] = start_cluster(2, 1, 3, None, + [["epoch_length", EPOCH_LENGTH], + ["block_producer_kickout_threshold", 10], + ["chunk_producer_kickout_threshold", 10]], + config_map) + wait_for_blocks(old_validator, count=5) + + new_validator.reset_validator_key(other_validator.validator_key) + other_validator.kill() + new_validator.reload_updateable_config() + new_validator.stop_checking_store() + wait_for_blocks(old_validator, count=2) + + block = old_validator.get_latest_block() + max_height = block.height + 4 * EPOCH_LENGTH + target_height = max_height - EPOCH_LENGTH // 2 + start_time = time.time() + + while True: + self.assertLess(time.time() - start_time, TIMEOUT, + 'Validators got stuck') + + info = old_validator.json_rpc('validators', 'latest') + next_validators = info['result']['next_validators'] + account_ids = [v['account_id'] for v in next_validators] + # We copied over 'test0' validator key, along with validator account ID. + # Therefore, despite nodes[0] being stopped, 'test0' still figures as active validator. + self.assertEqual(sorted(account_ids), ['test0', 'test1']) + + last_block_per_node = [ + new_validator.get_latest_block(), + old_validator.get_latest_block() + ] + height_per_node = list( + map(lambda block: block.height, last_block_per_node)) + logger.info(height_per_node) + + self.assertLess(max(height_per_node), max_height, + 'Nodes are not synced') + + synchronized = True + for i, node in enumerate([new_validator, old_validator]): + try: + node.get_block(last_block_per_node[1 - i].hash) + except Exception: + synchronized = False + break + + # Both validators should be synchronized + logger.info(f'Synchronized {synchronized}') + if synchronized and height_per_node[0] > target_height: + # If nodes are synchronized and the current height is close to `max_height` we can finish. + return + + wait_for_blocks(old_validator, count=1) + + +if __name__ == '__main__': + unittest.main() diff --git a/runtime/near-test-contracts/build.rs b/runtime/near-test-contracts/build.rs index 8120249eb47..6e8fbf842ca 100644 --- a/runtime/near-test-contracts/build.rs +++ b/runtime/near-test-contracts/build.rs @@ -1,8 +1,16 @@ +/// This script is used to build the contracts and copy the wasm files to the +/// `res` directory. +/// +/// It writes a few logs with the `debug` prefix. Those are ignored by cargo (as +/// any other messages with prefix other than `cargo::`) but can be seen in the +/// build logs. use std::env; use std::process::Command; type Error = Box; +const TEST_FEATURES_ENV: &str = "CARGO_FEATURE_TEST_FEATURES"; + fn main() { if let Err(err) = try_main() { eprintln!("{}", err); @@ -12,7 +20,11 @@ fn main() { fn try_main() -> Result<(), Error> { let mut test_contract_features = vec!["latest_protocol"]; - if env::var("CARGO_FEATURE_TEST_FEATURES").is_ok() { + + let test_features = &env::var(TEST_FEATURES_ENV); + println!("cargo:rerun-if-env-changed={TEST_FEATURES_ENV}"); + println!("debug: test_features = {test_features:?}"); + if test_features.is_ok() { test_contract_features.push("test_features"); } @@ -40,20 +52,28 @@ fn try_main() -> Result<(), Error> { Ok(()) } +/// build the contract and copy the wasm file to the `res` directory fn build_contract(dir: &str, args: &[&str], output: &str) -> Result<(), Error> { let target_dir = out_dir(); + // build the contract let mut cmd = cargo_build_cmd(&target_dir); cmd.args(args); cmd.current_dir(dir); check_status(cmd)?; - let src = - target_dir.join(format!("wasm32-unknown-unknown/release/{}.wasm", dir.replace('-', "_"))); - std::fs::copy(&src, format!("./res/{}.wasm", output)) - .map_err(|err| format!("failed to copy `{}`: {}", src.display(), err))?; + // copy the wasm file to the `res` directory + let target_path = format!("wasm32-unknown-unknown/release/{}.wasm", dir.replace('-', "_")); + let from = target_dir.join(target_path); + let to = format!("./res/{}.wasm", output); + let copy_result = std::fs::copy(&from, &to); + copy_result.map_err(|err| format!("failed to copy `{}`: {}", from.display(), err))?; + println!("cargo:rerun-if-changed=./{}/src/lib.rs", dir); println!("cargo:rerun-if-changed=./{}/Cargo.toml", dir); + + println!("debug: from = {from:?}, to = {to:?}"); + Ok(()) } @@ -73,6 +93,7 @@ fn cargo_build_cmd(target_dir: &std::path::Path) -> Command { } fn check_status(mut cmd: Command) -> Result<(), Error> { + println!("debug: running command: {cmd:?}"); cmd.status() .map_err(|err| format!("command `{cmd:?}` failed to run: {err}")) .and_then(|status| { diff --git a/runtime/near-test-contracts/estimator-contract/Cargo.toml b/runtime/near-test-contracts/estimator-contract/Cargo.toml index 765ac4144fe..19c7cf88ddf 100644 --- a/runtime/near-test-contracts/estimator-contract/Cargo.toml +++ b/runtime/near-test-contracts/estimator-contract/Cargo.toml @@ -21,4 +21,9 @@ panic = "abort" members = [] [features] -nightly = [] +nightly = [ + "nightly_protocol", + "protocol_feature_bls12381", +] +nightly_protocol = [] +protocol_feature_bls12381 = [] diff --git a/runtime/near-test-contracts/estimator-contract/src/lib.rs b/runtime/near-test-contracts/estimator-contract/src/lib.rs index 863a5ca11da..826b0fe5a74 100644 --- a/runtime/near-test-contracts/estimator-contract/src/lib.rs +++ b/runtime/near-test-contracts/estimator-contract/src/lib.rs @@ -40,6 +40,16 @@ extern "C" { fn alt_bn128_g1_multiexp(value_len: u64, value_ptr: u64, register_id: u64); fn alt_bn128_g1_sum(value_len: u64, value_ptr: u64, register_id: u64); fn alt_bn128_pairing_check(value_len: u64, value_ptr: u64) -> u64; + fn bls12381_p1_sum(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_p2_sum(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_g1_multiexp(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_g2_multiexp(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_map_fp_to_g1(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_map_fp2_to_g2(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_pairing_check(value_len: u64, value_ptr: u64) -> u64; + fn bls12381_p1_decompress(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn bls12381_p2_decompress(value_len: u64, value_ptr: u64, register_id: u64) -> u64; + fn random_seed(register_id: u64); fn sha256(value_len: u64, value_ptr: u64, register_id: u64); fn keccak256(value_len: u64, value_ptr: u64, register_id: u64); @@ -744,6 +754,264 @@ pub unsafe fn alt_bn128_pairing_check_10_10() { } } +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p1_sum_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_p1_sum( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p1_sum_50_100() { + let buffer: [[u8; 2*97]; 25] = [[0, 18, 25, 108, 90, 67, 214, 146, 36, 216, 113, 51, 137, 40, 95, 38, 185, 143, 134, 238, 145, 10, 179, 221, 102, 142, 65, 55, 56, 40, 32, 3, 204, 91, 115, 87, 175, 154, 122, 245, 75, 183, 19, 214, 34, 85, 232, 15, 86, 6, 186, 129, 2, 191, 190, 234, 68, 22, 183, 16, 199, 62, 140, 206, 48, 50, 195, 28, 98, 105, 196, 73, 6, 248, 172, 79, 120, 116, 206, 153, 251, 23, 85, 153, 146, 72, 101, 40, 150, 56, 132, 206, 66, 154, 153, 47, 238, + 0, 0, 1, 16, 16, 152, 245, 195, 152, 147, 118, 87, 102, 175, 69, 18, 160, 199, 78, 27, 184, 155, 199, 230, 253, 241, 78, 62, 115, 55, 210, 87, 204, 15, 148, 101, 129, 121, 216, 51, 32, 185, 159, 49, 255, 148, 205, 43, 172, 3, 225, 169, 249, 244, 76, 162, 205, 171, 79, 67, 161, 163, 238, 52, 112, 253, 249, 11, 47, 194, 40, 235, 59, 112, 159, 205, 114, 240, 20, 131, 138, 200, 42, 109, 121, 122, 238, 254, 217, 160, 128, 75, 34, 237, 28, 232, 247]; 25]; + + for _ in 0..100 { + assert_eq!(bls12381_p1_sum( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p2_sum_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_p2_sum( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p2_sum_50_100() { + let buffer: [[u8; 2*193]; 25] = [ + [0, 12, 199, 10, 88, 127, 70, 82, 3, 157, 129, 23, 182, 16, 56, 88, 173, 205, 151, 40, 246, 174, 190, 35, 5, 120, 56, 154, 98, 218, 0, 66, 183, 98, 59, 28, 4, 54, 115, 79, 70, 60, 253, 209, 135, 210, 9, 3, 36, 24, 192, 173, 166, 53, 27, 112, 102, 31, 5, 51, 101, 222, 174, 86, 145, 7, 152, 189, 42, 206, 110, 43, 246, 186, 65, 146, 209, 162, 41, 150, 127, 106, 246, 202, 28, 154, 138, 17, 235, 192, 162, 50, 52, 78, 224, 246, 214, 7, 155, 165, 13, 37, 17, 99, 27, 32, 182, 214, 243, 132, 30, 97, 110, 157, 17, 182, 142, 195, 54, 140, 214, 1, 41, 217, 212, 120, 122, 181, 108, 78, 145, 69, 163, 137, 39, 229, 28, 156, 214, 39, 29, 73, 61, 147, 136, 9, 245, 11, 215, 190, 237, 178, 51, 40, 129, 143, 159, 253, 175, 219, 109, 166, 164, 221, 128, 197, 169, 4, 138, 184, 177, 84, 223, 60, 173, 147, 140, 206, 222, 130, 159, 17, 86, 247, 105, 217, 225, 73, 121, 30, 142, 12, 217, + 0, 9, 174, 177, 12, 55, 43, 94, 241, 1, 6, 117, 198, 164, 118, 47, 218, 51, 99, 100, 137, 194, 59, 88, 28, 117, 34, 5, 137, 175, 188, 12, 196, 98, 73, 249, 33, 238, 160, 45, 209, 183, 97, 224, 54, 255, 219, 174, 34, 25, 47, 165, 216, 115, 47, 249, 243, 142, 11, 28, 241, 46, 173, 253, 38, 8, 240, 199, 163, 154, 206, 215, 116, 104, 55, 131, 58, 226, 83, 187, 87, 239, 156, 13, 152, 164, 182, 158, 235, 41, 80, 144, 25, 23, 233, 157, 30, 23, 72, 130, 205, 211, 85, 30, 12, 230, 23, 136, 97, 255, 131, 225, 149, 254, 203, 207, 253, 83, 166, 123, 111, 16, 180, 67, 30, 66, 62, 40, 164, 128, 50, 127, 235, 231, 2, 118, 3, 111, 96, 187, 156, 153, 207, 118, 51, 2, 210, 37, 68, 118, 0, 212, 159, 147, 43, 157, 211, 202, 30, 105, 89, 105, 122, 166, 3, 231, 77, 134, 102, 104, 26, 45, 202, 129, 96, 195, 133, 118, 104, 174, 7, 68, 64, 54, 102, 25, 235, 137, 32, 37, 108, 78, 74]; 25]; + + for _ in 0..100 { + assert_eq!(bls12381_p2_sum( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_g1_multiexp_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_g1_multiexp( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_g1_multiexp_50_100() { + let buffer: [[u8; 96 + 32]; 50] = [[23, 241, 211, 167, 49, 151, 215, 148, 38, 149, 99, 140, 79, 169, 172, 15, 195, 104, 140, 79, 151, 116, 185, 5, 161, 78, 58, 63, 23, 27, 172, 88, 108, 85, 232, 63, 249, 122, 26, 239, 251, 58, 240, 10, 219, 34, 198, 187, 8, 179, 244, 129, 227, 170, 160, 241, 160, 158, 48, 237, 116, 29, 138, 228, 252, 245, 224, 149, 213, 208, 10, 246, 0, 219, 24, 203, 44, 4, 179, 237, 208, 60, 199, 68, 162, 136, 138, 228, 12, 170, 35, 41, 70, 197, 231, + 225, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]; 50]; + + for _ in 0..100 { + assert_eq!(bls12381_g1_multiexp( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_g2_multiexp_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_g2_multiexp( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_g2_multiexp_50_100() { + let buffer: [[u8; 192 + 32]; 50] = [[19, 224, 43, 96, 82, 113, 159, 96, 125, 172, 211, 160, 136, 39, 79, 101, 89, 107, 208, 208, 153, 32, 182, 26, 181, 218, 97, 187, 220, 127, 80, 73, 51, 76, 241, 18, 19, 148, 93, 87, 229, 172, 125, 5, 93, 4, 43, 126, 2, 74, 162, 178, 240, 143, 10, 145, 38, 8, 5, 39, 45, 197, 16, 81, 198, 228, 122, 212, 250, 64, 59, 2, 180, 81, 11, 100, 122, 227, 209, 119, 11, 172, 3, 38, 168, 5, 187, 239, 212, 128, 86, 200, 193, 33, 189, 184, 6, 6, 196, 160, 46, 167, 52, 204, 50, 172, 210, 176, 43, 194, 139, 153, 203, 62, 40, 126, 133, 167, 99, 175, 38, 116, 146, 171, 87, 46, 153, 171, 63, 55, 13, 39, 92, 236, 29, 161, 170, 169, 7, 95, 240, 95, 121, 190, 12, 229, 213, 39, 114, 125, 110, 17, 140, 201, 205, 198, 218, 46, 53, 26, 173, 253, 155, 170, 140, 189, 211, 167, 109, 66, 154, 105, 81, 96, 209, 44, 146, 58, 201, 204, 59, 172, 162, 137, 225, 147, 84, 134, 8, 184, 40, 1, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]; 50]; + + for _ in 0..100 { + assert_eq!(bls12381_g2_multiexp( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_map_fp_to_g1_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_map_fp_to_g1( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_map_fp_to_g1_50_100() { + let buffer: [[u8; 48]; 50] = [[20, 64, 110, 91, 251, 146, 9, 37, 106, 56, 32, 135, 154, 41, 172, 47, 98, 214, 172, 168, 35, 36, 191, 58, 226, 170, 125, 60, 84, 121, 32, 67, 189, 140, 121, 31, 204, 219, 8, 12, 26, 82, 220, 104, 184, 182, 147, 80]; 50]; + + for _ in 0..100 { + assert_eq!(bls12381_map_fp_to_g1( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_map_fp2_to_g2_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_map_fp2_to_g2( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_map_fp2_to_g2_10_100() { + let buffer: [[u8; 96]; 10] = [[14, 136, 91, 179, 57, 150, 225, 47, 7, 218, 105, 7, 62, 44, 12, 200, 128, 188, 142, 255, 38, 210, 167, 36, 41, 158, 177, 45, 84, 244, 188, 242, 111, 71, 72, 187, 2, 14, 128, 167, 227, 121, 74, 123, 14, 71, 166, 65, 20, 64, 110, 91, 251, 146, 9, 37, 106, 56, 32, 135, 154, 41, 172, 47, 98, 214, 172, 168, 35, 36, 191, 58, 226, 170, 125, 60, 84, 121, 32, 67, 189, 140, 121, 31, 204, 219, 8, 12, 26, 82, 220, 104, 184, 182, 147, 80]; 10]; + + for _ in 0..100 { + assert_eq!(bls12381_map_fp2_to_g2( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_pairing_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_pairing_check( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64 + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_pairing_5_100() { + let buffer: [[u8; 288]; 5] = [[23, 241, 211, 167, 49, 151, 215, 148, 38, 149, 99, 140, 79, 169, 172, 15, 195, 104, 140, 79, 151, 116, 185, 5, 161, 78, 58, 63, 23, 27, 172, 88, 108, 85, 232, 63, 249, 122, 26, 239, 251, 58, 240, 10, 219, 34, 198, 187, 8, 179, 244, 129, 227, 170, 160, 241, 160, 158, 48, 237, 116, 29, 138, 228, 252, 245, 224, 149, 213, 208, 10, 246, 0, 219, 24, 203, 44, 4, 179, 237, 208, 60, 199, 68, 162, 136, 138, 228, 12, 170, 35, 41, 70, 197, 231, 225, 19, 224, 43, 96, 82, 113, 159, 96, 125, 172, 211, 160, 136, 39, 79, 101, 89, 107, 208, 208, 153, 32, 182, 26, 181, 218, 97, 187, 220, 127, 80, 73, 51, 76, 241, 18, 19, 148, 93, 87, 229, 172, 125, 5, 93, 4, 43, 126, 2, 74, 162, 178, 240, 143, 10, 145, 38, 8, 5, 39, 45, 197, 16, 81, 198, 228, 122, 212, 250, 64, 59, 2, 180, 81, 11, 100, 122, 227, 209, 119, 11, 172, 3, 38, 168, 5, 187, 239, 212, 128, 86, 200, 193, 33, 189, 184, 6, 6, 196, 160, 46, 167, 52, 204, 50, 172, 210, 176, 43, 194, 139, 153, 203, 62, 40, 126, 133, 167, 99, 175, 38, 116, 146, 171, 87, 46, 153, 171, 63, 55, 13, 39, 92, 236, 29, 161, 170, 169, 7, 95, 240, 95, 121, 190, 12, 229, 213, 39, 114, 125, 110, 17, 140, 201, 205, 198, 218, 46, 53, 26, 173, 253, 155, 170, 140, 189, 211, 167, 109, 66, 154, 105, 81, 96, 209, 44, 146, 58, 201, 204, 59, 172, 162, 137, 225, 147, 84, 134, 8, 184, 40, 1]; 5]; + + + for _ in 0..100 { + assert_eq!(bls12381_pairing_check( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64 + ), 2); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p1_decompress_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_p1_decompress( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p1_decompress_50_100() { + let buffer: [[u8; 48]; 50] = [[185, 110, 35, 139, 110, 142, 126, 177, 120, 97, 234, 41, 91, 204, 20, 203, 207, 103, 224, 112, 176, 18, 102, 59, 68, 107, 137, 231, 10, 71, 183, 63, 198, 228, 242, 206, 195, 124, 70, 91, 53, 182, 222, 158, 19, 104, 106, 15]; 50]; + + for _ in 0..100 { + assert_eq!(bls12381_p1_decompress( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p2_decompress_0_100() { + let buffer: [u8; 0] = []; + + for _ in 0..100 { + assert_eq!(bls12381_p2_decompress( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + +#[no_mangle] +#[cfg(feature = "protocol_feature_bls12381")] +pub unsafe fn bls12381_p2_decompress_50_100() { + let buffer: [[u8; 96]; 50] = [[143, 150, 139, 210, 67, 144, 143, 243, 229, 250, 26, 179, 243, 30, 7, 129, 151, 229, 138, 206, 86, 43, 190, 139, 90, 39, 29, 95, 186, 80, 35, 125, 160, 200, 254, 101, 231, 181, 119, 28, 192, 168, 111, 213, 127, 50, 52, 126, 21, 162, 109, 31, 93, 86, 196, 114, 208, 25, 238, 162, 83, 158, 88, 219, 0, 196, 154, 165, 208, 169, 102, 56, 56, 144, 63, 221, 190, 67, 107, 91, 21, 126, 131, 179, 93, 26, 78, 95, 137, 247, 129, 39, 243, 93, 172, 240]; 50]; + + for _ in 0..100 { + assert_eq!(bls12381_p2_decompress( + core::mem::size_of_val(&buffer) as u64, + buffer.as_ptr() as *const u64 as u64, + 0, + ), 0); + } +} + + // ############### // # Storage API # // ############### diff --git a/runtime/near-test-contracts/src/lib.rs b/runtime/near-test-contracts/src/lib.rs index 5a1314ef42a..b1d20a050da 100644 --- a/runtime/near-test-contracts/src/lib.rs +++ b/runtime/near-test-contracts/src/lib.rs @@ -90,7 +90,7 @@ pub fn fuzzing_contract() -> &'static [u8] { /// NEP-141 implementation (fungible token contract). /// /// The code is available here: -/// https://github.com/near/near-sdk-rs/tree/master/examples/fungible-token +/// /// /// We keep a static WASM of this for our integration tests. We don't have to /// update it with every SDK release, any contract implementing the interface diff --git a/runtime/near-test-contracts/test-contract-rs/src/lib.rs b/runtime/near-test-contracts/test-contract-rs/src/lib.rs index 1018c0a074d..0aa7ee02af3 100644 --- a/runtime/near-test-contracts/test-contract-rs/src/lib.rs +++ b/runtime/near-test-contracts/test-contract-rs/src/lib.rs @@ -131,7 +131,6 @@ extern "C" { // ######################## // # Promise Yield/Resume # // ######################## - #[cfg(feature = "nightly")] fn promise_yield_create( method_name_len: u64, method_name_ptr: u64, @@ -141,7 +140,6 @@ extern "C" { gas_weight: u64, register_id: u64, ) -> u64; - #[cfg(feature = "nightly")] fn promise_yield_resume( data_id_len: u64, data_id_ptr: u64, @@ -992,7 +990,6 @@ unsafe fn check_promise_result_write_status() { /// Call promise_yield_create, specifying `check_promise_result` as the yield callback. /// Given input is passed as the argument to the `check_promise_result` function call. /// Sets the yield callback's output as the return value. -#[cfg(feature = "nightly")] #[no_mangle] pub unsafe fn call_yield_create_return_promise() { input(0); @@ -1021,7 +1018,6 @@ pub unsafe fn call_yield_create_return_promise() { /// Call promise_yield_create, specifying `check_promise_result` as the yield callback. /// Given input is passed as the argument to the `check_promise_result` function call. /// Returns the data id produced by promise_yield_create. -#[cfg(feature = "nightly")] #[no_mangle] pub unsafe fn call_yield_create_return_data_id() { input(0); @@ -1053,7 +1049,6 @@ pub unsafe fn call_yield_create_return_data_id() { /// Call promise_yield_resume. /// Input is the byte array with `data_id` represented by last 32 bytes and `payload` /// represented by the first `register_len(0) - 32` bytes. -#[cfg(feature = "nightly")] #[no_mangle] pub unsafe fn call_yield_resume() { input(0); @@ -1076,7 +1071,6 @@ pub unsafe fn call_yield_resume() { } /// Call promise_yield_create and promise_yield_resume within the same function. -#[cfg(feature = "nightly")] #[no_mangle] pub unsafe fn call_yield_create_and_resume() { input(0); diff --git a/runtime/near-vm-runner/Cargo.toml b/runtime/near-vm-runner/Cargo.toml index 92300d68698..f3e49a3963e 100644 --- a/runtime/near-vm-runner/Cargo.toml +++ b/runtime/near-vm-runner/Cargo.toml @@ -14,20 +14,21 @@ workspace = true [dependencies] anyhow = { workspace = true, optional = true } -base64.workspace = true +blst = { workspace = true, optional = true } +base64 = { workspace = true, optional = true } bn.workspace = true borsh.workspace = true ed25519-dalek.workspace = true enum-map.workspace = true finite-wasm = { workspace = true, features = ["instrument"], optional = true } -lru = "0.12.3" -memoffset.workspace = true +lru.workspace = true +memoffset = { workspace = true, optional = true } num-rational.workspace = true once_cell.workspace = true parity-wasm = { workspace = true, optional = true } -prefix-sum-vec.workspace = true +prefix-sum-vec = { workspace = true, optional = true } ripemd.workspace = true -rustix = { workspace = true, features = [ "fs" ] } +rustix = { workspace = true, features = ["fs"] } serde_repr.workspace = true serde.workspace = true sha2.workspace = true @@ -37,13 +38,13 @@ strum.workspace = true tempfile.workspace = true thiserror.workspace = true tracing.workspace = true -prometheus.workspace = true +prometheus = { workspace = true, optional = true } wasm-encoder = { workspace = true, optional = true } wasmparser = { workspace = true, optional = true } wasmtime = { workspace = true, optional = true } near-crypto.workspace = true -near-o11y.workspace = true +near-o11y = { workspace = true, optional = true } near-parameters.workspace = true near-primitives-core.workspace = true bytesize.workspace = true @@ -70,8 +71,14 @@ near-vm-vm = { workspace = true, optional = true } [dev-dependencies] arbitrary.workspace = true +ark-bls12-381.workspace = true +ark-ec.workspace = true +ark-ff.workspace = true +ark-serialize.workspace = true +ark-std.workspace = true assert_matches.workspace = true bolero.workspace = true +csv.workspace = true cov-mark.workspace = true expect-test.workspace = true hex.workspace = true @@ -88,44 +95,63 @@ nightly_protocol = [ "near-parameters/nightly_protocol", "near-primitives-core/nightly_protocol", ] -wasmer0_vm = [ "wasmer-runtime", "wasmer-runtime-core", "prepare" ] -wasmtime_vm = [ "wasmtime", "anyhow", "prepare" ] +wasmer0_vm = ["wasmer-runtime", "wasmer-runtime-core", "prepare"] +wasmtime_vm = ["wasmtime", "anyhow", "prepare"] wasmer2_vm = [ - "wasmer-compiler", - "wasmer-compiler-singlepass", - "wasmer-engine", - "wasmer-engine-universal", - "wasmer-types", - "wasmer-vm", - "prepare", + "wasmer-compiler", + "wasmer-compiler-singlepass", + "wasmer-engine", + "wasmer-engine-universal", + "wasmer-types", + "wasmer-vm", + "memoffset", + "prepare", ] near_vm = [ - "near-vm-compiler", - "near-vm-compiler-singlepass", - "near-vm-engine", - "near-vm-types", - "near-vm-vm", - "prepare", + "near-vm-compiler", + "near-vm-compiler-singlepass", + "near-vm-engine", + "near-vm-types", + "near-vm-vm", + "memoffset", + "prepare", +] +prepare = [ + "finite-wasm", + "wasm-encoder", + "wasmparser", + "parity-wasm", + "parity-wasm_41", + "pwasm-utils_12", + "prefix-sum-vec", + "metrics", ] -prepare = [ "finite-wasm", "wasm-encoder", "wasmparser", "parity-wasm", "parity-wasm_41", "pwasm-utils_12" ] no_cpu_compatibility_checks = [] no_cache = [] protocol_feature_fix_contract_loading_cost = [ - "near-primitives-core/protocol_feature_fix_contract_loading_cost", + "near-primitives-core/protocol_feature_fix_contract_loading_cost", +] + +protocol_feature_bls12381 = [ + "blst", ] +metrics = ["prometheus", "near-o11y"] + + nightly = [ "near-o11y/nightly", "near-parameters/nightly", "near-primitives-core/nightly", "nightly_protocol", + "protocol_feature_bls12381", "protocol_feature_fix_contract_loading_cost", ] -sandbox = [] -io_trace = [] +sandbox = ["near-o11y/sandbox"] +io_trace = ["base64"] test_features = [] # Use this feature to enable counting of fees and costs applied. diff --git a/runtime/near-vm-runner/fuzz/fuzz_targets/diffrunner.rs b/runtime/near-vm-runner/fuzz/fuzz_targets/diffrunner.rs index b4eb13f6982..55e6a31e531 100644 --- a/runtime/near-vm-runner/fuzz/fuzz_targets/diffrunner.rs +++ b/runtime/near-vm-runner/fuzz/fuzz_targets/diffrunner.rs @@ -9,6 +9,7 @@ use near_vm_runner::logic::mocks::mock_external::MockedExternal; use near_vm_runner::logic::VMOutcome; use near_vm_runner::ContractCode; use near_vm_runner_fuzz::{create_context, find_entry_point, ArbitraryModule}; +use std::sync::Arc; libfuzzer_sys::fuzz_target!(|module: ArbitraryModule| { let code = ContractCode::new(module.0.module.to_bytes(), None); @@ -18,29 +19,22 @@ libfuzzer_sys::fuzz_target!(|module: ArbitraryModule| { }); fn run_fuzz(code: &ContractCode, vm_kind: VMKind) -> VMOutcome { - let mut fake_external = MockedExternal::new(); - let mut context = create_context(vec![]); + let mut fake_external = MockedExternal::with_code(code.clone_for_tests()); + let method_name = find_entry_point(code).unwrap_or_else(|| "main".to_string()); + let mut context = create_context(&method_name, vec![]); context.prepaid_gas = 10u64.pow(14); let config_store = RuntimeConfigStore::new(None); let config = config_store.get_config(PROTOCOL_VERSION); - let fees = &config.fees; - let mut wasm_config = config.wasm_config.clone(); + let fees = Arc::clone(&config.fees); + let mut wasm_config = near_parameters::vm::Config::clone(&config.wasm_config); wasm_config.limit_config.contract_prepare_version = near_vm_runner::logic::ContractPrepareVersion::V2; - let promise_results = vec![]; - - let method_name = find_entry_point(code).unwrap_or_else(|| "main".to_string()); - let res = vm_kind.runtime(wasm_config).unwrap().run( - *code.hash(), - Some(&code), - &method_name, - &mut fake_external, - &context, - fees, - &promise_results, - None, - ); + let res = vm_kind + .runtime(wasm_config.into()) + .unwrap() + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, fees); // Remove the VMError message details as they can differ between runtimes // TODO: maybe there's actually things we could check for equality here too? diff --git a/runtime/near-vm-runner/fuzz/fuzz_targets/runner.rs b/runtime/near-vm-runner/fuzz/fuzz_targets/runner.rs index e0d0b754588..54637c797c9 100644 --- a/runtime/near-vm-runner/fuzz/fuzz_targets/runner.rs +++ b/runtime/near-vm-runner/fuzz/fuzz_targets/runner.rs @@ -17,28 +17,18 @@ libfuzzer_sys::fuzz_target!(|module: ArbitraryModule| { }); fn run_fuzz(code: &ContractCode, config: Arc) -> VMOutcome { - let mut fake_external = MockedExternal::new(); - let mut context = create_context(vec![]); + let mut fake_external = MockedExternal::with_code(code.clone_for_tests()); + let method_name = find_entry_point(code).unwrap_or_else(|| "main".to_string()); + let mut context = create_context(&method_name, vec![]); context.prepaid_gas = 10u64.pow(14); - let mut wasm_config = config.wasm_config.clone(); + let mut wasm_config = near_parameters::vm::Config::clone(&config.wasm_config); wasm_config.limit_config.wasmer2_stack_limit = i32::MAX; // If we can crash wasmer2 even without the secondary stack limit it's still good to know let vm_kind = config.wasm_config.vm_kind; - let fees = &config.fees; - let promise_results = vec![]; - - let method_name = find_entry_point(code).unwrap_or_else(|| "main".to_string()); + let fees = Arc::clone(&config.fees); vm_kind - .runtime(wasm_config) + .runtime(wasm_config.into()) .unwrap() - .run( - *code.hash(), - Some(&code), - &method_name, - &mut fake_external, - &context, - fees, - &promise_results, - None, - ) + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, fees) .unwrap_or_else(|err| panic!("fatal error: {err:?}")) } diff --git a/runtime/near-vm-runner/fuzz/src/lib.rs b/runtime/near-vm-runner/fuzz/src/lib.rs index a7eb0a8e92f..2121f8ede3b 100644 --- a/runtime/near-vm-runner/fuzz/src/lib.rs +++ b/runtime/near-vm-runner/fuzz/src/lib.rs @@ -30,13 +30,15 @@ pub fn find_entry_point(contract: &ContractCode) -> Option { None } -pub fn create_context(input: Vec) -> VMContext { +pub fn create_context(method: &str, input: Vec) -> VMContext { VMContext { current_account_id: "alice".parse().unwrap(), signer_account_id: "bob".parse().unwrap(), signer_account_pk: vec![0, 1, 2, 3, 4], predecessor_account_id: "carol".parse().unwrap(), + method: method.into(), input, + promise_results: Vec::new().into(), block_height: 10, block_timestamp: 42, epoch_height: 1, diff --git a/runtime/near-vm-runner/src/cache.rs b/runtime/near-vm-runner/src/cache.rs index 11b238a663b..13ebee29a94 100644 --- a/runtime/near-vm-runner/src/cache.rs +++ b/runtime/near-vm-runner/src/cache.rs @@ -391,7 +391,7 @@ impl AnyCache { fn new(size: usize) -> Self { Self { cache: if let Some(size) = NonZeroUsize::new(size) { - Some(Mutex::new(lru::LruCache::new(size))) + Some(Mutex::new(lru::LruCache::new(size.into()))) } else { None }, @@ -475,19 +475,19 @@ impl AnyCache { /// is already in the cache, or if cache is `None`. pub fn precompile_contract( code: &ContractCode, - config: &Config, + config: Arc, cache: Option<&dyn ContractRuntimeCache>, ) -> Result, CacheError> { let _span = tracing::debug_span!(target: "vm", "precompile_contract").entered(); let vm_kind = config.vm_kind; let runtime = vm_kind - .runtime(config.clone()) + .runtime(Arc::clone(&config)) .unwrap_or_else(|| panic!("the {vm_kind:?} runtime has not been enabled at compile time")); let cache = match cache { Some(it) => it, None => return Ok(Ok(ContractPrecompilatonResult::CacheNotAvailable)), }; - let key = get_contract_cache_key(*code.hash(), config); + let key = get_contract_cache_key(*code.hash(), &config); // Check if we already cached with such a key. if cache.has(&key).map_err(CacheError::ReadError)? { return Ok(Ok(ContractPrecompilatonResult::ContractAlreadyInCache)); diff --git a/runtime/near-vm-runner/src/code.rs b/runtime/near-vm-runner/src/code.rs index 820457894be..213154bfc30 100644 --- a/runtime/near-vm-runner/src/code.rs +++ b/runtime/near-vm-runner/src/code.rs @@ -24,4 +24,8 @@ impl ContractCode { pub fn hash(&self) -> &CryptoHash { &self.hash } + + pub fn clone_for_tests(&self) -> Self { + Self { code: self.code.clone(), hash: self.hash } + } } diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index c365964272e..0a9dbb3aaa9 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -47,13 +47,13 @@ //! make imports retroactively available to old transactions. So //! `for_each_available_import` takes care to invoke `M!` only for currently //! available imports. - -#[cfg(any( +#![cfg(any( feature = "wasmer0_vm", feature = "wasmer2_vm", feature = "near_vm", feature = "wasmtime_vm" ))] + macro_rules! call_with_name { ( $M:ident => @in $mod:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > ) => { $M!($mod / $func : $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >) @@ -73,17 +73,11 @@ macro_rules! imports { $( @as $name:ident : )? $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >,)* ) => { - #[cfg(any( - feature = "wasmer0_vm", - feature = "wasmer2_vm", - feature = "near_vm", - feature = "wasmtime_vm" - ))] macro_rules! for_each_available_import { ($config:expr, $M:ident) => {$( $(#[cfg(feature = $feature_name)])? if true $(&& ($config).$config_field)? { - call_with_name!($M => $( @in $mod : )? $( @as $name : )? $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >); + $crate::imports::call_with_name!($M => $( @in $mod : )? $( @as $name : )? $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >); } )*} } @@ -280,6 +274,19 @@ imports! { #[alt_bn128] alt_bn128_g1_multiexp<[value_len: u64, value_ptr: u64, register_id: u64] -> []>, #[alt_bn128] alt_bn128_g1_sum<[value_len: u64, value_ptr: u64, register_id: u64] -> []>, #[alt_bn128] alt_bn128_pairing_check<[value_len: u64, value_ptr: u64] -> [u64]>, + // ############# + // # BLS12-381 # + // ############# + ##["protocol_feature_bls12381"] bls12381_p1_sum<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_p2_sum<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_g1_multiexp<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_g2_multiexp<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_map_fp_to_g1<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_map_fp2_to_g2<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_pairing_check<[value_len: u64, value_ptr: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_p1_decompress<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + ##["protocol_feature_bls12381"] bls12381_p2_decompress<[value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>, + // ############# // # Sandbox # // ############# @@ -297,461 +304,18 @@ imports! { ##["test_features"] burn_gas<[gas: u64] -> []>, } -#[cfg(all(feature = "wasmer0_vm", target_arch = "x86_64"))] -pub(crate) mod wasmer { - use super::str_eq; - use crate::logic::{VMLogic, VMLogicError}; - use std::ffi::c_void; - - #[derive(Clone, Copy)] - struct ImportReference(pub *mut c_void); - unsafe impl Send for ImportReference {} - unsafe impl Sync for ImportReference {} +pub(crate) use {call_with_name, for_each_available_import}; - pub(crate) fn build( - memory: wasmer_runtime::memory::Memory, - logic: &mut VMLogic<'_>, - ) -> wasmer_runtime::ImportObject { - let raw_ptr = logic as *mut _ as *mut c_void; - let import_reference = ImportReference(raw_ptr); - let mut import_object = wasmer_runtime::ImportObject::new_with_data(move || { - let dtor = (|_: *mut c_void| {}) as fn(*mut c_void); - ({ import_reference }.0, dtor) - }); - - let mut ns_internal = wasmer_runtime_core::import::Namespace::new(); - let mut ns_env = wasmer_runtime_core::import::Namespace::new(); - ns_env.insert("memory", memory); - - macro_rules! add_import { - ( - $mod:ident / $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > - ) => { - #[allow(unused_parens)] - fn $name( ctx: &mut wasmer_runtime::Ctx, $( $arg_name: $arg_type ),* ) -> Result<($( $returns ),*), VMLogicError> { - const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas"); - let _span = if IS_GAS { - None - } else { - Some(tracing::trace_span!(target: "host-function", stringify!($name)).entered()) - }; - let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) }; - logic.$func( $( $arg_name, )* ) - } - - match stringify!($mod) { - "env" => ns_env.insert(stringify!($name), wasmer_runtime::func!($name)), - "internal" => ns_internal.insert(stringify!($name), wasmer_runtime::func!($name)), - _ => unimplemented!(), - } - }; - } - for_each_available_import!(logic.config, add_import); - - import_object.register("env", ns_env); - import_object.register("internal", ns_internal); - import_object - } -} - -#[cfg(all(feature = "wasmer2_vm", target_arch = "x86_64"))] -pub(crate) mod wasmer2 { - use std::sync::Arc; - - use super::str_eq; - use crate::logic::VMLogic; - use wasmer_engine::Engine; - use wasmer_engine_universal::UniversalEngine; - use wasmer_vm::{ - ExportFunction, ExportFunctionMetadata, Resolver, VMFunction, VMFunctionKind, VMMemory, - }; - - pub(crate) struct Wasmer2Imports<'engine, 'vmlogic, 'vmlogic_refs> { - pub(crate) memory: VMMemory, - // Note: this same object is also referenced by the `metadata` field! - pub(crate) vmlogic: &'vmlogic mut VMLogic<'vmlogic_refs>, - pub(crate) metadata: Arc, - pub(crate) engine: &'engine UniversalEngine, - } - - trait Wasmer2Type { - type Wasmer; - fn to_wasmer(self) -> Self::Wasmer; - fn ty() -> wasmer_types::Type; - } - macro_rules! wasmer_types { - ($($native:ty as $wasmer:ty => $type_expr:expr;)*) => { - $(impl Wasmer2Type for $native { - type Wasmer = $wasmer; - fn to_wasmer(self) -> $wasmer { - self as _ - } - fn ty() -> wasmer_types::Type { - $type_expr - } - })* - } - } - wasmer_types! { - u32 as i32 => wasmer_types::Type::I32; - u64 as i64 => wasmer_types::Type::I64; - } - - macro_rules! return_ty { - ($return_type: ident = [ ]) => { - type $return_type = (); - fn make_ret() -> () {} - }; - ($return_type: ident = [ $($returns: ident),* ]) => { - #[repr(C)] - struct $return_type($(<$returns as Wasmer2Type>::Wasmer),*); - fn make_ret($($returns: $returns),*) -> Ret { Ret($($returns.to_wasmer()),*) } - } - } - - impl<'e, 'l, 'lr> Resolver for Wasmer2Imports<'e, 'l, 'lr> { - fn resolve(&self, _index: u32, module: &str, field: &str) -> Option { - if module == "env" && field == "memory" { - return Some(wasmer_vm::Export::Memory(self.memory.clone())); - } - - macro_rules! add_import { - ( - $mod:ident / $name:ident : $func:ident < - [ $( $arg_name:ident : $arg_type:ident ),* ] - -> [ $( $returns:ident ),* ] - > - ) => { - return_ty!(Ret = [ $($returns),* ]); - - extern "C" fn $name(env: *mut VMLogic<'_>, $( $arg_name: $arg_type ),* ) - -> Ret { - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas"); - let _span = if IS_GAS { - None - } else { - Some(tracing::trace_span!( - target: "host-function", - stringify!($name) - ).entered()) - }; - - // SAFETY: This code should only be executable within `'vmlogic` - // lifetime and so it is safe to dereference the `env` pointer which is - // known to be derived from a valid `&'vmlogic mut VMLogic<'_>` in the - // first place. - unsafe { (*env).$func( $( $arg_name, )* ) } - })); - // We want to ensure that the only kind of error that host function calls - // return are VMLogicError. This is important because we later attempt to - // downcast the `RuntimeError`s into `VMLogicError`. - let result: Result, _> = result; - #[allow(unused_parens)] - match result { - Ok(Ok(($($returns),*))) => make_ret($($returns),*), - Ok(Err(trap)) => unsafe { - // SAFETY: this can only be called by a WASM contract, so all the - // necessary hooks are known to be in place. - wasmer_vm::raise_user_trap(Box::new(trap)) - }, - Err(e) => unsafe { - // SAFETY: this can only be called by a WASM contract, so all the - // necessary hooks are known to be in place. - wasmer_vm::resume_panic(e) - }, - } - } - // TODO: a phf hashmap would probably work better here. - if module == stringify!($mod) && field == stringify!($name) { - let args = [$(<$arg_type as Wasmer2Type>::ty()),*]; - let rets = [$(<$returns as Wasmer2Type>::ty()),*]; - let signature = wasmer_types::FunctionTypeRef::new(&args[..], &rets[..]); - let signature = self.engine.register_signature(signature); - return Some(wasmer_vm::Export::Function(ExportFunction { - vm_function: VMFunction { - address: $name as *const _, - // SAFETY: here we erase the lifetime of the `vmlogic` reference, - // but we believe that the lifetimes on `Wasmer2Imports` enforce - // sufficiently that it isn't possible to call this exported - // function when vmlogic is no loger live. - vmctx: wasmer_vm::VMFunctionEnvironment { - host_env: self.vmlogic as *const _ as *mut _ - }, - signature, - kind: VMFunctionKind::Static, - call_trampoline: None, - instance_ref: None, - }, - metadata: Some(Arc::clone(&self.metadata)), - })); - } - }; - } - for_each_available_import!(self.vmlogic.config, add_import); - return None; - } - } - - pub(crate) fn build<'e, 'a, 'b>( - memory: VMMemory, - logic: &'a mut VMLogic<'b>, - engine: &'e UniversalEngine, - ) -> Wasmer2Imports<'e, 'a, 'b> { - let metadata = unsafe { - // SAFETY: the functions here are thread-safe. We ensure that the lifetime of `VMLogic` - // is sufficiently long by tying the lifetime of VMLogic to the return type which - // contains this metadata. - ExportFunctionMetadata::new(logic as *mut _ as *mut _, None, |ptr| ptr, |_| {}) - }; - Wasmer2Imports { memory, vmlogic: logic, metadata: Arc::new(metadata), engine } - } -} - -#[cfg(all(feature = "near_vm", target_arch = "x86_64"))] -pub(crate) mod near_vm { - use std::sync::Arc; - - use super::str_eq; - use crate::logic::VMLogic; - use near_vm_engine::universal::UniversalEngine; - use near_vm_vm::{ - ExportFunction, ExportFunctionMetadata, Resolver, VMFunction, VMFunctionKind, VMMemory, - }; - - pub(crate) struct NearVmImports<'engine, 'vmlogic, 'vmlogic_refs> { - pub(crate) memory: VMMemory, - // Note: this same object is also referenced by the `metadata` field! - pub(crate) vmlogic: &'vmlogic mut VMLogic<'vmlogic_refs>, - pub(crate) metadata: Arc, - pub(crate) engine: &'engine UniversalEngine, - } - - trait NearVmType { - type NearVm; - fn to_near_vm(self) -> Self::NearVm; - fn ty() -> near_vm_types::Type; - } - macro_rules! near_vm_types { - ($($native:ty as $near_vm:ty => $type_expr:expr;)*) => { - $(impl NearVmType for $native { - type NearVm = $near_vm; - fn to_near_vm(self) -> $near_vm { - self as _ - } - fn ty() -> near_vm_types::Type { - $type_expr - } - })* - } - } - near_vm_types! { - u32 as i32 => near_vm_types::Type::I32; - u64 as i64 => near_vm_types::Type::I64; - } - - macro_rules! return_ty { - ($return_type: ident = [ ]) => { - type $return_type = (); - fn make_ret() -> () {} - }; - ($return_type: ident = [ $($returns: ident),* ]) => { - #[repr(C)] - struct $return_type($(<$returns as NearVmType>::NearVm),*); - fn make_ret($($returns: $returns),*) -> Ret { Ret($($returns.to_near_vm()),*) } - } - } - - impl<'e, 'l, 'lr> Resolver for NearVmImports<'e, 'l, 'lr> { - fn resolve(&self, _index: u32, module: &str, field: &str) -> Option { - if module == "env" && field == "memory" { - return Some(near_vm_vm::Export::Memory(self.memory.clone())); - } - - macro_rules! add_import { - ( - $mod:ident / $name:ident : $func:ident < - [ $( $arg_name:ident : $arg_type:ident ),* ] - -> [ $( $returns:ident ),* ] - > - ) => { - return_ty!(Ret = [ $($returns),* ]); - - extern "C" fn $name(env: *mut VMLogic<'_>, $( $arg_name: $arg_type ),* ) - -> Ret { - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas"); - let _span = if IS_GAS { - None - } else { - Some(tracing::trace_span!( - target: "host-function", - stringify!($name) - ).entered()) - }; - - // SAFETY: This code should only be executable within `'vmlogic` - // lifetime and so it is safe to dereference the `env` pointer which is - // known to be derived from a valid `&'vmlogic mut VMLogic<'_>` in the - // first place. - unsafe { (*env).$func( $( $arg_name, )* ) } - })); - // We want to ensure that the only kind of error that host function calls - // return are VMLogicError. This is important because we later attempt to - // downcast the `RuntimeError`s into `VMLogicError`. - let result: Result, _> = result; - #[allow(unused_parens)] - match result { - Ok(Ok(($($returns),*))) => make_ret($($returns),*), - Ok(Err(trap)) => unsafe { - // SAFETY: this can only be called by a WASM contract, so all the - // necessary hooks are known to be in place. - near_vm_vm::raise_user_trap(Box::new(trap)) - }, - Err(e) => unsafe { - // SAFETY: this can only be called by a WASM contract, so all the - // necessary hooks are known to be in place. - near_vm_vm::resume_panic(e) - }, - } - } - // TODO: a phf hashmap would probably work better here. - if module == stringify!($mod) && field == stringify!($name) { - let args = [$(<$arg_type as NearVmType>::ty()),*]; - let rets = [$(<$returns as NearVmType>::ty()),*]; - let signature = near_vm_types::FunctionType::new(&args[..], &rets[..]); - let signature = self.engine.register_signature(signature); - return Some(near_vm_vm::Export::Function(ExportFunction { - vm_function: VMFunction { - address: $name as *const _, - // SAFETY: here we erase the lifetime of the `vmlogic` reference, - // but we believe that the lifetimes on `NearVmImports` enforce - // sufficiently that it isn't possible to call this exported - // function when vmlogic is no loger live. - vmctx: near_vm_vm::VMFunctionEnvironment { - host_env: self.vmlogic as *const _ as *mut _ - }, - signature, - kind: VMFunctionKind::Static, - call_trampoline: None, - instance_ref: None, - }, - metadata: Some(Arc::clone(&self.metadata)), - })); - } - }; - } - for_each_available_import!(self.vmlogic.config, add_import); - return None; - } - } - - pub(crate) fn build<'e, 'a, 'b>( - memory: VMMemory, - logic: &'a mut VMLogic<'b>, - engine: &'e UniversalEngine, - ) -> NearVmImports<'e, 'a, 'b> { - let metadata = unsafe { - // SAFETY: the functions here are thread-safe. We ensure that the lifetime of `VMLogic` - // is sufficiently long by tying the lifetime of VMLogic to the return type which - // contains this metadata. - ExportFunctionMetadata::new(logic as *mut _ as *mut _, None, |ptr| ptr, |_| {}) - }; - NearVmImports { memory, vmlogic: logic, metadata: Arc::new(metadata), engine } - } -} - -#[cfg(feature = "wasmtime_vm")] -pub(crate) mod wasmtime { - use super::str_eq; - use crate::logic::{VMLogic, VMLogicError}; - use std::cell::UnsafeCell; - use std::ffi::c_void; - - /// This is a container from which an error can be taken out by value. This is necessary as - /// `anyhow` does not really give any opportunity to grab causes by value and the VM Logic - /// errors end up a couple layers deep in a causal chain. - #[derive(Debug)] - pub(crate) struct ErrorContainer(std::sync::Mutex>); - impl ErrorContainer { - pub(crate) fn take(&self) -> Option { - let mut guard = self.0.lock().unwrap_or_else(|e| e.into_inner()); - guard.take() - } - } - impl std::error::Error for ErrorContainer {} - impl std::fmt::Display for ErrorContainer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("VMLogic error occurred and is now stored in an opaque storage container") - } - } - - thread_local! { - static CALLER_CONTEXT: UnsafeCell<*mut c_void> = const { UnsafeCell::new(core::ptr::null_mut()) }; - } - - pub(crate) fn link<'a, 'b>( - linker: &mut wasmtime::Linker<()>, - memory: wasmtime::Memory, - store: &wasmtime::Store<()>, - logic: &'a mut VMLogic<'b>, - ) { - // Unfortunately, due to the Wasmtime implementation we have to do tricks with the - // lifetimes of the logic instance and pass raw pointers here. - // FIXME(nagisa): I believe this is no longer required, we just need to look at this code - // again. - let raw_logic = logic as *mut _ as *mut c_void; - CALLER_CONTEXT.with(|caller_context| unsafe { *caller_context.get() = raw_logic }); - linker.define(store, "env", "memory", memory).expect("cannot define memory"); - - macro_rules! add_import { - ( - $mod:ident / $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > - ) => { - #[allow(unused_parens)] - fn $name(caller: wasmtime::Caller<'_, ()>, $( $arg_name: $arg_type ),* ) -> anyhow::Result<($( $returns ),*)> { - const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas"); - let _span = if IS_GAS { - None - } else { - Some(tracing::trace_span!(target: "host-function", stringify!($name)).entered()) - }; - // the below is bad. don't do this at home. it probably works thanks to the exact way the system is setup. - // Thanksfully, this doesn't run in production, and hopefully should be possible to remove before we even - // consider doing so. - let data = CALLER_CONTEXT.with(|caller_context| { - unsafe { - *caller_context.get() - } - }); - unsafe { - // Transmute the lifetime of caller so it's possible to put it in a thread-local. - crate::wasmtime_runner::CALLER.with(|runner_caller| *runner_caller.borrow_mut() = std::mem::transmute(caller)); - } - let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) }; - match logic.$func( $( $arg_name as $arg_type, )* ) { - Ok(result) => Ok(result as ($( $returns ),* ) ), - Err(err) => { - Err(ErrorContainer(std::sync::Mutex::new(Some(err))).into()) - } - } - } - - linker.func_wrap(stringify!($mod), stringify!($name), $name).expect("cannot link external"); - }; - } - for_each_available_import!(logic.config, add_import); +pub(crate) const fn should_trace_host_function(host_function: &str) -> bool { + match host_function { + _ if str_eq(host_function, "gas") => false, + _ if str_eq(host_function, "finite_wasm_gas") => false, + _ => true, } } /// Constant-time string equality, work-around for `"foo" == "bar"` not working /// in const context yet. -#[cfg(any( - feature = "wasmer0_vm", - feature = "wasmer2_vm", - feature = "near_vm", - feature = "wasmtime_vm" -))] const fn str_eq(s1: &str, s2: &str) -> bool { let s1 = s1.as_bytes(); let s2 = s2.as_bytes(); diff --git a/runtime/near-vm-runner/src/lib.rs b/runtime/near-vm-runner/src/lib.rs index 2f98014f300..ca8d19e8b9a 100644 --- a/runtime/near-vm-runner/src/lib.rs +++ b/runtime/near-vm-runner/src/lib.rs @@ -8,8 +8,7 @@ mod imports; #[cfg(feature = "prepare")] mod instrument; pub mod logic; -#[cfg(all(feature = "wasmer0_vm", target_arch = "x86_64"))] -mod memory; +#[cfg(feature = "metrics")] mod metrics; #[cfg(all(feature = "near_vm", target_arch = "x86_64"))] mod near_vm_runner; @@ -34,9 +33,10 @@ pub use cache::{ NoContractRuntimeCache, }; pub use code::ContractCode; +#[cfg(feature = "metrics")] pub use metrics::{report_metrics, reset_metrics}; pub use profile::ProfileDataV3; -pub use runner::{run, VM}; +pub use runner::{run, PreparedContract, VM}; /// This is public for internal experimentation use only, and should otherwise be considered an /// implementation detail of `near-vm-runner`. diff --git a/runtime/near-vm-runner/src/logic/bls12381.rs b/runtime/near-vm-runner/src/logic/bls12381.rs new file mode 100644 index 00000000000..0e62afbb33c --- /dev/null +++ b/runtime/near-vm-runner/src/logic/bls12381.rs @@ -0,0 +1,412 @@ +use crate::logic::{HostError, VMLogicError}; +use std::ptr::null; + +pub type Result = ::std::result::Result; + +const BLS_BOOL_SIZE: usize = 1; +const BLS_SCALAR_SIZE: usize = 32; +const BLS_FP_SIZE: usize = 48; +const BLS_FP2_SIZE: usize = 96; +const BLS_P1_SIZE: usize = 96; +const BLS_P2_SIZE: usize = 192; +const BLS_P1_COMPRESS_SIZE: usize = 48; +const BLS_P2_COMPRESS_SIZE: usize = 96; + +#[macro_export] +macro_rules! bls12381_impl { + ( + $doc:expr, + $fn_name:ident, + $ITEM_SIZE:expr, + $bls12381_base:ident, + $bls12381_element:ident, + $impl_fn_name:ident + ) => { + #[doc = $doc] + #[cfg(feature = "protocol_feature_bls12381")] + pub fn $fn_name( + &mut self, + value_len: u64, + value_ptr: u64, + register_id: u64, + ) -> Result { + self.result_state.gas_counter.pay_base($bls12381_base)?; + + let elements_count = value_len / $ITEM_SIZE; + self.result_state.gas_counter.pay_per($bls12381_element, elements_count as u64)?; + + let data = get_memory_or_register!(self, value_ptr, value_len)?; + let res_option = super::bls12381::$impl_fn_name(&data)?; + + if let Some(res) = res_option { + self.registers.set( + &mut self.result_state.gas_counter, + &self.config.limit_config, + register_id, + res.as_slice(), + )?; + + Ok(0) + } else { + Ok(1) + } + } + }; +} + +#[macro_export] +macro_rules! bls12381_fn { + ( + $p_sum:ident, + $g_multiexp:ident, + $p_decompress:ident, + $map_fp_to_g:ident, + $BLS_P_SIZE:ident, + $BLS_FP_SIZE:ident, + $BLS_P_COMPRESS_SIZE:ident, + $blst_p:ident, + $blst_p_affine:ident, + $blst_p_deserialize:ident, + $blst_p_from_affine:ident, + $blst_p_cneg:ident, + $blst_p_add_or_double:ident, + $blst_p_to_affine:ident, + $blst_p_affine_serialize:ident, + $blst_p_in_g:ident, + $blst_p_mult:ident, + $read_fp_point:ident, + $blst_map_to_g:ident, + $PubKeyOrSig:ident, + $parse_p:ident, + $serialize_p:ident, + $bls12381_p:expr, + $bls12381_map_fp_to_g:expr + ) => { + fn $parse_p(point_data: &[u8]) -> Option { + if point_data[0] & 0x80 != 0 { + return None; + } + + let mut pk_aff = blst::$blst_p_affine::default(); + let error_code = unsafe { blst::$blst_p_deserialize(&mut pk_aff, point_data.as_ptr()) }; + if error_code != blst::BLST_ERROR::BLST_SUCCESS { + return None; + } + + let mut pk = blst::$blst_p::default(); + unsafe { + blst::$blst_p_from_affine(&mut pk, &pk_aff); + } + Some(pk) + } + + fn $serialize_p(res_pk: &blst::$blst_p) -> Vec { + let mut res_affine = blst::$blst_p_affine::default(); + + unsafe { + blst::$blst_p_to_affine(&mut res_affine, res_pk); + } + + let mut res = [0u8; $BLS_P_SIZE]; + unsafe { + blst::$blst_p_affine_serialize(res.as_mut_ptr(), &res_affine); + } + + res.to_vec() + } + + pub(super) fn $p_sum(data: &[u8]) -> Result>> { + const ITEM_SIZE: usize = BLS_BOOL_SIZE + $BLS_P_SIZE; + check_input_size(data, ITEM_SIZE, &format!("{}_sum", $bls12381_p))?; + + let mut res_pk = blst::$blst_p::default(); + + for item_data in data.chunks_exact(ITEM_SIZE) { + let (sign_data, point_data) = item_data.split_at(BLS_BOOL_SIZE); + debug_assert_eq!(point_data.len(), $BLS_P_SIZE); + + let mut pk = match $parse_p(point_data) { + Some(pk) => pk, + None => return Ok(None), + }; + + let sign = sign_data[0]; + + if sign == 1 { + unsafe { + blst::$blst_p_cneg(&mut pk, true); + } + } else if sign != 0 { + return Ok(None); + } + + unsafe { + blst::$blst_p_add_or_double(&mut res_pk, &res_pk, &pk); + } + } + + Ok(Some($serialize_p(&res_pk))) + } + + pub(super) fn $g_multiexp(data: &[u8]) -> Result>> { + const ITEM_SIZE: usize = $BLS_P_SIZE + BLS_SCALAR_SIZE; + check_input_size(data, ITEM_SIZE, &format!("{}_multiexp", $bls12381_p))?; + + let mut res_pk = blst::$blst_p::default(); + + for item_data in data.chunks_exact(ITEM_SIZE) { + let (point_data, scalar_data) = item_data.split_at($BLS_P_SIZE); + debug_assert_eq!(scalar_data.len(), BLS_SCALAR_SIZE); + + let pk = match $parse_p(point_data) { + Some(pk) => pk, + None => return Ok(None), + }; + + if unsafe { blst::$blst_p_in_g(&pk) } != true { + return Ok(None); + } + + let mut pk_mul = blst::$blst_p::default(); + unsafe { + blst::$blst_p_mult(&mut pk_mul, &pk, scalar_data.as_ptr(), BLS_SCALAR_SIZE * 8); + } + + unsafe { + blst::$blst_p_add_or_double(&mut res_pk, &res_pk, &pk_mul); + } + } + + Ok(Some($serialize_p(&res_pk))) + } + + pub(super) fn $p_decompress(data: &[u8]) -> Result>> { + const ITEM_SIZE: usize = $BLS_P_COMPRESS_SIZE; + check_input_size(data, ITEM_SIZE, &format!("{}_decompress", $bls12381_p))?; + let elements_count = data.len() / ITEM_SIZE; + + let mut res = Vec::::with_capacity(elements_count * $BLS_P_SIZE); + + for item_data in data.chunks_exact(ITEM_SIZE) { + let pk_res = blst::min_pk::$PubKeyOrSig::uncompress(item_data); + let pk_ser = if let Ok(pk) = pk_res { + pk.serialize() + } else { + return Ok(None); + }; + + res.extend_from_slice(pk_ser.as_slice()); + } + + Ok(Some(res)) + } + + pub(super) fn $map_fp_to_g(data: &[u8]) -> Result>> { + const ITEM_SIZE: usize = $BLS_FP_SIZE; + check_input_size(data, ITEM_SIZE, $bls12381_map_fp_to_g)?; + let elements_count: usize = data.len() / ITEM_SIZE; + + let mut res_concat: Vec = Vec::with_capacity($BLS_P_SIZE * elements_count); + + for item_data in data.chunks_exact(ITEM_SIZE) { + let fp_point = match $read_fp_point(item_data) { + Some(fp_point) => fp_point, + None => return Ok(None), + }; + + let mut g_point = blst::$blst_p::default(); + unsafe { + blst::$blst_map_to_g(&mut g_point, &fp_point, null()); + } + + let mut res = $serialize_p(&g_point); + res_concat.append(&mut res); + } + + Ok(Some(res_concat)) + } + }; +} + +bls12381_fn!( + p1_sum, + g1_multiexp, + p1_decompress, + map_fp_to_g1, + BLS_P1_SIZE, + BLS_FP_SIZE, + BLS_P1_COMPRESS_SIZE, + blst_p1, + blst_p1_affine, + blst_p1_deserialize, + blst_p1_from_affine, + blst_p1_cneg, + blst_p1_add_or_double, + blst_p1_to_affine, + blst_p1_affine_serialize, + blst_p1_in_g1, + blst_p1_mult, + read_fp_point, + blst_map_to_g1, + PublicKey, + parse_p1, + serialize_p1, + "bls12381_p1", + "bls12381_map_fp_to_g1" +); + +bls12381_fn!( + p2_sum, + g2_multiexp, + p2_decompress, + map_fp2_to_g2, + BLS_P2_SIZE, + BLS_FP2_SIZE, + BLS_P2_COMPRESS_SIZE, + blst_p2, + blst_p2_affine, + blst_p2_deserialize, + blst_p2_from_affine, + blst_p2_cneg, + blst_p2_add_or_double, + blst_p2_to_affine, + blst_p2_affine_serialize, + blst_p2_in_g2, + blst_p2_mult, + read_fp2_point, + blst_map_to_g2, + Signature, + parse_p2, + serialize_p2, + "bls12381_p2", + "bls12381_map_fp2_to_g2" +); + +pub(super) fn pairing_check(data: &[u8]) -> Result { + const ITEM_SIZE: usize = BLS_P1_SIZE + BLS_P2_SIZE; + check_input_size(data, ITEM_SIZE, "bls12381_pairing_check")?; + let elements_count = data.len() / ITEM_SIZE; + + let mut blst_g1_list: Vec = + vec![blst::blst_p1_affine::default(); elements_count]; + let mut blst_g2_list: Vec = + vec![blst::blst_p2_affine::default(); elements_count]; + + for (i, item_data) in data.chunks_exact(ITEM_SIZE).enumerate() { + let (point1_data, point2_data) = item_data.split_at(BLS_P1_SIZE); + debug_assert_eq!(point2_data.len(), BLS_P2_SIZE); + + if point1_data[0] & 0x80 != 0 { + return Ok(1); + } + + let error_code = + unsafe { blst::blst_p1_deserialize(&mut blst_g1_list[i], point1_data.as_ptr()) }; + + if error_code != blst::BLST_ERROR::BLST_SUCCESS { + return Ok(1); + } + + let g1_check = unsafe { blst::blst_p1_affine_in_g1(&blst_g1_list[i]) }; + if g1_check == false { + return Ok(1); + } + + if point2_data[0] & 0x80 != 0 { + return Ok(1); + } + + let error_code = + unsafe { blst::blst_p2_deserialize(&mut blst_g2_list[i], point2_data.as_ptr()) }; + if error_code != blst::BLST_ERROR::BLST_SUCCESS { + return Ok(1); + } + + let g2_check = unsafe { blst::blst_p2_affine_in_g2(&blst_g2_list[i]) }; + if g2_check == false { + return Ok(1); + } + } + + let mut pairing_fp12 = blst::blst_fp12::default(); + for i in 0..elements_count { + pairing_fp12 *= blst::blst_fp12::miller_loop(&blst_g2_list[i], &blst_g1_list[i]); + } + pairing_fp12 = pairing_fp12.final_exp(); + + let pairing_res = unsafe { blst::blst_fp12_is_one(&pairing_fp12) }; + + if pairing_res { + Ok(0) + } else { + Ok(2) + } +} + +fn read_fp_point(item_data: &[u8]) -> Option { + let mut fp_point = blst::blst_fp::default(); + unsafe { + blst::blst_fp_from_bendian(&mut fp_point, item_data.as_ptr()); + } + + let mut fp_row: [u8; BLS_FP_SIZE] = [0u8; BLS_FP_SIZE]; + unsafe { + blst::blst_bendian_from_fp(fp_row.as_mut_ptr(), &fp_point); + } + + for j in 0..BLS_FP_SIZE { + if fp_row[j] != item_data[j] { + return None; + } + } + + Some(fp_point) +} + +fn read_fp2_point(item_data: &[u8]) -> Option { + let mut c_fp1 = [blst::blst_fp::default(); 2]; + + unsafe { + blst::blst_fp_from_bendian(&mut c_fp1[1], item_data[..BLS_FP_SIZE].as_ptr()); + blst::blst_fp_from_bendian(&mut c_fp1[0], item_data[BLS_FP_SIZE..].as_ptr()); + } + + let mut fp_row: [u8; BLS_FP_SIZE] = [0u8; BLS_FP_SIZE]; + unsafe { + blst::blst_bendian_from_fp(fp_row.as_mut_ptr(), &c_fp1[0]); + } + + for j in BLS_FP_SIZE..BLS_FP2_SIZE { + if fp_row[j - BLS_FP_SIZE] != item_data[j] { + return None; + } + } + + unsafe { + blst::blst_bendian_from_fp(fp_row.as_mut_ptr(), &c_fp1[1]); + } + + for j in 0..BLS_FP_SIZE { + if fp_row[j] != item_data[j] { + return None; + } + } + + Some(blst::blst_fp2 { fp: c_fp1 }) +} + +fn check_input_size(data: &[u8], item_size: usize, fn_name: &str) -> Result<()> { + if data.len() % item_size != 0 { + return Err(HostError::BLS12381InvalidInput { + msg: format!( + "Incorrect input length for {}: {} is not divisible by {}", + fn_name, + data.len(), + item_size + ), + } + .into()); + } + + Ok(()) +} diff --git a/runtime/near-vm-runner/src/logic/context.rs b/runtime/near-vm-runner/src/logic/context.rs index 869aa24554f..c2b252dbd40 100644 --- a/runtime/near-vm-runner/src/logic/context.rs +++ b/runtime/near-vm-runner/src/logic/context.rs @@ -1,4 +1,4 @@ -use super::types::PublicKey; +use super::types::{PromiseResult, PublicKey}; use near_primitives_core::config::ViewConfig; use near_primitives_core::types::{ AccountId, Balance, BlockHeight, EpochHeight, Gas, StorageUsage, @@ -20,9 +20,14 @@ pub struct VMContext { /// If this execution is the result of direct execution of transaction then it /// is equal to `signer_account_id`. pub predecessor_account_id: AccountId, + /// The name of the method to invoke. + pub method: String, /// The input to the contract call. /// Encoded as base64 string to be able to pass input in borsh binary format. pub input: Vec, + /// If this method execution is invoked directly as a callback by one or more contract calls + /// the results of the methods that made the callback are stored in this collection. + pub promise_results: std::sync::Arc<[PromiseResult]>, /// The current block height. pub block_height: BlockHeight, /// The current block timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC). diff --git a/runtime/near-vm-runner/src/logic/dependencies.rs b/runtime/near-vm-runner/src/logic/dependencies.rs index dcdde004110..723fce06e9f 100644 --- a/runtime/near-vm-runner/src/logic/dependencies.rs +++ b/runtime/near-vm-runner/src/logic/dependencies.rs @@ -488,4 +488,10 @@ pub trait External { /// /// Panics if `ReceiptIndex` is invalid. fn get_receipt_receiver(&self, receipt_index: ReceiptIndex) -> &AccountId; + + /// Hash of the contract for the current account. + fn code_hash(&self) -> CryptoHash; + + /// Get the contract code + fn get_contract(&self) -> Option>; } diff --git a/runtime/near-vm-runner/src/logic/errors.rs b/runtime/near-vm-runner/src/logic/errors.rs index 945c2383765..1ec8655a11a 100644 --- a/runtime/near-vm-runner/src/logic/errors.rs +++ b/runtime/near-vm-runner/src/logic/errors.rs @@ -29,6 +29,8 @@ pub enum VMRunnerError { Nondeterministic(String), #[error("unknown error during contract execution: {debug_message}")] WasmUnknownError { debug_message: String }, + #[error("account has no associated contract code")] + ContractCodeNotPresent, } /// Permitted errors that cause a function call to fail gracefully. @@ -56,15 +58,16 @@ pub enum FunctionCallError { #[derive(Debug, thiserror::Error, strum::IntoStaticStr)] pub enum CacheError { - #[error("cache read error")] + #[error("cache read error: {0}")] ReadError(#[source] io::Error), - #[error("cache write error")] + #[error("cache write error: {0}")] WriteError(#[source] io::Error), #[error("cache deserialization error")] DeserializationError, #[error("cache serialization error")] SerializationError { hash: [u8; 32] }, } + /// A kind of a trap happened during execution of a binary #[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)] pub enum WasmTrap { @@ -107,6 +110,12 @@ pub enum CompilationError { WasmerCompileError { msg: String, }, + /// This is for defense in depth. + /// We expect our runtime-independent preparation code to fully catch all invalid wasms, + /// but, if it ever misses something we’ll emit this error + WasmtimeCompileError { + msg: String, + }, } #[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)] @@ -207,6 +216,9 @@ pub enum HostError { /// Invalid input to ed25519 signature verification function (e.g. signature cannot be /// derived from bytes). Ed25519VerifyInvalidInput { msg: String }, + // Invalid input to bls12381 family of functions + #[cfg(feature = "protocol_feature_bls12381")] + BLS12381InvalidInput { msg: String }, /// Yield payload length exceeds the maximum permitted. YieldPayloadLength { length: u64, limit: u64 }, /// Yield resumption data id is malformed. @@ -339,6 +351,9 @@ impl fmt::Display for CompilationError { CompilationError::WasmerCompileError { msg } => { write!(f, "Wasmer compilation error: {}", msg) } + CompilationError::WasmtimeCompileError { msg } => { + write!(f, "Wasmtime compilation error: {}", msg) + } } } } @@ -442,6 +457,8 @@ impl std::fmt::Display for HostError { Ed25519VerifyInvalidInput { msg } => { write!(f, "ED25519 signature verification error: {}", msg) } + #[cfg(feature = "protocol_feature_bls12381")] + BLS12381InvalidInput { msg } => write!(f, "BLS12-381 invalid input: {}", msg), YieldPayloadLength { length, limit } => write!( f, "Yield resume payload is {length} bytes which exceeds the {limit} byte limit" diff --git a/runtime/near-vm-runner/src/logic/logic.rs b/runtime/near-vm-runner/src/logic/logic.rs index e2e6ad7795e..d5948e29f32 100644 --- a/runtime/near-vm-runner/src/logic/logic.rs +++ b/runtime/near-vm-runner/src/logic/logic.rs @@ -7,6 +7,8 @@ use super::types::{PromiseIndex, PromiseResult, ReceiptIndex, ReturnData}; use super::utils::split_method_names; use super::ValuePtr; use super::{HostError, VMLogicError}; +#[cfg(feature = "protocol_feature_bls12381")] +use crate::bls12381_impl; use crate::ProfileDataV3; use near_crypto::Secp256K1Signature; use near_parameters::vm::{Config, StorageGetMode}; @@ -19,6 +21,7 @@ use near_primitives_core::types::{ AccountId, Balance, Compute, EpochHeight, Gas, GasWeight, StorageUsage, }; use std::mem::size_of; +use std::sync::Arc; use ExtCosts::*; pub type Result = ::std::result::Result; @@ -29,6 +32,185 @@ fn base64(s: &[u8]) -> String { base64::engine::general_purpose::STANDARD.encode(s) } +/// Structure representing the results and outcomes of a contract execution. +/// +/// This is a subset of [`VMLogic`] that's strictly necessary to produce `VMOutcome`s. +pub struct ExecutionResultState { + /// All gas and economic parameters required during contract execution. + pub(crate) config: Arc, + /// Gas tracking for the current contract execution. + gas_counter: GasCounter, + /// Logs written by the runtime. + logs: Vec, + /// Tracks the total log length. The sum of length of all logs. + total_log_length: u64, + /// What method returns. + return_data: ReturnData, + /// Keeping track of the current account balance, which can decrease when we create promises + /// and attach balance to them. + current_account_balance: Balance, + /// Storage usage of the current account at the moment + current_storage_usage: StorageUsage, +} + +impl ExecutionResultState { + /// Create a new state. + /// + /// # Panics + /// + /// Note that `context.account_balance + context.attached_deposit` must not overflow `u128`, + /// otherwise this function will panic. + pub fn new(context: &VMContext, config: Arc) -> Self { + let current_account_balance = context + .account_balance + .checked_add(context.attached_deposit) + .expect("current_account_balance overflowed"); + let current_storage_usage = context.storage_usage; + let max_gas_burnt = match context.view_config { + Some(ViewConfig { max_gas_burnt: max_gas_burnt_view }) => max_gas_burnt_view, + None => config.limit_config.max_gas_burnt, + }; + let gas_counter = GasCounter::new( + config.ext_costs.clone(), + max_gas_burnt, + config.regular_op_cost, + context.prepaid_gas, + context.is_view(), + ); + Self { + config, + gas_counter, + logs: vec![], + total_log_length: 0, + return_data: ReturnData::None, + current_account_balance, + current_storage_usage, + } + } + + /// A helper function to subtract balance on transfer or attached deposit for promises. + /// + /// ### Args + /// + /// * `amount`: the amount to deduct from the current account balance. + fn deduct_balance(&mut self, amount: Balance) -> Result<()> { + self.current_account_balance = + self.current_account_balance.checked_sub(amount).ok_or(HostError::BalanceExceeded)?; + Ok(()) + } + + /// Checks that the current log number didn't reach the limit yet, so we can add a new message. + fn check_can_add_a_log_message(&self) -> Result<()> { + if self.logs.len() as u64 >= self.config.limit_config.max_number_logs { + Err(HostError::NumberOfLogsExceeded { limit: self.config.limit_config.max_number_logs } + .into()) + } else { + Ok(()) + } + } + + fn checked_push_log(&mut self, message: String) -> Result<()> { + let len = u64::try_from(message.len()).unwrap_or(u64::MAX); + let Some(total_log_length) = self.total_log_length.checked_add(len) else { + return self.total_log_length_exceeded(len); + }; + self.total_log_length = total_log_length; + if self.total_log_length > self.config.limit_config.max_total_log_length { + return self.total_log_length_exceeded(len); + } + self.logs.push(message); + Ok(()) + } + + fn total_log_length_exceeded(&self, add_len: u64) -> Result { + Err(HostError::TotalLogLengthExceeded { + length: self.total_log_length.saturating_add(add_len), + limit: self.config.limit_config.max_total_log_length, + } + .into()) + } + + /// Computes the outcome of the execution. + /// + /// If `FunctionCallWeight` protocol feature is enabled, unused gas will be + /// distributed to functions that specify a gas weight. If there are no functions with + /// a gas weight, the outcome will contain unused gas as usual. + pub fn compute_outcome(self) -> VMOutcome { + let burnt_gas = self.gas_counter.burnt_gas(); + let used_gas = self.gas_counter.used_gas(); + + let mut profile = self.gas_counter.profile_data(); + profile.compute_wasm_instruction_cost(burnt_gas); + let compute_usage = profile.total_compute_usage(&self.config.ext_costs); + + VMOutcome { + balance: self.current_account_balance, + storage_usage: self.current_storage_usage, + return_data: self.return_data, + burnt_gas, + used_gas, + compute_usage, + logs: self.logs, + profile, + aborted: None, + } + } + + /// Add a cost for loading the contract code in the VM. + /// + /// This cost does not consider the structure of the contract code, only the + /// size. This is currently the only loading fee. A fee that takes the code + /// structure into consideration could be added. But since that would have + /// to happen after loading, we cannot pre-charge it. This is the main + /// motivation to (only) have this simple fee. + pub fn add_contract_loading_fee(&mut self, code_len: u64) -> Result<()> { + self.gas_counter.pay_per(contract_loading_bytes, code_len)?; + self.gas_counter.pay_base(contract_loading_base) + } + + /// VM independent setup before loading the executable. + /// + /// Does VM independent checks that happen after the instantiation of + /// VMLogic but before loading the executable. This includes pre-charging gas + /// costs for loading the executable, which depends on the size of the WASM code. + pub fn before_loading_executable( + &mut self, + method_name: &str, + wasm_code_bytes: u64, + ) -> std::result::Result<(), super::errors::FunctionCallError> { + if method_name.is_empty() { + let error = super::errors::FunctionCallError::MethodResolveError( + super::errors::MethodResolveError::MethodEmptyName, + ); + return Err(error); + } + if self.config.fix_contract_loading_cost { + if self.add_contract_loading_fee(wasm_code_bytes).is_err() { + let error = + super::errors::FunctionCallError::HostError(super::HostError::GasExceeded); + return Err(error); + } + } + Ok(()) + } + + /// Legacy code to preserve old gas charging behaviour in old protocol versions. + pub fn after_loading_executable( + &mut self, + wasm_code_bytes: u64, + ) -> std::result::Result<(), super::errors::FunctionCallError> { + if !self.config.fix_contract_loading_cost { + if self.add_contract_loading_fee(wasm_code_bytes).is_err() { + return Err(super::errors::FunctionCallError::HostError( + super::HostError::GasExceeded, + )); + } + } + Ok(()) + } +} + +/// Structure pub struct VMLogic<'a> { /// Provides access to the components outside the Wasm runtime for operations on the trie and /// receipts creation. @@ -36,41 +218,28 @@ pub struct VMLogic<'a> { /// Part of Context API and Economics API that was extracted from the receipt. context: &'a VMContext, /// All gas and economic parameters required during contract execution. - pub(crate) config: &'a Config, - /// Fees for creating (async) actions on runtime. - fees_config: &'a RuntimeFeesConfig, - /// If this method execution is invoked directly as a callback by one or more contract calls the - /// results of the methods that made the callback are stored in this collection. - promise_results: &'a [PromiseResult], + config: Arc, + /// Fees charged for various operations that contract may execute. + fees_config: Arc, /// Pointer to the guest memory. - memory: super::vmstate::Memory<'a>, + memory: super::vmstate::Memory, - /// Keeping track of the current account balance, which can decrease when we create promises - /// and attach balance to them. - current_account_balance: Balance, /// Current amount of locked tokens, does not automatically change when staking transaction is /// issued. current_account_locked_balance: Balance, - /// Storage usage of the current account at the moment - current_storage_usage: StorageUsage, - gas_counter: GasCounter, - /// Tracks size of the recorded trie storage proof. - recorded_storage_counter: RecordedStorageCounter, - /// What method returns. - return_data: ReturnData, - /// Logs written by the runtime. - logs: Vec, /// Registers can be used by the guest to store blobs of data without moving them across /// host-guest boundary. registers: super::vmstate::Registers, - /// The DAG of promises, indexed by promise id. promises: Vec, - /// Tracks the total log length. The sum of length of all logs. - total_log_length: u64, /// Stores the amount of stack space remaining remaining_stack: u64, + + /// Tracks size of the recorded trie storage proof. + recorded_storage_counter: RecordedStorageCounter, + + pub(crate) result_state: ExecutionResultState, } /// Promises API allows to create a DAG-structure that defines dependencies between smart contract @@ -93,7 +262,7 @@ enum Promise { macro_rules! get_memory_or_register { ($logic:expr, $offset:expr, $len:expr) => { super::vmstate::get_memory_or_register( - &mut $logic.gas_counter, + &mut $logic.result_state.gas_counter, &$logic.memory, &$logic.registers, $offset, @@ -132,60 +301,40 @@ impl<'a> VMLogic<'a> { pub fn new( ext: &'a mut dyn External, context: &'a VMContext, - config: &'a Config, - fees_config: &'a RuntimeFeesConfig, - promise_results: &'a [PromiseResult], - memory: &'a mut dyn MemoryLike, + fees_config: Arc, + result_state: ExecutionResultState, + memory: impl MemoryLike + 'static, ) -> Self { - // Overflow should be checked before calling VMLogic. - let current_account_balance = context.account_balance + context.attached_deposit; - let current_storage_usage = context.storage_usage; - let max_gas_burnt = match context.view_config { - Some(ViewConfig { max_gas_burnt: max_gas_burnt_view }) => max_gas_burnt_view, - None => config.limit_config.max_gas_burnt, - }; - let current_account_locked_balance = context.account_locked_balance; - let gas_counter = GasCounter::new( - config.ext_costs.clone(), - max_gas_burnt, - config.regular_op_cost, - context.prepaid_gas, - context.is_view(), - ); + let config = Arc::clone(&result_state.config); let recorded_storage_counter = RecordedStorageCounter::new( ext.get_recorded_storage_size(), config.limit_config.per_receipt_storage_proof_size_limit, ); + let remaining_stack = u64::from(config.limit_config.max_stack_height); Self { ext, context, config, fees_config, - promise_results, memory: super::vmstate::Memory::new(memory), - current_account_balance, current_account_locked_balance, - current_storage_usage, - gas_counter, recorded_storage_counter, - return_data: ReturnData::None, - logs: vec![], registers: Default::default(), promises: vec![], - total_log_length: 0, - remaining_stack: u64::from(config.limit_config.max_stack_height), + remaining_stack, + result_state, } } /// Returns reference to logs that have been created so far. pub fn logs(&self) -> &[String] { - &self.logs + &self.result_state.logs } #[cfg(test)] pub(super) fn gas_counter(&self) -> &GasCounter { - &self.gas_counter + &self.result_state.gas_counter } #[cfg(test)] @@ -194,7 +343,7 @@ impl<'a> VMLogic<'a> { } #[cfg(test)] - pub(super) fn memory(&mut self) -> &mut super::vmstate::Memory<'a> { + pub(super) fn memory(&mut self) -> &mut super::vmstate::Memory { &mut self.memory } @@ -235,7 +384,12 @@ impl<'a> VMLogic<'a> { /// Convenience function for testing. #[cfg(test)] pub fn wrapped_internal_write_register(&mut self, register_id: u64, data: &[u8]) -> Result<()> { - self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, data) + self.registers.set( + &mut self.result_state.gas_counter, + &self.config.limit_config, + register_id, + data, + ) } /// Writes the entire content from the register `register_id` into the memory of the guest starting with `ptr`. @@ -259,9 +413,9 @@ impl<'a> VMLogic<'a> { /// /// `base + read_register_base + read_register_byte * num_bytes + write_memory_base + write_memory_byte * num_bytes` pub fn read_register(&mut self, register_id: u64, ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - let data = self.registers.get(&mut self.gas_counter, register_id)?; - self.memory.set(&mut self.gas_counter, ptr, data) + self.result_state.gas_counter.pay_base(base)?; + let data = self.registers.get(&mut self.result_state.gas_counter, register_id)?; + self.memory.set(&mut self.result_state.gas_counter, ptr, data) } /// Returns the size of the blob stored in the given register. @@ -276,7 +430,7 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn register_len(&mut self, register_id: u64) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; Ok(self.registers.get_len(register_id).unwrap_or(u64::MAX)) } @@ -294,10 +448,16 @@ impl<'a> VMLogic<'a> { /// /// `base + read_memory_base + read_memory_bytes * num_bytes + write_register_base + write_register_bytes * num_bytes` pub fn write_register(&mut self, register_id: u64, data_len: u64, data_ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - let data = - self.memory.view(&mut self.gas_counter, MemSlice { ptr: data_ptr, len: data_len })?; - self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, data) + self.result_state.gas_counter.pay_base(base)?; + let data = self + .memory + .view(&mut self.result_state.gas_counter, MemSlice { ptr: data_ptr, len: data_len })?; + self.registers.set( + &mut self.result_state.gas_counter, + &self.config.limit_config, + register_id, + data, + ) } // ################################### @@ -322,30 +482,36 @@ impl<'a> VMLogic<'a> { /// For nul-terminated string: /// `(read_memory_base + read_memory_byte) * num_bytes + utf8_decoding_base + utf8_decoding_byte * num_bytes` fn get_utf8_string(&mut self, len: u64, ptr: u64) -> Result { - self.gas_counter.pay_base(utf8_decoding_base)?; + self.result_state.gas_counter.pay_base(utf8_decoding_base)?; let mut buf; - let max_len = - self.config.limit_config.max_total_log_length.saturating_sub(self.total_log_length); + let max_len = self + .config + .limit_config + .max_total_log_length + .saturating_sub(self.result_state.total_log_length); if len != u64::MAX { if len > max_len { - return self.total_log_length_exceeded(len); + return self.result_state.total_log_length_exceeded(len); } - buf = self.memory.view(&mut self.gas_counter, MemSlice { ptr, len })?.into_owned(); + buf = self + .memory + .view(&mut self.result_state.gas_counter, MemSlice { ptr, len })? + .into_owned(); } else { buf = vec![]; for i in 0..=max_len { // self.memory_get_u8 will check for u64 overflow on the first iteration (i == 0) - let el = self.memory.get_u8(&mut self.gas_counter, ptr + i)?; + let el = self.memory.get_u8(&mut self.result_state.gas_counter, ptr + i)?; if el == 0 { break; } if i == max_len { - return self.total_log_length_exceeded(max_len.saturating_add(1)); + return self.result_state.total_log_length_exceeded(max_len.saturating_add(1)); } buf.push(el); } } - self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as _)?; + self.result_state.gas_counter.pay_per(utf8_decoding_byte, buf.len() as _)?; String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into()) } @@ -375,23 +541,26 @@ impl<'a> VMLogic<'a> { /// For nul-terminated string: /// `read_memory_base * num_bytes / 2 + read_memory_byte * num_bytes + utf16_decoding_base + utf16_decoding_byte * num_bytes` fn get_utf16_string(&mut self, mut len: u64, ptr: u64) -> Result { - self.gas_counter.pay_base(utf16_decoding_base)?; - let max_len = - self.config.limit_config.max_total_log_length.saturating_sub(self.total_log_length); + self.result_state.gas_counter.pay_base(utf16_decoding_base)?; + let max_len = self + .config + .limit_config + .max_total_log_length + .saturating_sub(self.result_state.total_log_length); let mem_view = if len == u64::MAX { len = self.get_nul_terminated_utf16_len(ptr, max_len)?; self.memory.view_for_free(MemSlice { ptr, len }) } else { - self.memory.view(&mut self.gas_counter, MemSlice { ptr, len }) + self.memory.view(&mut self.result_state.gas_counter, MemSlice { ptr, len }) }?; let input = stdx::as_chunks_exact(&mem_view).map_err(|_| HostError::BadUTF16)?; if len > max_len { - return self.total_log_length_exceeded(len); + return self.result_state.total_log_length_exceeded(len); } - self.gas_counter.pay_per(utf16_decoding_byte, len)?; + self.result_state.gas_counter.pay_per(utf16_decoding_byte, len)?; char::decode_utf16(input.into_iter().copied().map(u16::from_le_bytes)) .collect::>() .map_err(|_| HostError::BadUTF16.into()) @@ -405,13 +574,15 @@ impl<'a> VMLogic<'a> { fn get_nul_terminated_utf16_len(&mut self, ptr: u64, max_len: u64) -> Result { let mut len = 0; loop { - if self.memory.get_u16(&mut self.gas_counter, ptr.saturating_add(len))? == 0 { + if self.memory.get_u16(&mut self.result_state.gas_counter, ptr.saturating_add(len))? + == 0 + { return Ok(len); } len = match len.checked_add(2) { Some(len) if len <= max_len => len, - Some(len) => return self.total_log_length_exceeded(len), - None => return self.total_log_length_exceeded(u64::MAX), + Some(len) => return self.result_state.total_log_length_exceeded(len), + None => return self.result_state.total_log_length_exceeded(u64::MAX), }; } } @@ -420,16 +591,6 @@ impl<'a> VMLogic<'a> { // # Helper functions to prevent code duplication API # // #################################################### - /// Checks that the current log number didn't reach the limit yet, so we can add a new message. - fn check_can_add_a_log_message(&self) -> Result<()> { - if self.logs.len() as u64 >= self.config.limit_config.max_number_logs { - Err(HostError::NumberOfLogsExceeded { limit: self.config.limit_config.max_number_logs } - .into()) - } else { - Ok(()) - } - } - /// Adds a given promise to the vector of promises and returns a new promise index. /// Throws `NumberPromisesExceeded` if the total number of promises exceeded the limit. fn checked_push_promise(&mut self, promise: Promise) -> Result { @@ -448,24 +609,6 @@ impl<'a> VMLogic<'a> { } } - fn checked_push_log(&mut self, message: String) -> Result<()> { - // The size of logged data can't be too large. No overflow. - self.total_log_length += message.len() as u64; - if self.total_log_length > self.config.limit_config.max_total_log_length { - return self.total_log_length_exceeded(0); - } - self.logs.push(message); - Ok(()) - } - - fn total_log_length_exceeded(&self, add_len: u64) -> Result { - Err(HostError::TotalLogLengthExceeded { - length: self.total_log_length.saturating_add(add_len), - limit: self.config.limit_config.max_total_log_length, - } - .into()) - } - fn get_public_key(&mut self, ptr: u64, len: u64) -> Result { Ok(PublicKeyBuffer::new(&get_memory_or_register!(self, ptr, len)?)) } @@ -484,9 +627,9 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes` pub fn current_account_id(&mut self, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, self.context.current_account_id.as_bytes(), @@ -507,7 +650,7 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes` pub fn signer_account_id(&mut self, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { @@ -516,7 +659,7 @@ impl<'a> VMLogic<'a> { .into()); } self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, self.context.signer_account_id.as_bytes(), @@ -536,7 +679,7 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes` pub fn signer_account_pk(&mut self, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { @@ -545,7 +688,7 @@ impl<'a> VMLogic<'a> { .into()); } self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, self.context.signer_account_pk.as_slice(), @@ -565,7 +708,7 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes` pub fn predecessor_account_id(&mut self, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { @@ -574,7 +717,7 @@ impl<'a> VMLogic<'a> { .into()); } self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, self.context.predecessor_account_id.as_bytes(), @@ -589,10 +732,10 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes` pub fn input(&mut self, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, self.context.input.as_slice(), @@ -608,7 +751,7 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn block_index(&mut self) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; Ok(self.context.block_height) } @@ -618,7 +761,7 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn block_timestamp(&mut self) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; Ok(self.context.block_timestamp) } @@ -628,7 +771,7 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn epoch_height(&mut self) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; Ok(self.context.epoch_height) } @@ -644,11 +787,11 @@ impl<'a> VMLogic<'a> { account_id_ptr: u64, stake_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?; - self.gas_counter.pay_base(validator_stake_base)?; + self.result_state.gas_counter.pay_base(validator_stake_base)?; let balance = self.ext.validator_stake(&account_id)?.unwrap_or_default(); - self.memory.set_u128(&mut self.gas_counter, stake_ptr, balance) + self.memory.set_u128(&mut self.result_state.gas_counter, stake_ptr, balance) } /// Get the total validator stake of the current epoch. @@ -659,10 +802,10 @@ impl<'a> VMLogic<'a> { /// /// `base + memory_write_base + memory_write_size * 16 + validator_total_stake_base` pub fn validator_total_stake(&mut self, stake_ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - self.gas_counter.pay_base(validator_total_stake_base)?; + self.result_state.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(validator_total_stake_base)?; let total_stake = self.ext.validator_total_stake()?; - self.memory.set_u128(&mut self.gas_counter, stake_ptr, total_stake) + self.memory.set_u128(&mut self.result_state.gas_counter, stake_ptr, total_stake) } /// Returns the number of bytes used by the contract if it was saved to the trie as of the @@ -676,8 +819,8 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn storage_usage(&mut self) -> Result { - self.gas_counter.pay_base(base)?; - Ok(self.current_storage_usage) + self.result_state.gas_counter.pay_base(base)?; + Ok(self.result_state.current_storage_usage) } // ################# @@ -691,8 +834,12 @@ impl<'a> VMLogic<'a> { /// /// `base + memory_write_base + memory_write_size * 16` pub fn account_balance(&mut self, balance_ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - self.memory.set_u128(&mut self.gas_counter, balance_ptr, self.current_account_balance) + self.result_state.gas_counter.pay_base(base)?; + self.memory.set_u128( + &mut self.result_state.gas_counter, + balance_ptr, + self.result_state.current_account_balance, + ) } /// The current amount of tokens locked due to staking. @@ -701,9 +848,9 @@ impl<'a> VMLogic<'a> { /// /// `base + memory_write_base + memory_write_size * 16` pub fn account_locked_balance(&mut self, balance_ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; self.memory.set_u128( - &mut self.gas_counter, + &mut self.result_state.gas_counter, balance_ptr, self.current_account_locked_balance, ) @@ -720,9 +867,13 @@ impl<'a> VMLogic<'a> { /// /// `base + memory_write_base + memory_write_size * 16` pub fn attached_deposit(&mut self, balance_ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; - self.memory.set_u128(&mut self.gas_counter, balance_ptr, self.context.attached_deposit) + self.memory.set_u128( + &mut self.result_state.gas_counter, + balance_ptr, + self.context.attached_deposit, + ) } /// The amount of gas attached to the call that can be used to pay for the gas fees. @@ -735,7 +886,7 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn prepaid_gas(&mut self) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err( HostError::ProhibitedInView { method_name: "prepaid_gas".to_string() }.into() @@ -754,11 +905,11 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn used_gas(&mut self) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "used_gas".to_string() }.into()); } - Ok(self.gas_counter.used_gas()) + Ok(self.result_state.gas_counter.used_gas()) } // ############ @@ -798,15 +949,22 @@ impl<'a> VMLogic<'a> { value_ptr: u64, register_id: u64, ) -> Result<()> { - self.gas_counter.pay_base(alt_bn128_g1_multiexp_base)?; + self.result_state.gas_counter.pay_base(alt_bn128_g1_multiexp_base)?; let data = get_memory_or_register!(self, value_ptr, value_len)?; let elements = super::alt_bn128::split_elements(&data)?; - self.gas_counter.pay_per(alt_bn128_g1_multiexp_element, elements.len() as u64)?; + self.result_state + .gas_counter + .pay_per(alt_bn128_g1_multiexp_element, elements.len() as u64)?; let res = super::alt_bn128::g1_multiexp(elements)?; - self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, res) + self.registers.set( + &mut self.result_state.gas_counter, + &self.config.limit_config, + register_id, + res, + ) } /// Computes sum for signed g1 group elements on alt_bn128 curve \sum_i @@ -841,15 +999,20 @@ impl<'a> VMLogic<'a> { value_ptr: u64, register_id: u64, ) -> Result<()> { - self.gas_counter.pay_base(alt_bn128_g1_sum_base)?; + self.result_state.gas_counter.pay_base(alt_bn128_g1_sum_base)?; let data = get_memory_or_register!(self, value_ptr, value_len)?; let elements = super::alt_bn128::split_elements(&data)?; - self.gas_counter.pay_per(alt_bn128_g1_sum_element, elements.len() as u64)?; + self.result_state.gas_counter.pay_per(alt_bn128_g1_sum_element, elements.len() as u64)?; let res = super::alt_bn128::g1_sum(elements)?; - self.registers.set(&mut self.gas_counter, &self.config.limit_config, register_id, res) + self.registers.set( + &mut self.result_state.gas_counter, + &self.config.limit_config, + register_id, + res, + ) } /// Computes pairing check on alt_bn128 curve. @@ -869,8 +1032,8 @@ impl<'a> VMLogic<'a> { /// /// # Errors /// - /// If `value_len + value_ptr` points outside the memory or the registers use more memory than - /// the function returns `MemoryAccessViolation`. + /// If `value_len + value_ptr` points outside the memory or the registers + /// use more memory than the limit the function returns `MemoryAccessViolation`. /// /// If point coordinates are not on curve, point is not in the subgroup, scalar /// is not in the field or data are wrong serialized, for example, @@ -880,17 +1043,431 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes + alt_bn128_pairing_base + alt_bn128_pairing_element * num_elements` pub fn alt_bn128_pairing_check(&mut self, value_len: u64, value_ptr: u64) -> Result { - self.gas_counter.pay_base(alt_bn128_pairing_check_base)?; + self.result_state.gas_counter.pay_base(alt_bn128_pairing_check_base)?; let data = get_memory_or_register!(self, value_ptr, value_len)?; let elements = super::alt_bn128::split_elements(&data)?; - self.gas_counter.pay_per(alt_bn128_pairing_check_element, elements.len() as u64)?; + self.result_state + .gas_counter + .pay_per(alt_bn128_pairing_check_element, elements.len() as u64)?; let res = super::alt_bn128::pairing_check(elements)?; Ok(res as u64) } + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Calculates the sum of signed elements on the BLS12-381 curve. + It accepts an arbitrary number of pairs (sign_i, p_i), + where p_i from E(Fp) and sign_i is 0 or 1. + It calculates sum_i (-1)^{sign_i} * p_i + + # Arguments + + * `value` - sequence of (sign:bool, p:E(Fp)), where + p is point (x:Fp, y:Fp) on BLS12-381, + BLS12-381 is Y^2 = X^3 + 4 curve over Fp. + + `value` is encoded as packed `[(u8, ([u8;48], [u8;48]))]` slice. + `0u8` is positive sign, `1u8` -- negative. + Elements from Fp encoded as big-endian [u8;48]. + + # Output + + If the input data is correct returns 0 and the 96 bytes represent + the resulting points from E(Fp) which will be written to the register with + the register_id identifier + + If one of the points not on the curve, + the sign or points are incorrectly encoded then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 97 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + + `base + write_register_base + write_register_byte * num_bytes + + bls12381_p1_sum_base + bls12381_p1_sum_element * num_elements` + ", + bls12381_p1_sum, + 97, + bls12381_p1_sum_base, + bls12381_p1_sum_element, + p1_sum + ); + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Calculates the sum of signed elements on the twisted BLS12-381 curve. + It accepts an arbitrary number of pairs (sign_i, p_i), + where p_i from E'(Fp^2) and sign_i is 0 or 1. + It calculates sum_i (-1)^{sign_i} * p_i + + # Arguments + + * `value` - sequence of (sign:bool, p:E'(Fp^2)), where + p is point (x:Fp^2, y:Fp^2) on twisted BLS12-381, + twisted BLS12-381 is Y^2 = X^3 + 4(u + 1) curve over Fp^2. + + `value` is encoded as packed `[(u8, ([u8;96], [u8;96]))]` slice. + `0u8` is positive, `1u8` is negative. + Elements q = c0 + c1 * u from Fp^2 encoded as concatenation of c1 and c0, + where c1 and c0 from Fp and encoded as big-endian [u8;48]. + + # Output + + If the input data is correct returns 0 and the 192 bytes represent + the resulting points from E'(Fp^2) which will be written to the register with + the register_id identifier + + If one of the points not on the curve, + the sign or points are incorrectly encoded then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 193 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + + `base + write_register_base + write_register_byte * num_bytes + + bls12381_p2_sum_base + bls12381_p2_sum_element * num_elements` + ", + bls12381_p2_sum, + 193, + bls12381_p2_sum_base, + bls12381_p2_sum_element, + p2_sum + ); + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Calculates multiexp on BLS12-381 curve: + accepts an arbitrary number of pairs (p_i, s_i), + where p_i from G1 and s_i is a scalar and + calculates sum_i s_i*p_i + + # Arguments + + * `value` - sequence of (p:E(Fp), s:u256), where + p is point (x:Fp, y:Fp) on BLS12-381, + BLS12-381 is Y^2 = X^3 + 4 curve over Fp. + + `value` is encoded as packed `[(([u8;48], [u8;48]), [u8;32])]` slice. + Elements from Fp encoded as big-endian [u8;48]. + Scalars encoded as little-endian [u8;32]. + + # Output + + If the input data is correct returns 0 and the 96 bytes represent + the resulting points from G1 which will be written to the register with + the register_id identifier + + If one of the points not from G1 subgroup + or points are incorrectly encoded then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 128 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + + `base + write_register_base + write_register_byte * num_bytes + + bls12381_g1_multiexp_base + bls12381_g1_multiexp_element * num_elements` + ", + bls12381_g1_multiexp, + 128, + bls12381_g1_multiexp_base, + bls12381_g1_multiexp_element, + g1_multiexp + ); + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Calculates multiexp on twisted BLS12-381 curve: + accepts an arbitrary number of pairs (p_i, s_i), + where p_i from G2 and s_i is a scalar and + calculates sum_i s_i*p_i + + # Arguments + + * `value` - sequence of (p:E'(Fp^2), s:u256), where + p is point (x:Fp^2, y:Fp^2) on twisted BLS12-381, + BLS12-381 is Y^2 = X^3 + 4(u + 1) curve over Fp^2. + + `value` is encoded as packed `[(([u8;96], [u8;96]), [u8;32])]` slice. + Elements q = c0 + c1 * u from Fp^2 encoded as concatenation of c1 and c0, + where c1 and c0 from Fp and encoded as big-endian [u8;48]. + Scalars encoded as little-endian [u8;32]. + + # Output + + If the input data is correct returns 0 and the 192 bytes represent + the resulting points from G2 which will be written to the register with + the register_id identifier + + If one of the points not from G2 subgroup + or points are incorrectly encoded then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 224 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + + `base + write_register_base + write_register_byte * num_bytes + + bls12381_g2_multiexp_base + bls12381_g2_multiexp_element * num_elements` + ", + bls12381_g2_multiexp, + 224, + bls12381_g2_multiexp_base, + bls12381_g2_multiexp_element, + g2_multiexp + ); + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Maps elements from Fp to the G1 subgroup of BLS12-381 curve. + + # Arguments + + * `value` - sequence of p from Fp. + + `value` is encoded as packed `[[u8;48]]` slice. + Elements from Fp encoded as big-endian [u8;48]. + + # Output + + If the input data is correct returns 0 and the 96*num_elements bytes represent + the resulting points from G1 which will be written to the register with + the register_id identifier + + If one of the element >= p, then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 48 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + + `base + write_register_base + write_register_byte * num_bytes + + bls12381_map_fp_to_g1_base + bls12381_map_fp_to_g1_element * num_elements` + ", + bls12381_map_fp_to_g1, + 48, + bls12381_map_fp_to_g1_base, + bls12381_map_fp_to_g1_element, + map_fp_to_g1 + ); + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Maps elements from Fp^2 to the G2 subgroup of twisted BLS12-381 curve. + + # Arguments + + * `value` - sequence of p from Fp^2. + + `value` is encoded as packed `[[u8;96]]` slice. + Elements q = c0 + c1 * u from Fp^2 encoded as concatenation of c1 and c0, + where c1 and c0 from Fp and encoded as big-endian [u8;48]. + + # Output + + If the input data is correct returns 0 and the 192*num_elements bytes represent + the resulting points from G2 which will be written to the register with + the register_id identifier + + If one of the element not valid Fp^2, then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 96 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + `base + write_register_base + write_register_byte * num_bytes + + bls12381_map_fp2_to_g2_base + bls12381_map_fp2_to_g2_element * num_elements` + ", + bls12381_map_fp2_to_g2, + 96, + bls12381_map_fp2_to_g2_base, + bls12381_map_fp2_to_g2_element, + map_fp2_to_g2 + ); + + /// Computes pairing check on BLS12-381 curve. + /// In other words, computes whether \sum_i e(g_{1 i}, g_{2 i}) + /// is equal to one (in additive notation), where e(g1, g2) is the pairing function + /// + /// # Arguments + /// + /// * `value` - sequence of (g1:G1, g2:G2), where + /// g1 is point (x:Fp, y:Fp) on BLS12-381, + /// BLS12-381 is Y^2 = X^3 + 4 curve over Fp. + /// g2 is point (x:Fp^2, y:Fp^2) on twisted BLS12-381, + /// twisted BLS12-381 is Y^2 = X^3 + 4(u + 1) curve over Fp^2. + /// + /// `value` is encoded as packed `[(([u8;48], [u8;48]), ([u8;96], [u8;96]))]` slice. + /// Elements from Fp encoded as big-endian [u8;48]. + /// Elements q = c0 + c1 * u from Fp^2 encoded as concatenation of c1 and c0, + /// where c1 and c0 from Fp. + /// + /// # Output + /// + /// If the input data is correct and + /// the pairing result equals the multiplicative identity returns 0. + /// + /// If one of the points not on the curve, not from G1/G2 or + /// incorrectly encoded then 1 will be returned + /// + /// If the input data is correct and + /// the pairing result does NOT equal the multiplicative identity returns 2. + /// + /// # Errors + /// + /// If `value_len + value_ptr` points outside the memory or the registers + /// use more memory than the limit the function returns `MemoryAccessViolation`. + /// + /// If `value_len % 288 != 0`, the function returns `BLS12381InvalidInput`. + /// + /// # Cost + /// `base + write_register_base + write_register_byte * num_bytes + + /// bls12381_pairing_base + bls12381_pairing_element * num_elements` + #[cfg(feature = "protocol_feature_bls12381")] + pub fn bls12381_pairing_check(&mut self, value_len: u64, value_ptr: u64) -> Result { + self.result_state.gas_counter.pay_base(bls12381_pairing_base)?; + + const BLS_P1_SIZE: usize = 96; + const BLS_P2_SIZE: usize = 192; + const ITEM_SIZE: usize = BLS_P1_SIZE + BLS_P2_SIZE; + + let data = get_memory_or_register!(self, value_ptr, value_len)?; + let elements_count = data.len() / ITEM_SIZE; + + self.result_state.gas_counter.pay_per(bls12381_pairing_element, elements_count as u64)?; + + super::bls12381::pairing_check(&data) + } + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Decompress points from BLS12-381 curve. + + # Arguments + + * `value` - sequence of p:E(Fp), where + p is point in compressed format on BLS12-381, + BLS12-381 is Y^2 = X^3 + 4 curve over Fp. + + `value` is encoded as packed `[[u8;48]]` slice. + Where points (x: Fp, y: Fp) from E(Fp) encoded as + [u8; 48] -- big-endian x: Fp. y determined by the formula y=+-sqrt(x^3 + 4) + + The highest bit should be set as 1, the second-highest bit marks the point at infinity, + The third-highest bit represent the sign of y (0 for positive). + + # Output + + If the input data is correct returns 0 and the 96*num_elements bytes represent + the resulting uncompressed points from E(Fp) which will be written to the register with + the register_id identifier + + If one of the points not on the curve + or points are incorrectly encoded then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 48 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + `base + write_register_base + write_register_byte * num_bytes + + bls12381_p1_decompress_base + bls12381_p1_decompress_element * num_elements` + ", + bls12381_p1_decompress, + 48, + bls12381_p1_decompress_base, + bls12381_p1_decompress_element, + p1_decompress + ); + + #[cfg(feature = "protocol_feature_bls12381")] + bls12381_impl!( + r"Decompress points from twisted BLS12-381 curve. + + # Arguments + + * `value` - sequence of p:E'(Fp^2), where + p is point in compressed format on twisted BLS12-381, + twisted BLS12-381 is Y^2 = X^3 + 4(u + 1) curve over Fp^2. + + `value` is encoded as packed `[[u8;96]]` slice. + Where points (x: Fp^2, y: Fp^2) from E'(Fp^2) encoded as + [u8; 96] -- x: Fp^2. y determined by the formula y=+-sqrt(x^3 + 4(u + 1)) + + Elements q = c0 + c1 * u from Fp^2 encoded as concatenation of c1 and c0, + where c1 and c0 from Fp and encoded as big-endian [u8;48]. + + The highest bit should be set as 1, the second-highest bit marks the point at infinity, + The third-highest bit represent the sign of y (0 for positive). + + # Output + + If the input data is correct returns 0 and the 192*num_elements bytes represent + the resulting uncompressed points from E'(Fp^2) which will be written to the register with + the register_id identifier + + If one of the points not on the curve + or points are incorrectly encoded then 1 will be returned + and nothing will be written to the register. + + # Errors + + If `value_len + value_ptr` points outside the memory or the registers + use more memory than the limit the function returns `MemoryAccessViolation`. + + If `value_len % 96 != 0`, the function returns `BLS12381InvalidInput`. + + # Cost + + `base + write_register_base + write_register_byte * num_bytes + + bls12381_p2_decompress_base + bls12381_p2_decompress_element * num_elements` + ", + bls12381_p2_decompress, + 96, + bls12381_p2_decompress_base, + bls12381_p2_decompress_element, + p2_decompress + ); + /// Writes random seed into the register. /// /// # Errors @@ -901,9 +1478,9 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes`. pub fn random_seed(&mut self, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, self.context.random_seed.as_slice(), @@ -921,15 +1498,15 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes + sha256_base + sha256_byte * num_bytes` pub fn sha256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(sha256_base)?; + self.result_state.gas_counter.pay_base(sha256_base)?; let value = get_memory_or_register!(self, value_ptr, value_len)?; - self.gas_counter.pay_per(sha256_byte, value.len() as u64)?; + self.result_state.gas_counter.pay_per(sha256_byte, value.len() as u64)?; use sha2::Digest; let value_hash = sha2::Sha256::digest(&value); self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, value_hash.as_slice(), @@ -947,15 +1524,15 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes + keccak256_base + keccak256_byte * num_bytes` pub fn keccak256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(keccak256_base)?; + self.result_state.gas_counter.pay_base(keccak256_base)?; let value = get_memory_or_register!(self, value_ptr, value_len)?; - self.gas_counter.pay_per(keccak256_byte, value.len() as u64)?; + self.result_state.gas_counter.pay_per(keccak256_byte, value.len() as u64)?; use sha3::Digest; let value_hash = sha3::Keccak256::digest(&value); self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, value_hash.as_slice(), @@ -973,15 +1550,15 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes + keccak512_base + keccak512_byte * num_bytes` pub fn keccak512(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(keccak512_base)?; + self.result_state.gas_counter.pay_base(keccak512_base)?; let value = get_memory_or_register!(self, value_ptr, value_len)?; - self.gas_counter.pay_per(keccak512_byte, value.len() as u64)?; + self.result_state.gas_counter.pay_per(keccak512_byte, value.len() as u64)?; use sha3::Digest; let value_hash = sha3::Keccak512::digest(&value); self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, value_hash.as_slice(), @@ -1001,7 +1578,7 @@ impl<'a> VMLogic<'a> { /// /// `base + write_register_base + write_register_byte * num_bytes + ripemd160_base + ripemd160_block * message_blocks` pub fn ripemd160(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { - self.gas_counter.pay_base(ripemd160_base)?; + self.result_state.gas_counter.pay_base(ripemd160_base)?; let value = get_memory_or_register!(self, value_ptr, value_len)?; let message_blocks = value @@ -1011,13 +1588,13 @@ impl<'a> VMLogic<'a> { / 64 + 1; - self.gas_counter.pay_per(ripemd160_block, message_blocks as u64)?; + self.result_state.gas_counter.pay_per(ripemd160_block, message_blocks as u64)?; use ripemd::Digest; let value_hash = ripemd::Ripemd160::digest(&value); self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, value_hash.as_slice(), @@ -1054,7 +1631,7 @@ impl<'a> VMLogic<'a> { malleability_flag: u64, register_id: u64, ) -> Result { - self.gas_counter.pay_base(ecrecover_base)?; + self.result_state.gas_counter.pay_base(ecrecover_base)?; let signature = { let vec = get_memory_or_register!(self, sig_ptr, sig_len)?; @@ -1111,7 +1688,7 @@ impl<'a> VMLogic<'a> { if let Ok(pk) = signature.recover(hash) { self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, pk.as_ref(), @@ -1157,7 +1734,7 @@ impl<'a> VMLogic<'a> { ) -> Result { use ed25519_dalek::Verifier; - self.gas_counter.pay_base(ed25519_verify_base)?; + self.result_state.gas_counter.pay_base(ed25519_verify_base)?; let signature: ed25519_dalek::Signature = { let vec = get_memory_or_register!(self, signature_ptr, signature_len)?; @@ -1176,7 +1753,7 @@ impl<'a> VMLogic<'a> { }; let message = get_memory_or_register!(self, message_ptr, message_len)?; - self.gas_counter.pay_per(ed25519_verify_byte, message.len() as u64)?; + self.result_state.gas_counter.pay_per(ed25519_verify_byte, message.len() as u64)?; let public_key: ed25519_dalek::VerifyingKey = { let vec = get_memory_or_register!(self, public_key_ptr, public_key_len)?; @@ -1206,7 +1783,7 @@ impl<'a> VMLogic<'a> { /// * If we exceed usage limit imposed on burnt gas returns `GasLimitExceeded`; /// * If we exceed the `prepaid_gas` then returns `GasExceeded`. pub fn gas(&mut self, gas: Gas) -> Result<()> { - self.gas_counter.burn_gas(Gas::from(gas)) + self.result_state.gas_counter.burn_gas(Gas::from(gas)) } pub fn gas_opcodes(&mut self, opcodes: u32) -> Result<()> { @@ -1248,8 +1825,8 @@ impl<'a> VMLogic<'a> { /// # Cost /// /// This is a convenience function that encapsulates several costs: - /// `burnt_gas := dispatch cost of the receipt + base dispatch cost cost of the data receipt` - /// `used_gas := burnt_gas + exec cost of the receipt + base exec cost cost of the data receipt` + /// `burnt_gas := dispatch cost of the receipt + base dispatch cost of the data receipt` + /// `used_gas := burnt_gas + exec cost of the receipt + base exec cost of the data receipt` /// Notice that we prepay all base cost upon the creation of the data dependency, we are going to /// pay for the content transmitted through the dependency upon the actual creation of the /// DataReceipt. @@ -1268,16 +1845,11 @@ impl<'a> VMLogic<'a> { use_gas = use_gas.checked_add(burn_gas).ok_or(HostError::IntegerOverflow)?; // This should go to `new_data_receipt_base` and `new_action_receipt` in parts. // But we have to keep charing these two together unless we make a protocol change. - self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::new_action_receipt) - } - - /// A helper function to subtract balance on transfer or attached deposit for promises. - /// # Args: - /// * `amount`: the amount to deduct from the current account balance. - fn deduct_balance(&mut self, amount: Balance) -> Result<()> { - self.current_account_balance = - self.current_account_balance.checked_sub(amount).ok_or(HostError::BalanceExceeded)?; - Ok(()) + self.result_state.gas_counter.pay_action_accumulated( + burn_gas, + use_gas, + ActionCosts::new_action_receipt, + ) } /// Creates a promise that will execute a method on account with given arguments and attaches @@ -1398,22 +1970,23 @@ impl<'a> VMLogic<'a> { promise_idx_ptr: u64, promise_idx_count: u64, ) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err( HostError::ProhibitedInView { method_name: "promise_and".to_string() }.into() ); } - self.gas_counter.pay_base(promise_and_base)?; + self.result_state.gas_counter.pay_base(promise_and_base)?; let memory_len = promise_idx_count .checked_mul(size_of::() as u64) .ok_or(HostError::IntegerOverflow)?; - self.gas_counter.pay_per(promise_and_per_promise, memory_len)?; + self.result_state.gas_counter.pay_per(promise_and_per_promise, memory_len)?; // Read indices as little endian u64. - let promise_indices = self - .memory - .view(&mut self.gas_counter, MemSlice { ptr: promise_idx_ptr, len: memory_len })?; + let promise_indices = self.memory.view( + &mut self.result_state.gas_counter, + MemSlice { ptr: promise_idx_ptr, len: memory_len }, + )?; let promise_indices = stdx::as_chunks_exact::<{ size_of::() }, u8>(&promise_indices) .unwrap() .into_iter() @@ -1471,7 +2044,7 @@ impl<'a> VMLogic<'a> { account_id_len: u64, account_id_ptr: u64, ) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_create".to_string(), @@ -1513,7 +2086,7 @@ impl<'a> VMLogic<'a> { account_id_len: u64, account_id_ptr: u64, ) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_then".to_string(), @@ -1579,7 +2152,7 @@ impl<'a> VMLogic<'a> { /// `burnt_gas := base + dispatch action fee` /// `used_gas := burnt_gas + exec action fee` pub fn promise_batch_action_create_account(&mut self, promise_idx: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_create_account".to_string(), @@ -1617,7 +2190,7 @@ impl<'a> VMLogic<'a> { code_len: u64, code_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_deploy_contract".to_string(), @@ -1728,14 +2301,14 @@ impl<'a> VMLogic<'a> { gas: Gas, gas_weight: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_function_call".to_string(), } .into()); } - let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?; + let amount = self.memory.get_u128(&mut self.result_state.gas_counter, amount_ptr)?; let method_name = get_memory_or_register!(self, method_name_ptr, method_name_len)?; if method_name.is_empty() { return Err(HostError::EmptyMethodName.into()); @@ -1751,10 +2324,8 @@ impl<'a> VMLogic<'a> { self.pay_action_base(ActionCosts::function_call_base, sir)?; self.pay_action_per_byte(ActionCosts::function_call_byte, num_bytes, sir)?; // Prepaid gas - self.gas_counter.prepay_gas(gas)?; - - self.deduct_balance(amount)?; - + self.result_state.gas_counter.prepay_gas(gas)?; + self.result_state.deduct_balance(amount)?; self.ext.append_action_function_call_weight( receipt_idx, method_name, @@ -1786,36 +2357,38 @@ impl<'a> VMLogic<'a> { promise_idx: u64, amount_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_transfer".to_string(), } .into()); } - let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?; + let amount = self.memory.get_u128(&mut self.result_state.gas_counter, amount_ptr)?; let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; let receiver_id = self.ext.get_receipt_receiver(receipt_idx); let send_fee = transfer_send_fee( - self.fees_config, + &self.fees_config, sir, self.config.implicit_account_creation, self.config.eth_implicit_accounts, receiver_id.get_account_type(), ); let exec_fee = transfer_exec_fee( - self.fees_config, + &self.fees_config, self.config.implicit_account_creation, self.config.eth_implicit_accounts, receiver_id.get_account_type(), ); let burn_gas = send_fee; let use_gas = burn_gas.checked_add(exec_fee).ok_or(HostError::IntegerOverflow)?; - self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::transfer)?; - - self.deduct_balance(amount)?; - + self.result_state.gas_counter.pay_action_accumulated( + burn_gas, + use_gas, + ActionCosts::transfer, + )?; + self.result_state.deduct_balance(amount)?; self.ext.append_action_transfer(receipt_idx, amount)?; Ok(()) } @@ -1844,14 +2417,14 @@ impl<'a> VMLogic<'a> { public_key_len: u64, public_key_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_stake".to_string(), } .into()); } - let amount = self.memory.get_u128(&mut self.gas_counter, amount_ptr)?; + let amount = self.memory.get_u128(&mut self.result_state.gas_counter, amount_ptr)?; let public_key = self.get_public_key(public_key_ptr, public_key_len)?; let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; self.pay_action_base(ActionCosts::stake, sir)?; @@ -1883,7 +2456,7 @@ impl<'a> VMLogic<'a> { public_key_ptr: u64, nonce: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_add_key_with_full_access".to_string(), @@ -1928,7 +2501,7 @@ impl<'a> VMLogic<'a> { method_names_len: u64, method_names_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_add_key_with_function_call".to_string(), @@ -1936,7 +2509,7 @@ impl<'a> VMLogic<'a> { .into()); } let public_key = self.get_public_key(public_key_ptr, public_key_len)?; - let allowance = self.memory.get_u128(&mut self.gas_counter, allowance_ptr)?; + let allowance = self.memory.get_u128(&mut self.result_state.gas_counter, allowance_ptr)?; let allowance = if allowance > 0 { Some(allowance) } else { None }; let receiver_id = self.read_and_parse_account_id(receiver_id_ptr, receiver_id_len)?; let raw_method_names = get_memory_or_register!(self, method_names_ptr, method_names_len)?; @@ -1983,7 +2556,7 @@ impl<'a> VMLogic<'a> { public_key_len: u64, public_key_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_delete_key".to_string(), @@ -2019,7 +2592,7 @@ impl<'a> VMLogic<'a> { beneficiary_id_len: u64, beneficiary_id_ptr: u64, ) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_batch_action_delete_account".to_string(), @@ -2085,14 +2658,14 @@ impl<'a> VMLogic<'a> { gas_weight: u64, register_id: u64, ) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_yield_create".to_string(), } .into()); } - self.gas_counter.pay_base(yield_create_base)?; + self.result_state.gas_counter.pay_base(yield_create_base)?; let method_name = get_memory_or_register!(self, method_name_ptr, method_name_len)?; if method_name.is_empty() { @@ -2104,9 +2677,9 @@ impl<'a> VMLogic<'a> { // Input can't be large enough to overflow, WebAssembly address space is 32-bits. let num_bytes = method_name.len() as u64 + arguments.len() as u64; - self.gas_counter.pay_per(yield_create_byte, num_bytes)?; + self.result_state.gas_counter.pay_per(yield_create_byte, num_bytes)?; // Prepay gas for the callback so that it cannot be used for this execution any longer. - self.gas_counter.prepay_gas(gas)?; + self.result_state.gas_counter.prepay_gas(gas)?; // Here we are creating a receipt with a single data dependency which will then be // resolved by the resume call. @@ -2127,7 +2700,7 @@ impl<'a> VMLogic<'a> { )?; self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, *data_id.as_bytes(), @@ -2168,15 +2741,15 @@ impl<'a> VMLogic<'a> { payload_len: u64, payload_ptr: u64, ) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_submit_data".to_string(), } .into()); } - self.gas_counter.pay_base(yield_resume_base)?; - self.gas_counter.pay_per(yield_resume_byte, payload_len)?; + self.result_state.gas_counter.pay_base(yield_resume_base)?; + self.result_state.gas_counter.pay_per(yield_resume_byte, payload_len)?; let data_id = get_memory_or_register!(self, data_id_ptr, data_id_len)?; let payload = get_memory_or_register!(self, payload_ptr, payload_len)?; let payload_len = payload.len() as u64; @@ -2210,14 +2783,14 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn promise_results_count(&mut self) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err(HostError::ProhibitedInView { method_name: "promise_results_count".to_string(), } .into()); } - Ok(self.promise_results.len() as _) + Ok(self.context.promise_results.len() as _) } /// If the current function is invoked by a callback we can access the execution results of the @@ -2243,13 +2816,14 @@ impl<'a> VMLogic<'a> { /// /// `base + cost of writing data into a register` pub fn promise_result(&mut self, result_idx: u64, register_id: u64) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err( HostError::ProhibitedInView { method_name: "promise_result".to_string() }.into() ); } match self + .context .promise_results .get(result_idx as usize) .ok_or(HostError::InvalidPromiseResultIndex { result_idx })? @@ -2257,7 +2831,7 @@ impl<'a> VMLogic<'a> { PromiseResult::NotReady => Ok(0), PromiseResult::Successful(data) => { self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, data.as_slice(), @@ -2280,8 +2854,8 @@ impl<'a> VMLogic<'a> { /// /// `base + promise_return` pub fn promise_return(&mut self, promise_idx: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - self.gas_counter.pay_base(promise_return)?; + self.result_state.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(promise_return)?; if self.context.is_view() { return Err( HostError::ProhibitedInView { method_name: "promise_return".to_string() }.into() @@ -2293,7 +2867,7 @@ impl<'a> VMLogic<'a> { .ok_or(HostError::InvalidPromiseIndex { promise_idx })? { Promise::Receipt(receipt_idx) => { - self.return_data = ReturnData::ReceiptIndex(*receipt_idx); + self.result_state.return_data = ReturnData::ReceiptIndex(*receipt_idx); Ok(()) } Promise::NotReceipt(_) => Err(HostError::CannotReturnJointPromise.into()), @@ -2316,7 +2890,7 @@ impl<'a> VMLogic<'a> { /// # Cost /// `base + cost of reading return value from memory or register + dispatch&exec cost per byte of the data sent * num data receivers` pub fn value_return(&mut self, value_len: u64, value_ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; let return_val = get_memory_or_register!(self, value_ptr, value_len)?; let mut burn_gas: Gas = 0; let num_bytes = return_val.len() as u64; @@ -2350,12 +2924,12 @@ impl<'a> VMLogic<'a> { ) .ok_or(HostError::IntegerOverflow)?; } - self.gas_counter.pay_action_accumulated( + self.result_state.gas_counter.pay_action_accumulated( burn_gas, burn_gas, ActionCosts::new_data_receipt_byte, )?; - self.return_data = ReturnData::Value(return_val.into_owned()); + self.result_state.return_data = ReturnData::Value(return_val.into_owned()); Ok(()) } @@ -2365,7 +2939,7 @@ impl<'a> VMLogic<'a> { /// /// `base` pub fn panic(&mut self) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; Err(HostError::GuestPanic { panic_msg: "explicit guest panic".to_string() }.into()) } @@ -2381,7 +2955,7 @@ impl<'a> VMLogic<'a> { /// # Cost /// `base + cost of reading and decoding a utf8 string` pub fn panic_utf8(&mut self, len: u64, ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; Err(HostError::GuestPanic { panic_msg: self.get_utf8_string(len, ptr)? }.into()) } @@ -2401,12 +2975,12 @@ impl<'a> VMLogic<'a> { /// /// `base + log_base + log_byte + num_bytes + utf8 decoding cost` pub fn log_utf8(&mut self, len: u64, ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - self.check_can_add_a_log_message()?; + self.result_state.gas_counter.pay_base(base)?; + self.result_state.check_can_add_a_log_message()?; let message = self.get_utf8_string(len, ptr)?; - self.gas_counter.pay_base(log_base)?; - self.gas_counter.pay_per(log_byte, message.len() as u64)?; - self.checked_push_log(message) + self.result_state.gas_counter.pay_base(log_base)?; + self.result_state.gas_counter.pay_per(log_byte, message.len() as u64)?; + self.result_state.checked_push_log(message) } /// Logs the UTF-16 encoded string. If `len == u64::MAX` then treats the string as @@ -2425,13 +2999,13 @@ impl<'a> VMLogic<'a> { /// /// `base + log_base + log_byte * num_bytes + utf16 decoding cost` pub fn log_utf16(&mut self, len: u64, ptr: u64) -> Result<()> { - self.gas_counter.pay_base(base)?; - self.check_can_add_a_log_message()?; + self.result_state.gas_counter.pay_base(base)?; + self.result_state.check_can_add_a_log_message()?; let message = self.get_utf16_string(len, ptr)?; - self.gas_counter.pay_base(log_base)?; + self.result_state.gas_counter.pay_base(log_base)?; // Let's not use `encode_utf16` for gas per byte here, since it's a lot of compute. - self.gas_counter.pay_per(log_byte, message.len() as u64)?; - self.checked_push_log(message) + self.result_state.gas_counter.pay_per(log_byte, message.len() as u64)?; + self.result_state.checked_push_log(message) } /// Special import kept for compatibility with AssemblyScript contracts. Not called by smart @@ -2450,23 +3024,25 @@ impl<'a> VMLogic<'a> { /// /// `base + log_base + log_byte * num_bytes + utf16 decoding cost` pub fn abort(&mut self, msg_ptr: u32, filename_ptr: u32, line: u32, col: u32) -> Result<()> { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if msg_ptr < 4 || filename_ptr < 4 { return Err(HostError::BadUTF16.into()); } - self.check_can_add_a_log_message()?; + self.result_state.check_can_add_a_log_message()?; // Underflow checked above. - let msg_len = self.memory.get_u32(&mut self.gas_counter, (msg_ptr - 4) as u64)?; - let filename_len = self.memory.get_u32(&mut self.gas_counter, (filename_ptr - 4) as u64)?; + let msg_len = + self.memory.get_u32(&mut self.result_state.gas_counter, (msg_ptr - 4) as u64)?; + let filename_len = + self.memory.get_u32(&mut self.result_state.gas_counter, (filename_ptr - 4) as u64)?; let msg = self.get_utf16_string(msg_len as u64, msg_ptr as u64)?; let filename = self.get_utf16_string(filename_len as u64, filename_ptr as u64)?; let message = format!("{}, filename: \"{}\" line: {} col: {}", msg, filename, line, col); - self.gas_counter.pay_base(log_base)?; - self.gas_counter.pay_per(log_byte, message.as_bytes().len() as u64)?; - self.checked_push_log(format!("ABORT: {}", message))?; + self.result_state.gas_counter.pay_base(log_base)?; + self.result_state.gas_counter.pay_per(log_byte, message.as_bytes().len() as u64)?; + self.result_state.checked_push_log(format!("ABORT: {}", message))?; Err(HostError::GuestPanic { panic_msg: message }.into()) } @@ -2489,8 +3065,8 @@ impl<'a> VMLogic<'a> { /// `utf8_decoding_base + utf8_decoding_byte * num_bytes`. fn read_and_parse_account_id(&mut self, ptr: u64, len: u64) -> Result { let buf = get_memory_or_register!(self, ptr, len)?; - self.gas_counter.pay_base(utf8_decoding_base)?; - self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as u64)?; + self.result_state.gas_counter.pay_base(utf8_decoding_base)?; + self.result_state.gas_counter.pay_per(utf8_decoding_byte, buf.len() as u64)?; // We return an illegally constructed AccountId here for the sake of ensuring // backwards compatibility. For paths previously involving validation, like receipts @@ -2534,13 +3110,13 @@ impl<'a> VMLogic<'a> { value_ptr: u64, register_id: u64, ) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err( HostError::ProhibitedInView { method_name: "storage_write".to_string() }.into() ); } - self.gas_counter.pay_base(storage_write_base)?; + self.result_state.gas_counter.pay_base(storage_write_base)?; let key = get_memory_or_register!(self, key_ptr, key_len)?; if key.len() as u64 > self.config.limit_config.max_length_storage_key { return Err(HostError::KeyLengthExceeded { @@ -2557,14 +3133,17 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per(storage_write_key_byte, key.len() as u64)?; - self.gas_counter.pay_per(storage_write_value_byte, value.len() as u64)?; + self.result_state.gas_counter.pay_per(storage_write_key_byte, key.len() as u64)?; + self.result_state.gas_counter.pay_per(storage_write_value_byte, value.len() as u64)?; let nodes_before = self.ext.get_trie_nodes_count(); // For storage write, we need to first perform a read on the key to calculate the TTN cost. // This storage_get must be performed through trie instead of through FlatStorage let evicted_ptr = self.ext.storage_get(&key, StorageGetMode::Trie)?; - let evicted = - Self::deref_value(&mut self.gas_counter, storage_write_evicted_byte, evicted_ptr)?; + let evicted = Self::deref_value( + &mut self.result_state.gas_counter, + storage_write_evicted_byte, + evicted_ptr, + )?; let nodes_delta = self .ext .get_trie_nodes_count() @@ -2582,24 +3161,26 @@ impl<'a> VMLogic<'a> { tn_db_reads = nodes_delta.db_reads, ); - self.gas_counter.add_trie_fees(&nodes_delta)?; + self.result_state.gas_counter.add_trie_fees(&nodes_delta)?; self.ext.storage_set(&key, &value)?; let storage_config = &self.fees_config.storage_usage_config; self.recorded_storage_counter.observe_size(self.ext.get_recorded_storage_size())?; match evicted { Some(old_value) => { // Inner value can't overflow, because the value length is limited. - self.current_storage_usage = self + self.result_state.current_storage_usage = self + .result_state .current_storage_usage .checked_sub(old_value.len() as u64) .ok_or(InconsistentStateError::IntegerOverflow)?; // Inner value can't overflow, because the value length is limited. - self.current_storage_usage = self + self.result_state.current_storage_usage = self + .result_state .current_storage_usage .checked_add(value.len() as u64) .ok_or(InconsistentStateError::IntegerOverflow)?; self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, old_value, @@ -2608,7 +3189,8 @@ impl<'a> VMLogic<'a> { } None => { // Inner value can't overflow, because the key/value length is limited. - self.current_storage_usage = self + self.result_state.current_storage_usage = self + .result_state .current_storage_usage .checked_add( value.len() as u64 @@ -2653,8 +3235,8 @@ impl<'a> VMLogic<'a> { /// `base + storage_read_base + storage_read_key_byte * num_key_bytes + storage_read_value_byte + num_value_bytes /// cost to read key from register + cost to write value into register`. pub fn storage_read(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result { - self.gas_counter.pay_base(base)?; - self.gas_counter.pay_base(storage_read_base)?; + self.result_state.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(storage_read_base)?; let key = get_memory_or_register!(self, key_ptr, key_len)?; if key.len() as u64 > self.config.limit_config.max_length_storage_key { return Err(HostError::KeyLengthExceeded { @@ -2663,7 +3245,7 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per(storage_read_key_byte, key.len() as u64)?; + self.result_state.gas_counter.pay_per(storage_read_key_byte, key.len() as u64)?; let nodes_before = self.ext.get_trie_nodes_count(); let read = self.ext.storage_get(&key, self.config.storage_get_mode); let nodes_delta = self @@ -2671,8 +3253,9 @@ impl<'a> VMLogic<'a> { .get_trie_nodes_count() .checked_sub(&nodes_before) .ok_or(InconsistentStateError::IntegerOverflow)?; - self.gas_counter.add_trie_fees(&nodes_delta)?; - let read = Self::deref_value(&mut self.gas_counter, storage_read_value_byte, read?)?; + self.result_state.gas_counter.add_trie_fees(&nodes_delta)?; + let read = + Self::deref_value(&mut self.result_state.gas_counter, storage_read_value_byte, read?)?; #[cfg(feature = "io_trace")] tracing::trace!( @@ -2688,7 +3271,7 @@ impl<'a> VMLogic<'a> { match read { Some(value) => { self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, value, @@ -2719,13 +3302,13 @@ impl<'a> VMLogic<'a> { /// `base + storage_remove_base + storage_remove_key_byte * num_key_bytes + storage_remove_ret_value_byte * num_value_bytes /// + cost to read the key + cost to write the value`. pub fn storage_remove(&mut self, key_len: u64, key_ptr: u64, register_id: u64) -> Result { - self.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(base)?; if self.context.is_view() { return Err( HostError::ProhibitedInView { method_name: "storage_remove".to_string() }.into() ); } - self.gas_counter.pay_base(storage_remove_base)?; + self.result_state.gas_counter.pay_base(storage_remove_base)?; let key = get_memory_or_register!(self, key_ptr, key_len)?; if key.len() as u64 > self.config.limit_config.max_length_storage_key { return Err(HostError::KeyLengthExceeded { @@ -2734,13 +3317,16 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per(storage_remove_key_byte, key.len() as u64)?; + self.result_state.gas_counter.pay_per(storage_remove_key_byte, key.len() as u64)?; let nodes_before = self.ext.get_trie_nodes_count(); // To delete a key, we need to first perform a read on the key to calculate the TTN cost. // This storage_get must be performed through trie instead of through FlatStorage let removed_ptr = self.ext.storage_get(&key, StorageGetMode::Trie)?; - let removed = - Self::deref_value(&mut self.gas_counter, storage_remove_ret_value_byte, removed_ptr)?; + let removed = Self::deref_value( + &mut self.result_state.gas_counter, + storage_remove_ret_value_byte, + removed_ptr, + )?; self.ext.storage_remove(&key)?; let nodes_delta = self @@ -2759,13 +3345,14 @@ impl<'a> VMLogic<'a> { tn_db_reads = nodes_delta.db_reads, ); - self.gas_counter.add_trie_fees(&nodes_delta)?; + self.result_state.gas_counter.add_trie_fees(&nodes_delta)?; let storage_config = &self.fees_config.storage_usage_config; self.recorded_storage_counter.observe_size(self.ext.get_recorded_storage_size())?; match removed { Some(value) => { // Inner value can't overflow, because the key/value length is limited. - self.current_storage_usage = self + self.result_state.current_storage_usage = self + .result_state .current_storage_usage .checked_sub( value.len() as u64 @@ -2774,7 +3361,7 @@ impl<'a> VMLogic<'a> { ) .ok_or(InconsistentStateError::IntegerOverflow)?; self.registers.set( - &mut self.gas_counter, + &mut self.result_state.gas_counter, &self.config.limit_config, register_id, value, @@ -2798,8 +3385,8 @@ impl<'a> VMLogic<'a> { /// /// `base + storage_has_key_base + storage_has_key_byte * num_bytes + cost of reading key` pub fn storage_has_key(&mut self, key_len: u64, key_ptr: u64) -> Result { - self.gas_counter.pay_base(base)?; - self.gas_counter.pay_base(storage_has_key_base)?; + self.result_state.gas_counter.pay_base(base)?; + self.result_state.gas_counter.pay_base(storage_has_key_base)?; let key = get_memory_or_register!(self, key_ptr, key_len)?; if key.len() as u64 > self.config.limit_config.max_length_storage_key { return Err(HostError::KeyLengthExceeded { @@ -2808,7 +3395,7 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per(storage_has_key_byte, key.len() as u64)?; + self.result_state.gas_counter.pay_per(storage_has_key_byte, key.len() as u64)?; let nodes_before = self.ext.get_trie_nodes_count(); let res = self.ext.storage_has_key(&key, self.config.storage_get_mode); let nodes_delta = self @@ -2826,7 +3413,7 @@ impl<'a> VMLogic<'a> { tn_db_reads = nodes_delta.db_reads, ); - self.gas_counter.add_trie_fees(&nodes_delta)?; + self.result_state.gas_counter.add_trie_fees(&nodes_delta)?; self.recorded_storage_counter.observe_size(self.ext.get_recorded_storage_size())?; Ok(res? as u64) } @@ -2914,7 +3501,7 @@ impl<'a> VMLogic<'a> { /// * If `iterator_id` does not correspond to an existing iterator returns `InvalidIteratorId`; /// * If between the creation of the iterator and calling `storage_iter_next` the range over /// which it iterates was modified returns `IteratorWasInvalidated`. Specifically, if - /// `storage_write` or `storage_remove` was invoked on the key key such that: + /// `storage_write` or `storage_remove` was invoked on the key such that: /// * in case of `storage_iter_prefix`. `key` has the given prefix and: /// * Iterator was not called next yet. /// * `next` was already called on the iterator and it is currently pointing at the `key` @@ -2939,54 +3526,16 @@ impl<'a> VMLogic<'a> { })) } - /// Computes the outcome of the execution. - /// - /// If `FunctionCallWeight` protocol feature (127) is enabled, unused gas will be - /// distributed to functions that specify a gas weight. If there are no functions with - /// a gas weight, the outcome will contain unused gas as usual. - pub fn compute_outcome(self) -> VMOutcome { - let burnt_gas = self.gas_counter.burnt_gas(); - let used_gas = self.gas_counter.used_gas(); - - let mut profile = self.gas_counter.profile_data(); - profile.compute_wasm_instruction_cost(burnt_gas); - let compute_usage = profile.total_compute_usage(&self.config.ext_costs); - - VMOutcome { - balance: self.current_account_balance, - storage_usage: self.current_storage_usage, - return_data: self.return_data, - burnt_gas, - used_gas, - compute_usage, - logs: self.logs, - profile, - aborted: None, - } - } - - /// Add a cost for loading the contract code in the VM. - /// - /// This cost does not consider the structure of the contract code, only the - /// size. This is currently the only loading fee. A fee that takes the code - /// structure into consideration could be added. But since that would have - /// to happen after loading, we cannot pre-charge it. This is the main - /// motivation to (only) have this simple fee. - pub fn add_contract_loading_fee(&mut self, code_len: u64) -> Result<()> { - self.gas_counter.pay_per(contract_loading_bytes, code_len)?; - self.gas_counter.pay_base(contract_loading_base) - } - /// Gets pointer to the fast gas counter. pub fn gas_counter_pointer(&mut self) -> *mut FastGasCounter { - self.gas_counter.gas_counter_raw_ptr() + self.result_state.gas_counter.gas_counter_raw_ptr() } /// Properly handles gas limit exceeded error. pub fn process_gas_limit(&mut self) -> HostError { - let new_burn_gas = self.gas_counter.burnt_gas(); - let new_used_gas = self.gas_counter.used_gas(); - self.gas_counter.process_gas_limit(new_burn_gas, new_used_gas) + let new_burn_gas = self.result_state.gas_counter.burnt_gas(); + let new_used_gas = self.result_state.gas_counter.used_gas(); + self.result_state.gas_counter.process_gas_limit(new_burn_gas, new_used_gas) } /// A helper function to pay base cost gas fee for batching an action. @@ -2995,7 +3544,7 @@ impl<'a> VMLogic<'a> { let burn_gas = base_fee.send_fee(sir); let use_gas = burn_gas.checked_add(base_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?; - self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action) + self.result_state.gas_counter.pay_action_accumulated(burn_gas, use_gas, action) } /// A helper function to pay per byte gas fee for batching an action. @@ -3013,48 +3562,7 @@ impl<'a> VMLogic<'a> { num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?, ) .ok_or(HostError::IntegerOverflow)?; - self.gas_counter.pay_action_accumulated(burn_gas, use_gas, action) - } - - /// VM independent setup before loading the executable. - /// - /// Does VM independent checks that happen after the instantiation of - /// VMLogic but before loading the executable. This includes pre-charging gas - /// costs for loading the executable, which depends on the size of the WASM code. - pub fn before_loading_executable( - &mut self, - method_name: &str, - wasm_code_bytes: u64, - ) -> std::result::Result<(), super::errors::FunctionCallError> { - if method_name.is_empty() { - let error = super::errors::FunctionCallError::MethodResolveError( - super::errors::MethodResolveError::MethodEmptyName, - ); - return Err(error); - } - if self.config.fix_contract_loading_cost { - if self.add_contract_loading_fee(wasm_code_bytes).is_err() { - let error = - super::errors::FunctionCallError::HostError(super::HostError::GasExceeded); - return Err(error); - } - } - Ok(()) - } - - /// Legacy code to preserve old gas charging behaviour in old protocol versions. - pub fn after_loading_executable( - &mut self, - wasm_code_bytes: u64, - ) -> std::result::Result<(), super::errors::FunctionCallError> { - if !self.config.fix_contract_loading_cost { - if self.add_contract_loading_fee(wasm_code_bytes).is_err() { - return Err(super::errors::FunctionCallError::HostError( - super::HostError::GasExceeded, - )); - } - } - Ok(()) + self.result_state.gas_counter.pay_action_accumulated(burn_gas, use_gas, action) } } @@ -3075,16 +3583,16 @@ pub struct VMOutcome { impl VMOutcome { /// Consumes the `VMLogic` object and computes the final outcome with the /// given error that stopped execution from finishing successfully. - pub fn abort(logic: VMLogic, error: FunctionCallError) -> VMOutcome { - let mut outcome = logic.compute_outcome(); + pub fn abort(state: ExecutionResultState, error: FunctionCallError) -> VMOutcome { + let mut outcome = state.compute_outcome(); outcome.aborted = Some(error); outcome } /// Consumes the `VMLogic` object and computes the final outcome for a /// successful execution. - pub fn ok(logic: VMLogic) -> VMOutcome { - logic.compute_outcome() + pub fn ok(state: ExecutionResultState) -> VMOutcome { + state.compute_outcome() } /// Creates an outcome with a no-op outcome. @@ -3108,11 +3616,11 @@ impl VMOutcome { /// Like `Self::abort()` but without feature `FixContractLoadingCost` it /// will return a NOP outcome. This is used for backwards-compatibility only. pub fn abort_but_nop_outcome_in_old_protocol( - logic: VMLogic, + state: ExecutionResultState, error: FunctionCallError, ) -> VMOutcome { - if logic.config.fix_contract_loading_cost { - Self::abort(logic, error) + if state.config.fix_contract_loading_cost { + Self::abort(state, error) } else { Self::nop_outcome(error) } diff --git a/runtime/near-vm-runner/src/logic/mocks/mock_external.rs b/runtime/near-vm-runner/src/logic/mocks/mock_external.rs index 70d56b026e6..97df18bf249 100644 --- a/runtime/near-vm-runner/src/logic/mocks/mock_external.rs +++ b/runtime/near-vm-runner/src/logic/mocks/mock_external.rs @@ -1,8 +1,11 @@ +use crate::logic::dependencies::{Result, TrieNodesCount}; use crate::logic::types::ReceiptIndex; use crate::logic::{External, StorageGetMode, ValuePtr}; +use crate::ContractCode; use near_primitives_core::hash::{hash, CryptoHash}; use near_primitives_core::types::{AccountId, Balance, Gas, GasWeight}; use std::collections::HashMap; +use std::sync::Arc; #[derive(serde::Serialize)] #[serde(remote = "GasWeight")] @@ -77,6 +80,8 @@ pub struct MockedExternal { pub fake_trie: HashMap, Vec>, pub validators: HashMap, pub action_log: Vec, + pub code: Option>, + pub code_hash: CryptoHash, data_count: u64, } @@ -107,9 +112,15 @@ impl MockedExternal { pub fn new() -> Self { Self::default() } -} -use crate::logic::dependencies::{Result, TrieNodesCount}; + pub fn with_code(code: ContractCode) -> Self { + Self::with_code_and_hash(*code.hash(), code) + } + + pub fn with_code_and_hash(code_hash: CryptoHash, code: ContractCode) -> Self { + Self { code_hash, code: Some(code.into()), ..Self::default() } + } +} impl External for MockedExternal { fn storage_set(&mut self, key: &[u8], value: &[u8]) -> Result<()> { @@ -309,4 +320,12 @@ impl External for MockedExternal { _ => panic!("not a valid receipt index!"), } } + + fn code_hash(&self) -> CryptoHash { + self.code_hash + } + + fn get_contract(&self) -> Option> { + self.code.clone() + } } diff --git a/runtime/near-vm-runner/src/logic/mocks/mock_memory.rs b/runtime/near-vm-runner/src/logic/mocks/mock_memory.rs index 94d4b135470..51340e450ff 100644 --- a/runtime/near-vm-runner/src/logic/mocks/mock_memory.rs +++ b/runtime/near-vm-runner/src/logic/mocks/mock_memory.rs @@ -2,6 +2,7 @@ use crate::logic::{MemSlice, MemoryLike}; use std::borrow::Cow; +#[derive(Clone)] pub struct MockedMemory(Box<[u8]>); impl MockedMemory { diff --git a/runtime/near-vm-runner/src/logic/mod.rs b/runtime/near-vm-runner/src/logic/mod.rs index 0501cea0e7f..000460a71b7 100644 --- a/runtime/near-vm-runner/src/logic/mod.rs +++ b/runtime/near-vm-runner/src/logic/mod.rs @@ -1,4 +1,6 @@ mod alt_bn128; +#[cfg(feature = "protocol_feature_bls12381")] +mod bls12381; mod context; mod dependencies; pub mod errors; @@ -17,7 +19,7 @@ pub use context::VMContext; pub use dependencies::{External, MemSlice, MemoryLike, TrieNodesCount, ValuePtr}; pub use errors::{HostError, VMLogicError}; pub use gas_counter::with_ext_cost_counter; -pub use logic::{VMLogic, VMOutcome}; +pub use logic::{ExecutionResultState, VMLogic, VMOutcome}; pub use near_parameters::vm::{Config, ContractPrepareVersion, LimitConfig, StorageGetMode}; pub use near_primitives_core::types::ProtocolVersion; pub use types::ReturnData; diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381.rs b/runtime/near-vm-runner/src/logic/tests/bls12381.rs new file mode 100644 index 00000000000..c48abdf60ce --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381.rs @@ -0,0 +1,1521 @@ +mod tests { + use crate::logic::tests::vm_logic_builder::{TestVMLogic, VMLogicBuilder}; + use crate::logic::MemSlice; + use ark_bls12_381::{Bls12_381, Fq, Fq2, Fr, G1Affine, G2Affine}; + use ark_ec::hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve}; + use ark_ec::{bls12::Bls12Config, pairing::Pairing, AffineRepr, CurveGroup}; + use ark_ff::{Field, PrimeField}; + use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, CanonicalSerializeWithFlags, EmptyFlags, + }; + use ark_std::{One, Zero}; + use bolero::{generator, TypeGenerator}; + use rand::{seq::SliceRandom, thread_rng}; + use std::{fs, ops::Add, ops::Mul, ops::Neg, str::FromStr}; + + const P: &str = "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787"; + const P_HEX: &str = "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"; + const P_MINUS_1: &str = "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559786"; + const R: &str = "52435875175126190479447740508185965837690552500527637822603658699938581184513"; + const R_MINUS_1: &str = "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"; + + const MAX_N_PAIRING: usize = 15; + + macro_rules! run_bls12381_fn { + ($fn_name:ident, $buffer:expr, $expected_res:expr) => {{ + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + let input = logic.internal_mem_write($buffer.concat().as_slice()); + let res = logic.$fn_name(input.len, input.ptr, 0).unwrap(); + assert_eq!(res, $expected_res); + }}; + ($fn_name:ident, $buffer:expr) => {{ + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + let input = logic.internal_mem_write($buffer.concat().as_slice()); + let res = logic.$fn_name(input.len, input.ptr, 0).unwrap(); + assert_eq!(res, 0); + logic.registers().get_for_free(0).unwrap().to_vec() + }}; + } + + struct G1Operations; + struct G2Operations; + + #[derive(Debug)] + pub struct FP { + pub p: Fq, + } + + impl TypeGenerator for FP { + fn generate(driver: &mut D) -> Option { + let fq_ser: [u8; 48] = <[u8; 48]>::generate(driver)?; + Some(FP { p: Fq::from_random_bytes(&fq_ser)? }) + } + } + + #[derive(Debug)] + pub struct FP2 { + pub p: Fq2, + } + + impl TypeGenerator for FP2 { + fn generate(driver: &mut D) -> Option { + let c0: FP = FP::generate(driver)?; + let c1: FP = FP::generate(driver)?; + + Some(FP2 { p: Fq2::new(c0.p, c1.p) }) + } + } + + #[derive(Debug)] + pub struct Scalar { + pub p: Fr, + } + + impl TypeGenerator for Scalar { + fn generate(driver: &mut D) -> Option { + let raw = <[u8; 32]>::generate(driver)?; + + Some(Scalar { p: Fr::from_random_bytes(&raw)? }) + } + } + + impl G1Operations { + const POINT_LEN: usize = 96; + const MAX_N_SUM: usize = 675; + const MAX_N_MULTIEXP: usize = 100; + const MAX_N_MAP: usize = 150; + const MAX_N_DECOMPRESS: usize = 500; + + fn serialize_fp(fq: &Fq) -> Vec { + let mut result = [0u8; 48]; + let rep = fq.into_bigint(); + for i in 0..6 { + result[i * 8..(i + 1) * 8].copy_from_slice(&rep.0[5 - i].to_be_bytes()); + } + result.to_vec() + } + } + + impl G2Operations { + const POINT_LEN: usize = 192; + const MAX_N_SUM: usize = 338; + const MAX_N_MULTIEXP: usize = 50; + const MAX_N_MAP: usize = 75; + const MAX_N_DECOMPRESS: usize = 250; + + fn serialize_fp(fq: &Fq2) -> Vec { + let c1_bytes = G1Operations::serialize_fp(&fq.c1); + let c0_bytes = G1Operations::serialize_fp(&fq.c0); + [c1_bytes, c0_bytes].concat() + } + } + + macro_rules! impl_goperations { + ( + $GOperations:ident, + $Fq:ident, + $FP:ident, + $EPoint:ident, + $GPoint:ident, + $EnotGPoint:ident, + $GConfig:ident, + $GAffine:ident, + $add_p_y:ident, + $bls12381_decompress:ident, + $bls12381_sum:ident, + $bls12381_multiexp:ident, + $bls12381_map_fp_to_g:ident + ) => { + #[derive(Debug)] + pub struct $EPoint { + pub p: $GAffine, + } + + impl TypeGenerator for $EPoint { + fn generate(driver: &mut D) -> Option<$EPoint> { + let x: $FP = $FP::generate(driver)?; + let greatest: bool = bool::generate(driver)?; + Some($EPoint { p: $GAffine::get_point_from_x_unchecked(x.p, greatest)? }) + } + } + + #[derive(Debug)] + pub struct $GPoint { + pub p: $GAffine, + } + + impl TypeGenerator for $GPoint { + fn generate(driver: &mut D) -> Option<$GPoint> { + let curve_point = $EPoint::generate(driver)?; + Some($GPoint { p: curve_point.p.clear_cofactor() }) + } + } + + #[derive(Debug)] + pub struct $EnotGPoint { + pub p: $GAffine, + } + + impl TypeGenerator for $EnotGPoint { + fn generate(driver: &mut D) -> Option<$EnotGPoint> { + let p = $EPoint::generate(driver)?; + if p.p.is_in_correct_subgroup_assuming_on_curve() { + return None; + } + Some($EnotGPoint { p: p.p }) + } + } + + impl $GOperations { + fn check_multipoint_sum(ps: &Vec<(bool, $EPoint)>) { + let mut res3 = $GAffine::identity(); + + let mut points: Vec<(u8, $GAffine)> = vec![]; + for i in 0..ps.len() { + points.push((ps[i].0 as u8, ps[i].1.p)); + + let mut current_point = points[i].1.clone(); + if points[i].0 == 1 { + current_point = current_point.neg(); + } + + res3 = res3.add(¤t_point).into(); + } + + let res1 = Self::get_sum_many_points(&points); + + points.shuffle(&mut thread_rng()); + let res2 = Self::get_sum_many_points(&points); + assert_eq!(res1, res2); + + assert_eq!(res1, Self::serialize_uncompressed_g(&res3).to_vec()); + } + + fn decompress_p(ps: Vec<$GAffine>) -> Vec { + let mut ps_vec: Vec> = vec![vec![]]; + for i in 0..ps.len() { + ps_vec.push(Self::serialize_g(&ps[i])); + } + + run_bls12381_fn!($bls12381_decompress, ps_vec) + } + + fn get_sum(p_sign: u8, p: &[u8], q_sign: u8, q: &[u8]) -> Vec { + let buffer = vec![vec![p_sign], p.to_vec(), vec![q_sign], q.to_vec()]; + run_bls12381_fn!($bls12381_sum, buffer) + } + + fn get_inverse(p: &[u8]) -> Vec { + let buffer = vec![vec![1], p.to_vec()]; + run_bls12381_fn!($bls12381_sum, buffer) + } + + fn get_sum_many_points(points: &Vec<(u8, $GAffine)>) -> Vec { + let mut buffer: Vec> = vec![]; + for i in 0..points.len() { + buffer.push(vec![points[i].0]); + buffer.push(Self::serialize_uncompressed_g(&points[i].1).to_vec()); + } + run_bls12381_fn!($bls12381_sum, buffer) + } + + fn get_multiexp(points: &Vec<(Fr, $GAffine)>) -> Vec { + let mut buffer: Vec> = vec![]; + for i in 0..points.len() { + buffer.push(Self::serialize_uncompressed_g(&points[i].1).to_vec()); + + let mut n_vec: [u8; 32] = [0u8; 32]; + points[i].0.serialize_with_flags(n_vec.as_mut_slice(), EmptyFlags).unwrap(); + buffer.push(n_vec.to_vec()); + } + + run_bls12381_fn!($bls12381_multiexp, buffer) + } + + fn get_multiexp_small(points: &Vec<(u8, $GAffine)>) -> Vec { + let mut buffer: Vec> = vec![]; + for i in 0..points.len() { + buffer.push(Self::serialize_uncompressed_g(&points[i].1).to_vec()); + let mut n_vec: [u8; 32] = [0u8; 32]; + n_vec[0] = points[i].0; + buffer.push(n_vec.to_vec()); + } + + run_bls12381_fn!($bls12381_multiexp, buffer) + } + + fn get_multiexp_many_points(points: &Vec<(u8, $GAffine)>) -> Vec { + let mut buffer: Vec> = vec![]; + for i in 0..points.len() { + buffer.push(Self::serialize_uncompressed_g(&points[i].1).to_vec()); + if points[i].0 == 0 { + buffer.push(vec![vec![1], vec![0; 31]].concat()); + } else { + buffer + .push(hex::decode(R_MINUS_1).unwrap().into_iter().rev().collect()); + } + } + + run_bls12381_fn!($bls12381_multiexp, buffer) + } + + fn map_fp_to_g(fps: Vec<$Fq>) -> Vec { + let mut fp_vec: Vec> = vec![]; + + for i in 0..fps.len() { + fp_vec.push(Self::serialize_fp(&fps[i])); + } + + run_bls12381_fn!($bls12381_map_fp_to_g, fp_vec) + } + + fn get_incorrect_points(p: &$EPoint) -> Vec> { + let mut res: Vec> = vec![]; + + // Incorrect encoding of the point at infinity + let mut zero = get_zero(Self::POINT_LEN); + zero[Self::POINT_LEN - 1] = 1; + res.push(zero); + + // Erroneous coding of field elements with an incorrect extra bit in the decompressed encoding. + let mut zero = vec![0u8; Self::POINT_LEN]; + zero[0] = 192; + res.push(zero); + + let mut p_ser = Self::serialize_uncompressed_g(&p.p); + p_ser[0] |= 0x80; + res.push(p_ser.to_vec()); + + // Point not on the curve + let mut p_ser = Self::serialize_uncompressed_g(&p.p); + p_ser[Self::POINT_LEN - 1] ^= 0x01; + res.push(p_ser.to_vec()); + + //Erroneous coding of field elements, resulting in a correct point on the curve if only the suffix is considered. + let mut p_ser = Self::serialize_uncompressed_g(&p.p); + p_ser[0] ^= 0x20; + res.push(p_ser.to_vec()); + + let p_ser = $add_p_y(&p.p).to_vec(); + res.push(p_ser); + + res + } + + fn map_to_curve_g(fp: $Fq) -> $GAffine { + let wbmap = + WBMap::<::$GConfig>::new().unwrap(); + let res = wbmap.map_to_curve(fp).unwrap(); + if res.infinity { + return $GAffine::identity(); + } + + $GAffine::new_unchecked(res.x, res.y) + } + + fn serialize_uncompressed_g(p: &$GAffine) -> Vec { + let mut serialized = vec![0u8; Self::POINT_LEN]; + p.serialize_with_mode(serialized.as_mut_slice(), ark_serialize::Compress::No) + .unwrap(); + + serialized + } + + fn serialize_g(p: &$GAffine) -> Vec { + let mut serialized = vec![0u8; Self::POINT_LEN / 2]; + p.serialize_with_mode(serialized.as_mut_slice(), ark_serialize::Compress::Yes) + .unwrap(); + + serialized + } + + fn deserialize_g(p: Vec) -> $GAffine { + $GAffine::deserialize_with_mode( + p.as_slice(), + ark_serialize::Compress::No, + ark_serialize::Validate::No, + ) + .unwrap() + } + } + }; + } + + impl_goperations!( + G1Operations, + Fq, + FP, + E1Point, + G1Point, + EnotG1Point, + G1Config, + G1Affine, + add_p_y, + bls12381_p1_decompress, + bls12381_p1_sum, + bls12381_g1_multiexp, + bls12381_map_fp_to_g1 + ); + impl_goperations!( + G2Operations, + Fq2, + FP2, + E2Point, + G2Point, + EnotG2Point, + G2Config, + G2Affine, + add2_p_y, + bls12381_p2_decompress, + bls12381_p2_sum, + bls12381_g2_multiexp, + bls12381_map_fp2_to_g2 + ); + + fn get_zero(point_len: usize) -> Vec { + let mut zero1 = vec![0; point_len]; + zero1[0] |= 0x40; + zero1 + } + + fn pairing_check(p1s: Vec, p2s: Vec) -> u64 { + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + + let mut buffer: Vec> = vec![]; + for i in 0..p1s.len() { + buffer.push(G1Operations::serialize_uncompressed_g(&p1s[i]).to_vec()); + buffer.push(G2Operations::serialize_uncompressed_g(&p2s[i]).to_vec()); + } + + let input = logic.internal_mem_write(&buffer.concat().as_slice()); + let res = logic.bls12381_pairing_check(input.len, input.ptr).unwrap(); + return res; + } + + fn pairing_check_vec(p1: Vec, p2: Vec) -> u64 { + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + + let buffer: Vec> = vec![p1, p2]; + + let input = logic.internal_mem_write(&buffer.concat().as_slice()); + let res = logic.bls12381_pairing_check(input.len, input.ptr).unwrap(); + return res; + } + + macro_rules! test_bls12381_sum { + ( + $GOp:ident, + $GPoint:ident, + $EPoint:ident, + $EnotGPoint:ident, + $GAffine:ident, + $bls12381_sum:ident, + $check_sum:ident, + $test_bls12381_sum_edge_cases:ident, + $test_bls12381_sum:ident, + $test_bls12381_sum_not_g_points:ident, + $test_bls12381_sum_inverse:ident, + $test_bls12381_sum_many_points:ident, + $test_bls12381_crosscheck_sum_and_multiexp:ident, + $test_bls12381_sum_incorrect_input:ident + ) => { + #[test] + fn $test_bls12381_sum_edge_cases() { + // 0 + 0 + let zero = get_zero($GOp::POINT_LEN); + assert_eq!(zero.to_vec(), $GOp::get_sum(0, &zero, 0, &zero)); + + // 0 + P = P + 0 = P + bolero::check!().with_type().for_each(|p: &$GPoint| { + let p_ser = $GOp::serialize_uncompressed_g(&p.p); + assert_eq!(p_ser.to_vec(), $GOp::get_sum(0, &zero, 0, &p_ser)); + assert_eq!(p_ser.to_vec(), $GOp::get_sum(0, &p_ser, 0, &zero)); + }); + + // P + P + // P + (-P) = (-P) + P = 0 + // P + (-(P + P)) + bolero::check!().with_type().for_each(|p: &$EPoint| { + let p_ser = $GOp::serialize_uncompressed_g(&p.p); + + let pmul2 = p.p.mul(Fr::from(2)); + let pmul2_ser = $GOp::serialize_uncompressed_g(&pmul2.into_affine()); + assert_eq!(pmul2_ser.to_vec(), $GOp::get_sum(0, &p_ser, 0, &p_ser)); + + let pneg = p.p.neg(); + let p_neg_ser = $GOp::serialize_uncompressed_g(&pneg); + + assert_eq!(zero.to_vec(), $GOp::get_sum(0, &p_neg_ser, 0, &p_ser)); + assert_eq!(zero.to_vec(), $GOp::get_sum(0, &p_ser, 0, &p_neg_ser)); + + let pmul2neg = pmul2.neg(); + let pmul2_neg = $GOp::serialize_uncompressed_g(&pmul2neg.into_affine()); + assert_eq!(p_neg_ser.to_vec(), $GOp::get_sum(0, &p_ser, 0, &pmul2_neg)); + }); + } + + fn $check_sum(p: $GAffine, q: $GAffine) { + let p_ser = $GOp::serialize_uncompressed_g(&p); + let q_ser = $GOp::serialize_uncompressed_g(&q); + + // P + Q = Q + P + let got1 = $GOp::get_sum(0, &p_ser, 0, &q_ser); + let got2 = $GOp::get_sum(0, &q_ser, 0, &p_ser); + assert_eq!(got1, got2); + + // compare with library results + let psum = p.add(&q); + let library_sum = $GOp::serialize_uncompressed_g(&psum.into_affine()); + + assert_eq!(library_sum.to_vec(), got1); + + let p_inv = $GOp::get_inverse(&library_sum); + let pneg = psum.neg(); + let p_neg_ser = $GOp::serialize_uncompressed_g(&pneg.into_affine()); + + assert_eq!(p_neg_ser.to_vec(), p_inv); + } + + #[test] + fn $test_bls12381_sum() { + bolero::check!().with_type().for_each(|(p, q): &($EPoint, $EPoint)| { + $check_sum(p.p, q.p); + }); + + bolero::check!().with_type().for_each(|(p, q): &($GPoint, $GPoint)| { + let p_ser = $GOp::serialize_uncompressed_g(&p.p); + let q_ser = $GOp::serialize_uncompressed_g(&q.p); + + let got1 = $GOp::get_sum(0, &p_ser, 0, &q_ser); + + let result_point = $GOp::deserialize_g(got1); + assert!(result_point.is_in_correct_subgroup_assuming_on_curve()); + }); + } + + #[test] + fn $test_bls12381_sum_not_g_points() { + //points not from G + bolero::check!().with_type().for_each(|(p, q): &($EnotGPoint, $EnotGPoint)| { + $check_sum(p.p, q.p); + }); + } + + #[test] + fn $test_bls12381_sum_inverse() { + let zero = get_zero($GOp::POINT_LEN); + + bolero::check!().with_type().for_each(|p: &$EPoint| { + let p_ser = $GOp::serialize_uncompressed_g(&p.p); + + // P - P = - P + P = 0 + let got1 = $GOp::get_sum(1, &p_ser, 0, &p_ser); + let got2 = $GOp::get_sum(0, &p_ser, 1, &p_ser); + assert_eq!(got1, got2); + assert_eq!(got1, zero.to_vec()); + + // -(-P) + let p_inv = $GOp::get_inverse(&p_ser); + let p_inv_inv = $GOp::get_inverse(p_inv.as_slice()); + + assert_eq!(p_ser.to_vec(), p_inv_inv); + }); + + // P in G => -P in G + bolero::check!().with_type().for_each(|p: &$GPoint| { + let p_ser = $GOp::serialize_uncompressed_g(&p.p); + + let p_inv = $GOp::get_inverse(&p_ser); + + let result_point = $GOp::deserialize_g(p_inv); + assert!(result_point.is_in_correct_subgroup_assuming_on_curve()); + }); + + // -0 + let zero_inv = $GOp::get_inverse(&zero); + assert_eq!(zero.to_vec(), zero_inv); + } + + #[test] + fn $test_bls12381_sum_many_points() { + let zero = get_zero($GOp::POINT_LEN); + //empty input + let res = $GOp::get_sum_many_points(&vec![]); + assert_eq!(zero.to_vec(), res); + + bolero::check!() + .with_generator( + bolero::gen::>().with().len(0usize..$GOp::MAX_N_SUM), + ) + .for_each(|ps: &Vec<(bool, $EPoint)>| { + $GOp::check_multipoint_sum(ps); + }); + + bolero::check!() + .with_generator( + bolero::gen::>().with().len(0usize..$GOp::MAX_N_SUM), + ) + .for_each(|ps: &Vec<(bool, $GPoint)>| { + let mut points: Vec<(u8, $GAffine)> = vec![]; + for i in 0..ps.len() { + points.push((ps[i].0 as u8, ps[i].1.p)); + } + + let res1 = $GOp::get_sum_many_points(&points); + let sum = $GOp::deserialize_g(res1); + + assert!(sum.is_in_correct_subgroup_assuming_on_curve()); + }); + } + + #[test] + fn $test_bls12381_crosscheck_sum_and_multiexp() { + bolero::check!() + .with_generator( + bolero::gen::>() + .with() + .len(0usize..=$GOp::MAX_N_MULTIEXP), + ) + .for_each(|ps: &Vec<(bool, $GPoint)>| { + let mut points: Vec<(u8, $GAffine)> = vec![]; + for i in 0..ps.len() { + points.push((ps[i].0 as u8, ps[i].1.p)); + } + + let res1 = $GOp::get_sum_many_points(&points); + let res2 = $GOp::get_multiexp_many_points(&points); + assert_eq!(res1, res2); + }); + } + + #[test] + fn $test_bls12381_sum_incorrect_input() { + bolero::check!().with_type().for_each(|p: &$EPoint| { + let mut test_vecs: Vec>> = $GOp::get_incorrect_points(p) + .into_iter() + .map(|test| vec![vec![0u8], test]) + .collect(); + + // Incorrect sign encoding + test_vecs.push(vec![vec![2u8], get_zero($GOp::POINT_LEN)]); + + for i in 0..test_vecs.len() { + run_bls12381_fn!($bls12381_sum, test_vecs[i], 1); + } + }); + } + }; + } + + test_bls12381_sum!( + G1Operations, + G1Point, + E1Point, + EnotG1Point, + G1Affine, + bls12381_p1_sum, + check_sum_p1, + test_bls12381_p1_sum_edge_cases_fuzzer, + test_bls12381_p1_sum_fuzzer, + test_bls12381_p1_sum_not_g1_points_fuzzer, + test_bls12381_p1_sum_inverse_fuzzer, + test_bls12381_p1_sum_many_points_fuzzer, + test_bls12381_p1_crosscheck_sum_and_multiexp_fuzzer, + test_bls12381_p1_sum_incorrect_input_fuzzer + ); + test_bls12381_sum!( + G2Operations, + G2Point, + E2Point, + EnotG2Point, + G2Affine, + bls12381_p2_sum, + check_sum_p2, + test_bls12381_p2_sum_edge_cases_fuzzer, + test_bls12381_p2_sum_fuzzer, + test_bls12381_p2_sum_not_g2_points_fuzzer, + test_bls12381_p2_sum_inverse_fuzzer, + test_bls12381_p2_sum_many_points_fuzzer, + test_bls12381_p2_crosscheck_sum_and_multiexp_fuzzer, + test_bls12381_p2_sum_incorrect_input_fuzzer + ); + + macro_rules! test_bls12381_memory_limit { + ( + $namespace_name:ident, + $INPUT_SIZE:expr, + $MAX_N:expr, + $run_bls_fn:ident + ) => { + mod $namespace_name { + use crate::logic::tests::bls12381::tests::$run_bls_fn; + use crate::logic::tests::vm_logic_builder::VMLogicBuilder; + + // Input is beyond memory bounds. + #[test] + #[should_panic] + fn test_bls12381_too_big_input() { + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + + let buffer = vec![0u8; $INPUT_SIZE * $MAX_N]; + + let input = logic.internal_mem_write(buffer.as_slice()); + $run_bls_fn(input, &mut logic); + } + + #[test] + #[should_panic] + fn test_bls12381_incorrect_length() { + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + + let buffer = vec![0u8; $INPUT_SIZE - 1]; + + let input = logic.internal_mem_write(buffer.as_slice()); + $run_bls_fn(input, &mut logic); + } + } + }; + } + + test_bls12381_memory_limit!(memory_limit_p1_sum, 97, 676, sum_g1_return_value); + test_bls12381_memory_limit!(memory_limit_p2_sum, 193, 340, sum_g2_return_value); + test_bls12381_memory_limit!(memory_limit_p1_multiexp, 128, 600, multiexp_g1_return_value); + test_bls12381_memory_limit!(memory_limit_p2_multiexp, 224, 300, multiexp_g2_return_value); + test_bls12381_memory_limit!(memory_limit_map_fp_to_g1, 48, 1500, map_fp_to_g1_return_value); + test_bls12381_memory_limit!(memory_limit_map_fp2_to_g2, 96, 700, map_fp2tog2_return_value); + test_bls12381_memory_limit!(memory_limit_p1_decompress, 48, 1500, decompress_g1_return_value); + test_bls12381_memory_limit!(memory_limit_p2_decompress, 96, 700, decompress_g2_return_value); + test_bls12381_memory_limit!(memory_limit_pairing_check, 288, 500, run_pairing_check_raw); + + macro_rules! test_bls12381_multiexp { + ( + $GOp:ident, + $GPoint:ident, + $EPoint:ident, + $EnotGPoint:ident, + $GAffine:ident, + $bls12381_multiexp:ident, + $bls12381_sum:ident, + $test_bls12381_multiexp_mul:ident, + $test_bls12381_multiexp_many_points: ident, + $test_bls12381_multiexp_incorrect_input: ident, + $test_bls12381_multiexp_invariants_checks: ident, + $test_bls12381_error_encoding: ident + ) => { + #[test] + fn $test_bls12381_multiexp_mul() { + bolero::check!() + .with_generator(( + generator::gen::<$GPoint>(), + generator::gen::().with().bounds(0..=200), + )) + .for_each(|(p, n): &($GPoint, usize)| { + let points: Vec<(u8, $GAffine)> = vec![(0, p.p.clone()); *n]; + let res1 = $GOp::get_sum_many_points(&points); + let res2 = $GOp::get_multiexp_small(&vec![(*n as u8, p.p.clone())]); + + assert_eq!(res1, res2); + let res3 = p.p.mul(Fr::from(*n as u64)); + assert_eq!(res1, $GOp::serialize_uncompressed_g(&res3.into())); + }); + + bolero::check!().with_type().for_each(|(p, n): &($GPoint, Scalar)| { + let res1 = $GOp::get_multiexp(&vec![(n.p.clone(), p.p.clone())]); + let res2 = p.p.mul(&n.p); + + assert_eq!(res1, $GOp::serialize_uncompressed_g(&res2.into())); + }); + } + + #[test] + fn $test_bls12381_multiexp_many_points() { + bolero::check!() + .with_generator( + bolero::gen::>() + .with() + .len(0usize..=$GOp::MAX_N_MULTIEXP), + ) + .for_each(|ps: &Vec<(Scalar, $GPoint)>| { + let mut res2 = $GAffine::identity(); + let mut points: Vec<(Fr, $GAffine)> = vec![]; + for i in 0..ps.len() { + points.push((ps[i].0.p, ps[i].1.p)); + res2 = res2.add(&points[i].1.mul(&points[i].0)).into(); + } + + let res1 = $GOp::get_multiexp(&points); + assert_eq!(res1, $GOp::serialize_uncompressed_g(&res2.into())); + }); + } + + #[test] + fn $test_bls12381_multiexp_incorrect_input() { + let zero_scalar = vec![0u8; 32]; + bolero::check!().with_type().for_each(|p: &$EPoint| { + let test_vecs: Vec>> = $GOp::get_incorrect_points(p) + .into_iter() + .map(|test| vec![test, zero_scalar.clone()]) + .collect(); + + for i in 0..test_vecs.len() { + run_bls12381_fn!($bls12381_multiexp, test_vecs[i], 1); + } + }); + + //points not from G + bolero::check!().with_type().for_each(|p: &$EnotGPoint| { + let p_ser = $GOp::serialize_uncompressed_g(&p.p); + run_bls12381_fn!($bls12381_multiexp, vec![p_ser, zero_scalar.clone()], 1); + }); + } + + #[test] + fn $test_bls12381_multiexp_invariants_checks() { + let zero1 = get_zero($GOp::POINT_LEN); + let r = Fr::from_str(R).unwrap(); + + bolero::check!() + .with_generator(( + generator::gen::<$GPoint>(), + generator::gen::(), + generator::gen::().with().bounds(0..$GOp::MAX_N_MULTIEXP), + )) + .for_each(|(p, scalar, n): &($GPoint, Scalar, usize)| { + // group_order * P = 0 + let res = $GOp::get_multiexp(&vec![(r.clone(), p.p.clone())]); + assert_eq!(res.as_slice(), zero1); + + // (scalar + group_order) * P = scalar * P + let res1 = $GOp::get_multiexp(&vec![(scalar.p.clone(), p.p.clone())]); + let scalar_plus_r = scalar.p.add(&r); + let res2 = $GOp::get_multiexp(&vec![(scalar_plus_r.clone(), p.p.clone())]); + assert_eq!(res1, res2); + + // P + P + ... + P = N * P + let res1 = $GOp::get_multiexp(&vec![(Fr::one(), p.p.clone()); *n]); + let res2 = $GOp::get_multiexp(&vec![(Fr::from(*n as u8), p.p.clone())]); + assert_eq!(res1, res2); + + // 0 * P = 0 + let res1 = $GOp::get_multiexp(&vec![(Fr::zero(), p.p.clone())]); + assert_eq!(res1, zero1); + + // 1 * P = P + let res1 = $GOp::get_multiexp(&vec![(Fr::one(), p.p.clone())]); + assert_eq!(res1, $GOp::serialize_uncompressed_g(&p.p)); + }); + } + }; + } + + test_bls12381_multiexp!( + G1Operations, + G1Point, + E1Point, + EnotG1Point, + G1Affine, + bls12381_g1_multiexp, + bls12381_p1_sum, + test_bls12381_g1_multiexp_mul_fuzzer, + test_bls12381_g1_multiexp_many_points_fuzzer, + test_bls12381_g1_multiexp_incorrect_input_fuzzer, + test_bls12381_g1_multiexp_invariants_checks_fuzzer, + test_bls12381_error_g1_encoding + ); + test_bls12381_multiexp!( + G2Operations, + G2Point, + E2Point, + EnotG2Point, + G2Affine, + bls12381_g2_multiexp, + bls12381_p2_sum, + test_bls12381_g2_multiexp_mul_fuzzer, + test_bls12381_g2_multiexp_many_points_fuzzer, + test_bls12381_g2_multiexp_incorrect_input_fuzzer, + test_bls12381_g2_multiexp_invariants_checks_fuzzer, + test_bls12381_error_g2_encoding + ); + + fn add_p_y(point: &G1Affine) -> Vec { + let mut ybig: Fq = *point.y().unwrap(); + ybig = ybig.add(&Fq::from_str(P).unwrap()); + let p_ser = G1Operations::serialize_uncompressed_g(&point); + let mut y_ser: Vec = vec![0u8; 48]; + ybig.serialize_with_flags(y_ser.as_mut_slice(), EmptyFlags).unwrap(); + + [p_ser[..48].to_vec(), y_ser].concat() + } + + fn add2_p_y(point: &G2Affine) -> Vec { + let mut yabig = (*point.y().unwrap()).c1; + yabig = yabig.add(&Fq::from_str(P).unwrap()); + let p_ser = G2Operations::serialize_uncompressed_g(&point); + let mut y_ser: Vec = vec![0u8; 48]; + yabig.serialize_with_flags(y_ser.as_mut_slice(), EmptyFlags).unwrap(); + + [p_ser[..96 + 48].to_vec(), y_ser].concat() + } + + macro_rules! test_bls12381_map_fp_to_g { + ( + $GOp:ident, + $map_to_curve_g:ident, + $Fq:ident, + $FP:ident, + $check_map_fp:ident, + $test_bls12381_map_fp_to_g:ident, + $test_bls12381_map_fp_to_g_many_points:ident + ) => { + fn $check_map_fp(fp: $Fq) { + let res1 = $GOp::map_fp_to_g(vec![fp.clone()]); + + let mut res2 = $GOp::map_to_curve_g(fp); + res2 = res2.clear_cofactor(); + + assert_eq!(res1, $GOp::serialize_uncompressed_g(&res2)); + } + + #[test] + fn $test_bls12381_map_fp_to_g() { + bolero::check!().with_type().for_each(|fp: &$FP| { + $check_map_fp(fp.p); + }); + } + + #[test] + fn $test_bls12381_map_fp_to_g_many_points() { + bolero::check!() + .with_generator(bolero::gen::>().with().len(0usize..=$GOp::MAX_N_MAP)) + .for_each(|fps: &Vec<$FP>| { + let mut fps_fq: Vec<$Fq> = vec![]; + let mut res2_mul: Vec = vec![]; + for i in 0..fps.len() { + fps_fq.push(fps[i].p); + let mut res2 = $GOp::map_to_curve_g(fps[i].p.clone()); + res2 = res2.clear_cofactor(); + + res2_mul.append(&mut $GOp::serialize_uncompressed_g(&res2)); + } + + let res1 = $GOp::map_fp_to_g(fps_fq); + assert_eq!(res1, res2_mul); + }); + } + }; + } + + test_bls12381_map_fp_to_g!( + G1Operations, + map_to_curve_g1, + Fq, + FP, + check_map_fp, + test_bls12381_map_fp_to_g1_fuzzer, + test_bls12381_map_fp_to_g1_many_points_fuzzer + ); + + test_bls12381_map_fp_to_g!( + G2Operations, + map_to_curve_g2, + Fq2, + FP2, + check_map_fp2, + test_bls12381_map_fp2_to_g2_fuzzer, + test_bls12381_map_fp2_to_g2_many_points_fuzzer + ); + + #[test] + fn test_bls12381_map_fp_to_g1_edge_cases() { + check_map_fp(Fq::ZERO); + check_map_fp(Fq::from_str(P_MINUS_1).unwrap()); + } + + #[test] + fn test_bls12381_map_fp_to_g1_incorrect_input() { + let p = hex::decode(P_HEX).unwrap(); + run_bls12381_fn!(bls12381_map_fp_to_g1, [p], 1); + } + + #[test] + fn test_bls12381_map_fp2_to_g2_incorrect_input() { + let p = hex::decode(P_HEX).unwrap(); + run_bls12381_fn!(bls12381_map_fp2_to_g2, [p.clone(), vec![0u8; 48]], 1); + run_bls12381_fn!(bls12381_map_fp2_to_g2, [vec![0u8; 48], p], 1); + } + + macro_rules! test_bls12381_decompress { + ( + $GOp:ident, + $GPoint:ident, + $EPoint:ident, + $GAffine:ident, + $POINT_LEN:expr, + $bls12381_decompress:ident, + $add_p:ident, + $test_bls12381_decompress:ident, + $test_bls12381_decompress_many_points:ident, + $test_bls12381_decompress_incorrect_input:ident + ) => { + #[test] + fn $test_bls12381_decompress() { + bolero::check!().with_type().for_each(|p1: &$GPoint| { + let res1 = $GOp::decompress_p(vec![p1.p.clone()]); + assert_eq!(res1, $GOp::serialize_uncompressed_g(&p1.p)); + + let p1_neg = p1.p.mul(&Fr::from(-1)); + let res1_neg = $GOp::decompress_p(vec![p1_neg.clone().into()]); + + assert_eq!(res1[0..$POINT_LEN], res1_neg[0..$POINT_LEN]); + assert_ne!(res1[$POINT_LEN..], res1_neg[$POINT_LEN..]); + assert_eq!(res1_neg, $GOp::serialize_uncompressed_g(&p1_neg.into())); + }); + + let zero1 = $GAffine::identity(); + let res1 = $GOp::decompress_p(vec![zero1.clone()]); + + assert_eq!(res1, $GOp::serialize_uncompressed_g(&zero1)); + } + + #[test] + fn $test_bls12381_decompress_many_points() { + bolero::check!() + .with_generator( + bolero::gen::>().with().len(0usize..=$GOp::MAX_N_DECOMPRESS), + ) + .for_each(|es: &Vec<$EPoint>| { + let mut p1s: Vec<$GAffine> = vec![]; + let mut res2: Vec = vec![]; + for i in 0..es.len() { + p1s.push(es[i].p); + res2.append(&mut $GOp::serialize_uncompressed_g(&p1s[i]).to_vec()); + } + let res1 = $GOp::decompress_p(p1s.clone()); + assert_eq!(res1, res2); + }); + + bolero::check!() + .with_generator( + bolero::gen::>().with().len(0usize..=$GOp::MAX_N_DECOMPRESS), + ) + .for_each(|gs: &Vec<$GPoint>| { + let mut p1s: Vec<$GAffine> = vec![]; + let mut res2: Vec = vec![]; + for i in 0..gs.len() { + p1s.push(gs[i].p); + res2.append(&mut $GOp::serialize_uncompressed_g(&p1s[i]).to_vec()); + } + let res1 = $GOp::decompress_p(p1s.clone()); + assert_eq!(res1, res2); + }); + } + + #[test] + fn $test_bls12381_decompress_incorrect_input() { + // Incorrect encoding of the point at infinity + let mut zero = vec![0u8; $POINT_LEN]; + zero[0] = 0x80 | 0x40; + zero[$POINT_LEN - 1] = 1; + run_bls12381_fn!($bls12381_decompress, vec![zero], 1); + + // Erroneous coding of field elements with an incorrect extra bit in the decompressed encoding. + let mut zero = vec![0u8; $POINT_LEN]; + zero[0] = 0x40; + run_bls12381_fn!($bls12381_decompress, vec![zero], 1); + + bolero::check!().with_type().for_each(|p: &$EPoint| { + let mut p_ser = $GOp::serialize_g(&p.p); + p_ser[0] ^= 0x80; + run_bls12381_fn!($bls12381_decompress, vec![p_ser], 1); + }); + + //Point with a coordinate larger than 'p'. + bolero::check!().with_type().for_each(|p: &$EPoint| { + run_bls12381_fn!($bls12381_decompress, vec![$add_p(&p.p)], 1); + }); + } + }; + } + + test_bls12381_decompress!( + G1Operations, + G1Point, + E1Point, + G1Affine, + 48, + bls12381_p1_decompress, + add_p_x, + test_bls12381_p1_decompress_fuzzer, + test_bls12381_p1_decompress_many_points_fuzzer, + test_bls12381_p1_decompress_incorrect_input_fuzzer + ); + + test_bls12381_decompress!( + G2Operations, + G2Point, + E2Point, + G2Affine, + 96, + bls12381_p2_decompress, + add2_p_x, + test_bls12381_p2_decompress_fuzzer, + test_bls12381_p2_decompress_many_points_fuzzer, + test_bls12381_p2_decompress_incorrect_input_fuzzer + ); + + fn add_p_x(point: &G1Affine) -> Vec { + let mut p_ser = G1Operations::serialize_g(&point); + p_ser[0] |= 0x1f; + p_ser + } + + fn add2_p_x(point: &G2Affine) -> Vec { + let mut p_ser = G2Operations::serialize_g(&point); + p_ser[0] |= 0x1f; + p_ser + } + + #[test] + fn test_bls12381_pairing_check_one_point_fuzzer() { + bolero::check!().with_type().for_each(|(p1, p2): &(G1Point, G2Point)| { + let zero1 = G1Affine::zero(); + let zero2 = G2Affine::zero(); + + let v = Bls12_381::pairing(p1.p, zero2); + assert!(v.is_zero()); + + assert_eq!(pairing_check(vec![zero1], vec![zero2]), 0); + assert_eq!(pairing_check(vec![zero1], vec![p2.p]), 0); + assert_eq!(pairing_check(vec![p1.p], vec![zero2]), 0); + assert_eq!(pairing_check(vec![p1.p], vec![p2.p]), 2); + }); + } + + #[test] + fn test_bls12381_pairing_check_two_points_fuzzer() { + bolero::check!().with_type().for_each( + |(p1, p2, s1, s2): &(G1Point, G2Point, Scalar, Scalar)| { + let p1_neg = p1.p.neg(); + let p2_neg = p2.p.neg(); + + assert_eq!(pairing_check(vec![p1.p, p1_neg], vec![p2.p, p2.p]), 0); + assert_eq!(pairing_check(vec![p1.p, p1.p], vec![p2.p, p2_neg]), 0); + assert_eq!(pairing_check(vec![p1.p, p1.p], vec![p2.p, p2.p]), 2); + + assert_eq!( + pairing_check( + vec![p1.p.mul(&s1.p).into(), p1_neg.mul(&s2.p).into()], + vec![p2.p.mul(&s2.p).into(), p2.p.mul(&s1.p).into()] + ), + 0 + ); + assert_eq!( + pairing_check( + vec![p1.p.mul(&s1.p).into(), p1.p.mul(&s2.p).into()], + vec![p2.p.mul(&s2.p).into(), p2_neg.mul(&s1.p).into()] + ), + 0 + ); + assert_eq!( + pairing_check( + vec![p1.p.mul(&s1.p).into(), p1.p.mul(&s2.p).into()], + vec![p2_neg.mul(&s2.p).into(), p2_neg.mul(&s1.p).into()] + ), + 2 + ); + }, + ); + } + + #[test] + fn test_bls12381_pairing_check_many_points_fuzzer() { + bolero::check!() + .with_generator( + bolero::gen::>().with().len(0usize..MAX_N_PAIRING), + ) + .for_each(|scalars: &Vec<(Scalar, Scalar)>| { + let mut scalars_1: Vec = vec![]; + let mut scalars_2: Vec = vec![]; + + let g1: G1Affine = G1Affine::generator(); + let g2: G2Affine = G2Affine::generator(); + + let mut g1s: Vec = vec![]; + let mut g2s: Vec = vec![]; + + let mut scalar_res = Fr::from(0); + + for i in 0..scalars.len() { + scalars_1.push(scalars[i].0.p); + scalars_2.push(scalars[i].1.p); + + scalar_res = scalar_res.add(&scalars_1[i].mul(&scalars_2[i])); + + g1s.push(g1.mul(&scalars_1[i]).into()); + g2s.push(g2.mul(&scalars_2[i]).into()); + } + + if !scalar_res.is_zero() { + assert_eq!(pairing_check(g1s.clone(), g2s.clone()), 2); + } else { + assert_eq!(pairing_check(g1s.clone(), g2s.clone()), 0); + } + + for i in 0..scalars.len() { + let mut p2 = g2.mul(&scalars_1[i]).into_affine(); + p2 = p2.neg(); + + g1s.push(g1.mul(&scalars_2[i]).into()); + g2s.push(p2); + } + + assert_eq!(pairing_check(g1s, g2s), 0); + }); + } + + #[test] + fn test_bls12381_pairing_incorrect_input_point_fuzzer() { + bolero::check!().with_type().for_each( + |(p1_not_from_g1, p2, p1, p2_not_from_g2, curve_p1, curve_p2): &( + EnotG1Point, + G2Point, + G1Point, + EnotG2Point, + E1Point, + E2Point, + )| { + assert_eq!(pairing_check(vec![p1_not_from_g1.p], vec![p2.p]), 1); + assert_eq!(pairing_check(vec![p1.p], vec![p2_not_from_g2.p]), 1); + + let p1_ser = G1Operations::serialize_uncompressed_g(&p1.p).to_vec(); + let p2_ser = G2Operations::serialize_uncompressed_g(&p2.p).to_vec(); + let test_vecs: Vec> = G1Operations::get_incorrect_points(curve_p1); + for i in 0..test_vecs.len() { + assert_eq!(pairing_check_vec(test_vecs[i].clone(), p2_ser.clone()), 1); + } + + let test_vecs: Vec> = G2Operations::get_incorrect_points(curve_p2); + for i in 0..test_vecs.len() { + assert_eq!(pairing_check_vec(p1_ser.clone(), test_vecs[i].clone()), 1); + } + + // not G1 point + let p_ser = G1Operations::serialize_uncompressed_g(&p1_not_from_g1.p); + assert_eq!( + pairing_check_vec( + p_ser.to_vec(), + G2Operations::serialize_uncompressed_g(&p2.p).to_vec() + ), + 1 + ); + + // not G2 point + let p_ser = G2Operations::serialize_uncompressed_g(&p2_not_from_g2.p); + assert_eq!( + pairing_check_vec( + G1Operations::serialize_uncompressed_g(&p1.p).to_vec(), + p_ser.to_vec() + ), + 1 + ); + }, + ); + } + + #[test] + fn test_bls12381_empty_input() { + assert_eq!(get_zero(96), G1Operations::get_multiexp_many_points(&vec![])); + assert_eq!(get_zero(192), G2Operations::get_multiexp_many_points(&vec![])); + assert_eq!(G1Operations::map_fp_to_g(vec![]).len(), 0); + assert_eq!(G2Operations::map_fp_to_g(vec![]).len(), 0); + assert_eq!(pairing_check(vec![], vec![]), 0); + assert_eq!(G1Operations::decompress_p(vec![]).len(), 0); + assert_eq!(G2Operations::decompress_p(vec![]).len(), 0); + } + + // EIP-2537 tests + macro_rules! eip2537_tests { + ( + $file_path:expr, + $test_name:ident, + $item_size:expr, + $transform_input:ident, + $run_bls_fn:ident, + $check_res:ident + ) => { + #[test] + fn $test_name() { + let input_csv = fs::read($file_path).unwrap(); + let mut reader = csv::Reader::from_reader(input_csv.as_slice()); + for record in reader.records() { + let record = record.unwrap(); + + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(); + + let bytes_input = hex::decode(&record[0]).unwrap(); + let k = bytes_input.len() / $item_size; + let mut bytes_input_fix: Vec> = vec![]; + for i in 0..k { + bytes_input_fix.push($transform_input( + bytes_input[i * $item_size..(i + 1) * $item_size].to_vec(), + )); + } + + let input = logic.internal_mem_write(&bytes_input_fix.concat()); + let res = $run_bls_fn(input, &mut logic); + $check_res(&record[1], res); + } + } + }; + } + + fn fix_eip2537_pairing_input(input: Vec) -> Vec { + [ + fix_eip2537_g1(input[..128].to_vec()).to_vec(), + fix_eip2537_g2(input[128..].to_vec()).to_vec(), + ] + .concat() + } + + fn fix_eip2537_fp(fp: Vec) -> Vec { + fp[16..].to_vec() + } + + fn fix_eip2537_fp2(fp2: Vec) -> Vec { + [fp2[64 + 16..].to_vec(), fp2[16..64].to_vec()].concat() + } + + macro_rules! fix_eip2537_input { + ($namespace_name:ident, $fix_eip2537_fp:ident) => { + mod $namespace_name { + use crate::logic::tests::bls12381::tests::$fix_eip2537_fp; + + pub fn fix_eip2537_g(g: Vec) -> Vec { + let mut res = [ + $fix_eip2537_fp(g[..g.len() / 2].to_vec()), + $fix_eip2537_fp(g[g.len() / 2..].to_vec()), + ] + .concat(); + + if g == vec![0; g.len()] { + res[0] |= 0x40; + } + + return res; + } + + pub fn fix_eip2537_sum_input(input: Vec) -> Vec { + vec![ + vec![0u8], + fix_eip2537_g(input[..input.len() / 2].to_vec()), + vec![0u8], + fix_eip2537_g(input[input.len() / 2..].to_vec()), + ] + .concat() + } + + pub fn fix_eip2537_mul_input(input: Vec) -> Vec { + vec![ + fix_eip2537_g(input[..(input.len() - 32)].to_vec()), + input[(input.len() - 32)..].to_vec().into_iter().rev().collect(), + ] + .concat() + } + + pub fn cmp_output_g(output: &str, res: Vec) { + let bytes_output = fix_eip2537_g(hex::decode(output).unwrap()); + assert_eq!(res, bytes_output); + } + } + }; + } + + fix_eip2537_input!(fix_eip2537_g1_namespace, fix_eip2537_fp); + use fix_eip2537_g1_namespace::cmp_output_g as cmp_output_g1; + use fix_eip2537_g1_namespace::fix_eip2537_g as fix_eip2537_g1; + use fix_eip2537_g1_namespace::fix_eip2537_mul_input as fix_eip2537_mul_g1_input; + use fix_eip2537_g1_namespace::fix_eip2537_sum_input as fix_eip2537_sum_g1_input; + + fix_eip2537_input!(fix_eip2537_g2_namespace, fix_eip2537_fp2); + use fix_eip2537_g2_namespace::cmp_output_g as cmp_output_g2; + use fix_eip2537_g2_namespace::fix_eip2537_g as fix_eip2537_g2; + use fix_eip2537_g2_namespace::fix_eip2537_mul_input as fix_eip2537_mul_g2_input; + use fix_eip2537_g2_namespace::fix_eip2537_sum_input as fix_eip2537_sum_g2_input; + + fn check_pairing_res(output: &str, res: u64) { + if output == "0000000000000000000000000000000000000000000000000000000000000000" { + assert_eq!(res, 2); + } else if output == "0000000000000000000000000000000000000000000000000000000000000001" { + assert_eq!(res, 0); + } else { + assert_eq!(res, 1); + } + } + + fn error_check(output: &str, res: u64) { + if !output.contains("padded BE encoding are NOT zeroes") { + assert_eq!(res, 1) + } + } + + macro_rules! run_bls12381_fn_raw { + ($fn_name_raw:ident, $fn_name_return_value_only:ident, $bls_fn_name:ident) => { + #[allow(unused)] + fn $fn_name_raw(input: MemSlice, logic: &mut TestVMLogic) -> Vec { + let res = logic.$bls_fn_name(input.len, input.ptr, 0).unwrap(); + assert_eq!(res, 0); + logic.registers().get_for_free(0).unwrap().to_vec() + } + + #[allow(unused)] + fn $fn_name_return_value_only(input: MemSlice, logic: &mut TestVMLogic) -> u64 { + logic.$bls_fn_name(input.len, input.ptr, 0).unwrap() + } + }; + } + + run_bls12381_fn_raw!(run_map_fp_to_g1, map_fp_to_g1_return_value, bls12381_map_fp_to_g1); + run_bls12381_fn_raw!(run_map_fp2_to_g2, map_fp2tog2_return_value, bls12381_map_fp2_to_g2); + run_bls12381_fn_raw!(run_sum_g1, sum_g1_return_value, bls12381_p1_sum); + run_bls12381_fn_raw!(run_sum_g2, sum_g2_return_value, bls12381_p2_sum); + run_bls12381_fn_raw!(run_multiexp_g1, multiexp_g1_return_value, bls12381_g1_multiexp); + run_bls12381_fn_raw!(run_multiexp_g2, multiexp_g2_return_value, bls12381_g2_multiexp); + run_bls12381_fn_raw!(decompress_g1, decompress_g1_return_value, bls12381_p1_decompress); + run_bls12381_fn_raw!(decompress_g2, decompress_g2_return_value, bls12381_p2_decompress); + fn run_pairing_check_raw(input: MemSlice, logic: &mut TestVMLogic) -> u64 { + logic.bls12381_pairing_check(input.len, input.ptr).unwrap() + } + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/pairing.csv", + test_bls12381_pairing_test_vectors, + 384, + fix_eip2537_pairing_input, + run_pairing_check_raw, + check_pairing_res + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/fp_to_g1.csv", + test_bls12381_fp_to_g1_test_vectors, + 64, + fix_eip2537_fp, + run_map_fp_to_g1, + cmp_output_g1 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/fp2_to_g2.csv", + test_bls12381_fp2_to_g2_test_vectors, + 128, + fix_eip2537_fp2, + run_map_fp2_to_g2, + cmp_output_g2 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/g1_add.csv", + test_bls12381_g1_add_test_vectors, + 256, + fix_eip2537_sum_g1_input, + run_sum_g1, + cmp_output_g1 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/g2_add.csv", + test_bls12381_g2_add_test_vectors, + 512, + fix_eip2537_sum_g2_input, + run_sum_g2, + cmp_output_g2 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/g1_mul.csv", + test_bls12381_g1_mul_test_vectors, + 160, + fix_eip2537_mul_g1_input, + run_multiexp_g1, + cmp_output_g1 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/g2_mul.csv", + test_bls12381_g2_mul_test_vectors, + 288, + fix_eip2537_mul_g2_input, + run_multiexp_g2, + cmp_output_g2 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/g1_multiexp.csv", + test_bls12381_g1_multiexp_test_vectors, + 160, + fix_eip2537_mul_g1_input, + run_multiexp_g1, + cmp_output_g1 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/g2_multiexp.csv", + test_bls12381_g2_multiexp_test_vectors, + 288, + fix_eip2537_mul_g2_input, + run_multiexp_g2, + cmp_output_g2 + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/pairing_error.csv", + test_bls12381_pairing_error_test_vectors, + 384, + fix_eip2537_pairing_input, + run_pairing_check_raw, + check_pairing_res + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/multiexp_g1_error.csv", + test_bls12381_g1_multiexp_error_test_vectors, + 160, + fix_eip2537_mul_g1_input, + multiexp_g1_return_value, + error_check + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/multiexp_g2_error.csv", + test_bls12381_g2_multiexp_error_test_vectors, + 288, + fix_eip2537_mul_g2_input, + multiexp_g2_return_value, + error_check + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/fp_to_g1_error.csv", + test_bls12381_fp_to_g1_error_test_vectors, + 64, + fix_eip2537_fp, + map_fp_to_g1_return_value, + error_check + ); + + eip2537_tests!( + "src/logic/tests/bls12381_test_vectors/fp2_to_g2_error.csv", + test_bls12381_fp2_to_g2_error_test_vectors, + 128, + fix_eip2537_fp2, + map_fp2tog2_return_value, + error_check + ); +} diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp2_to_g2.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp2_to_g2.csv new file mode 100644 index 00000000000..90a0867059a --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp2_to_g2.csv @@ -0,0 +1,104 @@ +input,result +0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,000000000000000000000000000000000d029393d3a13ff5b26fe52bd8953768946c5510f9441f1136f1e938957882db6adbd7504177ee49281ecccba596f2bf000000000000000000000000000000001993f668fb1ae603aefbb1323000033fcb3b65d8ed3bf09c84c61e27704b745f540299a1872cd697ae45a5afd780f1d600000000000000000000000000000000079cb41060ef7a128d286c9ef8638689a49ca19da8672ea5c47b6ba6dbde193ee835d3b87a76a689966037c07159c10d0000000000000000000000000000000017c688ae9a8b59a7069c27f2d58dd2196cb414f4fb89da8510518a1142ab19d158badd1c3bad03408fafb1669903cd6c +000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2,000000000000000000000000000000000a2bca68ca23f3f03c678140d87465b5b336dbd50926d1219fcc0def162280765fe1093c117d52483d3d8cdc7ab76529000000000000000000000000000000000fe83e3a958d6038569da6132bfa19f0e3dae3bee0d8a60e7cc33e4d7084a9e8c32fe31ec6e617277e2e450699eba1f80000000000000000000000000000000005602683f0ef231cc0b7c8c695765d7933f4efa7503ed9f2aa3c774284eabcdd32fd287b6a3539c9749f2e15b58f5cd50000000000000000000000000000000000b4f17de0db6e9d081723b613b23864c1eeae91b7cbda40ecd24823022aee7fc4068adc41947b97e17009fad9d0d4de +000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de200000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799,00000000000000000000000000000000184af3f8a359dd35dddd3dfcc6f5b55ed327907ed573378289209569244e3c9c02bdf278eb567186f8b64de380c115360000000000000000000000000000000012f5ba8e520c4730ac1fb75dabbfdc0181855e5ba2968a8c0ba36a47ab86ac45d19aa3d55f15a601e120be1f75eefe240000000000000000000000000000000004e313db704b103c2c1e3a58f8e95a470e7199081eb086e9524583131714c4a3db551fd51a3f2314a19a658e7b1765380000000000000000000000000000000004040eab7416a1703b0d103120506f1de2b26b0f48c7a0ea63dca4d9ad1c478ae03b5d7bfd51f4cd6f8cea26212c4edf +000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60,0000000000000000000000000000000017fc341e495bf4ef5da4c159a28320aca97ca28fe3a0441242cf506b0f89bb52f5b5d8c6e038d229ffe67d00151912f00000000000000000000000000000000007666300b7be3d904ae3d19019f7be5cf5ba6161b969c1a78aff639a24387d8fdcc4d0e3cd81ba6f063ebf2d859370f20000000000000000000000000000000007cc705dbfb5c0418beb1cfbd864fa0631bd60eccfdb16b5d55b6ef3558e2ec87dac3b45294dcf04a064d6d1eba5a6eb00000000000000000000000000000000052cb9c982e6b05c1d2ab4eed1d8082f96426b55615ebc6a53bdc320ccad0aad044395ed641b3176b554f19e62d46b73 +0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f1570000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac,0000000000000000000000000000000015ad9155037e03898cb3b706f7105e39d413ff3a5abb65812b8d21d003cab8fbb607d3938ccd6a774bc8debfa30f42760000000000000000000000000000000019d6382bb2d78180a8998a0536d67412d00ec0ef65f4cbce01340b8d6e781c0ff790296f8cada28966b147c69e02f366000000000000000000000000000000001290c2c205b748069d0875a89ca74a3b05ad8218ed46a1570696932302983c090d96e17e0b828a666fdfc3b72cd348bc000000000000000000000000000000000114f2f7ffaa9f90b547e86c863a5d3585819a78b095848dfa39576a10874a905488687b73e613f3d426510f5d1d1ce1 +000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c80000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3,0000000000000000000000000000000012084a53cde353a46af17cd2fb02c477e47b874d8ff58025b5015837759032ff98013dc5bf01253bb964f035183c9071000000000000000000000000000000001659272ab7e3a070a5c7b25a5d3402f7371ed67e58cac8438df41c39c1acd95ac5886b030384bf537d7c4bb8ddb2c538000000000000000000000000000000000852ddcc37a09a0a8f62dfbd1ba5064c1f6afacc9a279a4d998bed643eec5a0d96d6bad95701a04f52c83e8f87f48d5d00000000000000000000000000000000097a399370875398028d42bde8cf4e9641730af7a2971e2f59c95938120603a239c65030ded4323c955f7fd24bebf31b +00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be,0000000000000000000000000000000006f8191123f1e8f6a05e4e663fa763c8a0ade5de3c7cd38ec1c82e1c85f123ab51fffcebd677afec8e9adecd8d11263d0000000000000000000000000000000004fcd825bc55d044eb70e0bdd5ea2ac58ec1487e903b431c57a640c756265a382581b8450fb15dc649cf22a8539088220000000000000000000000000000000015259f83d76490bb868bb88c2a2c3e07a326bd3e97fc2f552adf85722a360a443d720c328076e35224328e09494746e0000000000000000000000000000000000f76b0b960a1343b4267f5aff44901fd6796a778b1a87666b95b773edd0e7ffb6656d4f0cc3b9b38bc6c0ed20cfce153 +0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3,0000000000000000000000000000000018bf5f93dbc2c37479b819f8edccd687c4d3c4dd04f8c73762fd89d0c003674e3b2ed749d23e775f925279b3112689f80000000000000000000000000000000008a033b197aa8ea2213dbd7ed478d98c25dc6e9f91b9924f3c14124da26a67bb196926e02da89b746f2a67b14ad226070000000000000000000000000000000006f7824bdc9c53212609512858278f79d9b094165ff178e3da8776e24311bebbd9deb29f366d4c7693a15c34df118403000000000000000000000000000000000edde25fc24b9ec58b3c317aa3ae48dd5fecdf6397ed9636ea042722d264db0b1a89a15a1e16e892755730ef52796527 +00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad,0000000000000000000000000000000002d28025f4b798083aec3ca9a91a051ce27a374b115c944932026b4fe0dcf68b335d5e47212f800c241c2d42fd219635000000000000000000000000000000001742fb6ef8e9a5a7572b0d3fa4ae8ae56c9c6f4daa20d0b88212c40511c6f6b5ee98314a2d1cbe4bbbec907495a1ade8000000000000000000000000000000000d700a511a58c1b8f11153669cb21d88512dfdacbabe38e402431b4f7ba374b5f9a88614da2d56799d39324e9d19e27a000000000000000000000000000000000c6068bc7a43d614b8f1132b13e04f66d2fb5ac0c5bc8501b754a0bcf4f382db92b0994c4999e104c9d1111ef91d5edc +000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20,000000000000000000000000000000001350c68434a9b02392e60540a3985bae8daf9a170b30336ac73afae6f892c7ae8f5f1cadfb2780d6e5961ebf91cd69ee0000000000000000000000000000000000c20bd286fc1886b9b28dfa40d1a27395cf76a8b73946849ea0a7b5e12530de13c16acef8fe2a2c247ea65ca023eed70000000000000000000000000000000002d8ffd0235fb60fa573662034d46260e0c96396537b2a9d486dd03bdd13c5a1efd2d3cb9849ed11c4376b665f378226000000000000000000000000000000000d90ca1b73a6a9566832f9f19d8530a3b12f22bef853fc44088559b923ca108cebf4291e0d7de8f25c7429d455f5ae46 +0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab10000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175,0000000000000000000000000000000013f223602e8d12c3bb51cd393f6f59beb5c55fe80c3fc8fb0bc90eca533d9b7981563a30ebd727ab6cf0111fa2d3099d000000000000000000000000000000000962b0585c681894cb701f17ec06c0c240899db574c02d82d85ed4dabd4b8654c29b84c71d2921986fc2abc542a3ed9f0000000000000000000000000000000000f0e79245e645a6e3fb88b9103ede3e6ecdd7e45d61b5755d7a8d100d80719746af58bb23d3068cee7389b2acf17f8b0000000000000000000000000000000017fa0aac84c58283f34b9bf713cde98c175b38e92503c08205350822d778f3dd5bed8051e185c495831a628aa89335c7 +0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d235020000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3,000000000000000000000000000000001062c97c214b86518660c5e1c33a4e48923ae89ab7d8bc5c798e631de16fc1f104aa957d3e7915aee8551e24aaafc8e6000000000000000000000000000000000e42b785f17f25b87a0dc558a8d57b19d8f41767c3b4fd70c147e95443aff2d9a743003da41d578a2b56d7dc748cf59500000000000000000000000000000000111fd38cd2f5f681bb37f6239a5eea820ce3f01023c685f8e7e244fe9aa9dcbd18f0e50705faa5d8d66b28af9f371c630000000000000000000000000000000004726d3e452f6fcb180ce1d50bbee3a23f7949b635a058f12de1cf5abda19c042168feea53211dbed0bfca489a020930 +0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d99000000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844,000000000000000000000000000000001211bb8d3bf65b60efc7237ffecddb4e7e2f0dd36e2a704dfc9f4972897addff1a57182f8e0a0ac08c9af2c98eaa4c560000000000000000000000000000000007e9877280aad45a3b1453b6771ab509e4f53937cc6da73d3add50aff94869b27f49218fb479fe19a6176b9aadd36e35000000000000000000000000000000000ff915801695a281f6642751be77155a813847ae0237d77d2edf836aebac02b659b98d49842d4d10e82d9d146e63a3da000000000000000000000000000000000fae1c8c01a2dd94f17c660353d158ff6f3eed4e6375f1e414ade9d6fd040a48e3ff0d558c882e92e74bd6ef4ab06168 +0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2,000000000000000000000000000000000dd00d9f31cb5148048125668286c1790cb7294e740df978ac0bdaa6e1c4ba139a04f5770b194c9bcfb123d9b40b6acb00000000000000000000000000000000085d5f4cb831720fa13cef25464a1ba7af33abcc4079d2c5736a219ad9649ebb5dbb8687a2d3952390866587d7088f72000000000000000000000000000000000de377d773e40e1c76e218b969297d15f7819c525ce39aee5114e8405bd7361116682cf9d673574d415a7016b23b567d0000000000000000000000000000000018db26c2097f72b8788ef5aad2d7aa400627e224924afea1ac7c7a6b5cff4a55255e218572614519a536eaaf0f65533c +000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb87500000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75,000000000000000000000000000000001853b4c4e6fcdbed29c5d3aa4a9f6d447adc512f66a32fdef06c6ad316c42eb3ca47ffe6f21318ad610d0a68673d7bc300000000000000000000000000000000123d15c37fa8b1a95229e28500c9a767e6286b780138dcff2714bf1f8242f39bebb7d86e2811551914719ca90fb5615f000000000000000000000000000000000537498c2ec64b2ba58aa0a858b69990cac544d5cac29abdf6a42ae9c04061f83580b79c2a6104ebc55939d9a2bc5ae2000000000000000000000000000000000b348c19aad3b67c690512f372d995555ee38bffcdaf33bb827160d6929d2ce598523880f6136f11e1d6482a654cb016 +00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be122200000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760,000000000000000000000000000000000994e7b6ccafc996f672c42ab491105ffe1482e65aeb456de2213b531889773ad4d5e6ea1687d6a1f13e74878766f11e000000000000000000000000000000000b89030486a1d622c97970ee7da6189ac341b9cafbb4081463f579ab8b4b049c6e6c8b63157455770a79108424a14f24000000000000000000000000000000000ded43800a991f8c37282d803a39941d3bfbfbdc56dbf7500ef3d16750b27dcb1ad93f89714395fd3dffe318c1771375000000000000000000000000000000001994144b032e1f8c4d688754eef82cdba0018ac47030fcb77e8fd920e0b0336255d2cc8376c03e1074f91269cd2519d1 +00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091,00000000000000000000000000000000195d99406baadc7d8740962cbbf4bc1f22b08eafb52f3cb3c588b6cb3cd89d16cb7b8d388563289f5b5ea466128525c80000000000000000000000000000000004809f70463633595dd763d658354df4f9b409911e1a0328fdaf486d76ffb410d7c6cfcc2d48fd6757d5c2a4834f81fd000000000000000000000000000000000654f8475562098a2cb27ce224674a383283cde35173e1c16b141998b641ac9ee663d766f045451a7f6d600973f0ec520000000000000000000000000000000013bac451a44982c7b1aaac7522dab598cb79b9a3dab77f4d5a4c1c97c154451499979af1f86ced8ce2099bccd400420d +0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1,0000000000000000000000000000000016ea88d0bce32981f489438df1bc14e7ade7a45d449ee1ac1a041c1204460cf53ae5c0e111914d8af9e6b3b7fa394484000000000000000000000000000000000db571ca6a55bc8285421553a373048f7877ecb9683d52acf07d48e1026795993e4e7177490921bc6fe1e63d69c2de3c0000000000000000000000000000000011602919de1df6cc0dd36a59c84ebb8e209056534e336f5074c9ae5323f8a03b123dc6354cf85301d838b16518ab64390000000000000000000000000000000004407d30fbd632fd493055bd4d8cbed337767a2ac534411a3eabec570ba41d2ad28ef37512a7da3611ad60b6536b3f07 +000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192,000000000000000000000000000000000c1e7b188697aa9a053f14e2d907f2c61a59e0b0c72f9cce30faf81dc714a50113500ca9bc3af6657a5d214f52c90616000000000000000000000000000000001544c35d712eaf79d8dd5a22fbab72f8a6843728898412a7f305b205f8a50e03c6c462b87b3ac165e9e6428e0a44a74a00000000000000000000000000000000029ebafd90a1a887669fd0ace762a66bca2bf0a216333b0ac97dedb6bff3dda2bca1e3d0ed5fa9081c2887fe6a8e24cf000000000000000000000000000000000e1a01ca93ed268e0291a937483f7f8e252c91f9bd8bde55271b0c97fcbbb9219009514217dd8bd7e0267f44e9927a93 +00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a7700000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510,0000000000000000000000000000000004e8ad9838e7e269cddf0ae5c8f0f57e7467e0b6f2b9e37e7c4bcae965e9582dc46c9c50aa01f5dc761bf2f1ad311eec0000000000000000000000000000000011b1438ccc668900914578c3ec6e1334d0823861c892608817498fe2e538deec73e0034a6e8ba9790f63fdd95af3714a0000000000000000000000000000000005b4c88196425d3ecd22bfc0cb1a95488493f85bb74f50315f0ffcdd57ad2de23c137cd6d2f6f6dca8af2e3f7bb0539c0000000000000000000000000000000017066344a0f345ecf6a2ba66c37ccbce26a3f551524f74636d4c4812bf5adfabffb0645b898b10c332e94e5f2ae2d1c2 +000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b8000000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5,0000000000000000000000000000000010d393bf893d589c578df58f4d0098ad3cd10d3a1d0f112f51b132a369e68c0284a6b70a5673383ae24a27a9043b16cf0000000000000000000000000000000003402afb77b187b45906d9cce348976ed88c758d75b9962a53352a6c3ee37751a9928097c0d68c6f8a315def4ca875200000000000000000000000000000000019b98631e53a3ffda3fb9165ef7236dad5c0c8d57c3315617cbd3ce77430bd89b9e1d88a019042cae0075594514a5e67000000000000000000000000000000001783bf1c9b0ec44c9191dab01ef5bda0cb2f533dbcd3aeac2b7c6720dbc8e3f770a215ec8ea2035129711ce4b448ba87 +000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03,00000000000000000000000000000000086ac901098212acd091d9c4d42a1318c3b343480f1130d6e52128d61df9e19fb61ef1ff35de0ef60062cd99202910ff0000000000000000000000000000000019109b7292f1a420f09a56dce9694cb4944808a2ce9f1964cbb6ffd14a710c35abe81300090ffcd9e95f33e0de9f879a0000000000000000000000000000000012660c4e114a215390c6f6eabc4bd6e3d062ee28d0c87e24351c7d43195253cb7b5bcfed2b4abb2fdeb3ac04ee228997000000000000000000000000000000000e56d35a7e40a86ffd2088c81488265ecc4468d6cf02d563c91611cdf8b4333cf66ef50b993fe651b1792d2b242cff94 +000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e,00000000000000000000000000000000071d3e796fb15d63c2d5cf68f59f11792b0b580b85c8839a02fad96664f14735ede2edfd5ba5b64045b366904f54ab600000000000000000000000000000000013fd1ea38d32772458622731b9e2d9d749f2b747443f7e47ef5e041531b56f86d1775d42a548b2bb201228f49ec9f46800000000000000000000000000000000099c2bd996c8c5ee37de971e8b75a0bdd4f69299778ee3d216973c9dbba97c7a93e40b209d390024bc4b5e82560a1a83000000000000000000000000000000000c4922ed9af845467440b78efa3a53ba904f29adf66e8ac437c8bb6624b5e5ba0772a5639b45fe167b1fb9283747c50f +000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e900000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe,000000000000000000000000000000000e413d72fdc3db6fc79ef26ae8b37fe5c4356a80b3598513b5173b3406ffb54708b8794dae158060a1accbe956a39ff30000000000000000000000000000000019ba9dfa74fd241a55a3b47c9f37c6ebd1e8b51f46197881abb64b7f57c0e2d8f18edee35bb9da03702c0dc5cc8749f700000000000000000000000000000000183525156fbc80cc67d6cd15fd2ddf7fb0528656ec1d31b4c275ef101dbb635424abbff1154a3ee04346ac53148fb1f70000000000000000000000000000000011da0dcd666d01180902d8a7fd7d2fbb39f9c7587540451045956108a8579d7c116385a81627dad9d4cb8cfe68927b6d +0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf480000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909,000000000000000000000000000000001932acb1fd0708edf13c293007a035991bdfbfe0089b61c261258e8c5c10d82a5318b2af221b372f0f3f43c391421582000000000000000000000000000000000973650743f0ec8e2acca33f2ef230ee7a05635d14099cdce913ad8678458ec0dde5c5a941097af2ee0c8ffb937d09fd000000000000000000000000000000000bdaf319044101ee9aa27b3accd36a5ecaf8b80deda4548377ddeb97283537be3f7199ad3c190ed23cdb44abb8786a080000000000000000000000000000000006c448827e3fe4f274bfa55a66bc76c5b01e29ac6a8dbebd801855ba4e93bcbd03292ccf804f07f21481260c135b827b +0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d50000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5,0000000000000000000000000000000004aee050b0ea07118d76f835218b77b39854f5ababc4e2a29d7c8cc7c18a69c30bb22437049a051d049c8a84f7868ad40000000000000000000000000000000003b1b809d5046054924c3814d26fd5fbdc59e03e5505813bab73bc212b0f5bc0d3fc34478311c5e1ac70fd16a01c52800000000000000000000000000000000002249a026af0b49f4659eca2c23dc790fb36a7b2996188828a17d5852003f1420f11699062932835cfe6543d454521e30000000000000000000000000000000008217aea2221f8748cd81cd37777605a95a63aba36a6ddad72c1e1ac57b24d79ff9d9c4ed71a6e3ac8a378129d5475ad +00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a,000000000000000000000000000000001274f676bcc05e54fa4b0cce234870ba97a0b1626543d6a9f09afebd5a752769000df404e4d434ebfd561f8335f36d0d0000000000000000000000000000000002877c9438fa319dd1a00f381834e8f3d3cdebf4e1f7690cb82559a2e978bedfd2455be020d0353aa56d435c0174b5b10000000000000000000000000000000009487cc9c7a09be901673cb1bd9a51f45e5d2ed30c90cbdd3e2b294c8f866f68da55533b78152e9ef6de30c345fde5b7000000000000000000000000000000000a3a8d4aabdb260203898655745cb695e6dc90c6e7bf0248784f8aa2340390fd5d8f1c6a98eb1990eb97c2a7f103e3fe +0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f80000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa,000000000000000000000000000000000ef415dfc1e47f39e9632ed21c9c2bfcc1959299710dcd7935a757e3756a42c8f6c627c720fd62f9c486a8e88a64c76d00000000000000000000000000000000088079108fe7d9ac93590c045be0d41396f3204d83793c4e862c5360ddb3268a63f704a9d14323943fc85874cdadaff1000000000000000000000000000000000cce908e8dbb7ec35820f2db5ae1174e0f675b21ae416fc89a7f242df3ee98764022744842999f65132229156d2627370000000000000000000000000000000011e0e2f8513d0a71b48599139a9a29c8eca090c5b02292baba58e07b1d3898fe158cdeb3bbe8edb4a805e695e896984a +0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13,0000000000000000000000000000000007c6f133647745c312695439f1d8c251e941bad6e988cfe324ec7c959a9e0fb50618984429ff1841d4286922a26873170000000000000000000000000000000008edb220f77ed17fa1f4757a42ec66ad808c1acc25c4b9311be4c09703d547f648d9dd7c8109ffa89d01a35c69ec2685000000000000000000000000000000001595cc05b04f557ed569b19d64c09f4d82e6617437571fddd72a672d07ad94bfbaaed906b3a7e3db519159ec8d0a8c4400000000000000000000000000000000041157d4f40bfcef680af0143ccdd0c4bdd25e598a470dae844d887c398bc498edad715fd7383421fc78758cc9b00326 +0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce7300000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b,000000000000000000000000000000000f983607a6d8a5c3b8a577cbd5d81ad2ae936e714199e3f4095cf280b8fd6d3699acf4d2ef251a571dd1ef4ba6d838bc00000000000000000000000000000000048c12f8b95f9537e56479b1bc43a121e4edfb6477fcb090a5ea60c5f4d01071776dd0264b0250902448f62800f4d2ea000000000000000000000000000000001644ba272d7003d0077991ccb4569638de0dcc48fd2e8e9a41cee1d2200aee1a849f2d620f60beeb06b08c31cd4eeacc0000000000000000000000000000000018892d773f7e48247215484ca0c8d996833c43a5291b0380c97607c86f4ab2784e692673a1da012ac4fec2713d156a49 +000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82,000000000000000000000000000000000a06ea8e644d2d762520ad956d41ac2086a588450bc34f6d070b86fdfd73cd0734341a751d823935a009b7517770f86e00000000000000000000000000000000140ef0d6a0482537da7db8d775ac3c4a93b16c15fbe4602b5b1843ce757aada5f7776a74151d0bcf760f7284d4ffe56c000000000000000000000000000000000873c90f56a2b99da2f0a1528b8e376a5912f9cd81a159379ad70b7c10e6ebb7fea0a90d65543d968a34ebd539372e89000000000000000000000000000000000b05ff57079386e4e18e73cbff5f7b0efa329ef7355f083e8be258922203240dbb8926f7d11c22ab4c16d1df4bcbb600 +000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb,000000000000000000000000000000000e9d6f9e83a2584f2cdacc4711085bd251e060f8c87ff7538ce474d663c6f23361c88971c9da589586e754ed69699c820000000000000000000000000000000003fa90cc1dd81b815704e15c0448bd0e8e8d0cd7ad51237a25d4b8a0f78f532b18ec30a108930b7407b7486aad9824de0000000000000000000000000000000000cb97bce1f75b1df5a4b52745014eb632d2d2230e52a9767e3dfd76754e98252ca81ce274b92a2947f6a65fedbaa3e400000000000000000000000000000000090edabb37f411fae1764792083c8c7412fb470833a9f7399fb312c58687d4afbdc622ecf9d74cdfa3ea87382adcdd5f +0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de740000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5,000000000000000000000000000000000cd234fcc729a4206233e46875a557027cb52c96322386b56d6e50d95dd9d23b6f8936ddc6f8475b1076a855c1ae23510000000000000000000000000000000010a774120f607bf9ad2d7bc498536cc9d35cefe384f88a2439a75f1a4f6a9e4b4253daff0d2c91b5915ee0e9a99b4582000000000000000000000000000000001496e7181495114abc0314f580c16038a04a8dab43b5564d518dba5f5e48112ce9daca4b16b6ad51c3af54ec9ce915d20000000000000000000000000000000002c61691a96a2120663c726d7fba3ed37524b58c92a024c15fccc659d1d2cdce077ba233a0d4419a6f237ee4e09abf52 +000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7,00000000000000000000000000000000011cd454f16209b0b7040c744291f2df465ebc786946ce3cde77fe4d4bcc4b60a51573c45b8bb2d209da69107613764b0000000000000000000000000000000018a026f29fc2f81e82015ef8610b4396f2e3514ab1a213356953804d585c5cd6a3c5cffbf70d63d9dfca50129021f0e60000000000000000000000000000000015bdcc8c139e636b05ba7376c1ced4a183eb465df53b1996f4ddc8cbf42cdff4ae2bbc2d24831a8ec8b1134cff4444ee0000000000000000000000000000000017671fc3995babcd2c0a1d2a71c417fea84e29df67fa1096fe6d3ec77c45b64fb8da6ed08a57726ab314fb860899961d +000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb,00000000000000000000000000000000156d8823c37c81d8f03c0b2e61a2342aab6e6c9db36cadc9eb741e085de711e9fda08ca78f21753c4fdd8cec059b6c2800000000000000000000000000000000064d4fc2584c78f1e92f808d4457070b0470eb8de9d558885bba8b03efd8d8e195e4923d8e3382481a0ecee905371ae10000000000000000000000000000000008f1dc4d2ba12e7e3e1b0ef3855df4dbf29468bc99d5cb29fa3058a535af2ba038396bccaa238bba6d538498565c2809000000000000000000000000000000000fc9839b6ee876f7846b5086d487360b8faf133b6f5bd2dbc92a7fe2261b91b15aef8d90c227cd5f8ec05e32d807e022 +00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2,000000000000000000000000000000000d40f1c25dd57e36ed305276d4505cb250d2d9da0d5b954fe5e396b2c17a5399613243216586cedb19340e80f898873800000000000000000000000000000000063367c4a622fc925319fc6d119d8592f40f126ae05eed86ee5e4f6707b1d234c747e698c40f292dcb82ac5fe74ea80c00000000000000000000000000000000199ddbb5d4b6cd0fb9225a72c53f4596cf2597de63da56f4a9a18be8321a982de17367b0f3d794fa799657dd8ca10c5f000000000000000000000000000000000f1ed84e4fd958547d40cd2dbf16e2da4cb6d0d02763441067221890ae27ea1f689c26c900b695464ededf083667146d +000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b2990000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf,000000000000000000000000000000000ee446310185ce76e31c13e4ca6c43166d971d9b9c539c7d0e8dd8ebbbdd9249922cb674bf6ad6840c203a5e208911fc00000000000000000000000000000000037344752896cff03bc39a9d09757a83c15fbd90f8bc1d8d58dca9b23bc00fa2b0f3f0bd7c9ed857d285825d40afde450000000000000000000000000000000003ef77f0220d1caa7538ecaef1ae2924ac1a180f11004034fc118aeac464fe1ce684b5fc90dae3370e3f79619889f3d7000000000000000000000000000000000fdfa434e7bedec071a1a333088d06299f55735f085a1e907a1c71c312bbb8d27ffa7de7ac69d421ebd675c4afd37594 +00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec43329200000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036,0000000000000000000000000000000000ac465de3832452edcead434729be73be90785158617b5ec3ad53b12653e43721eda7de6742dc51d4d4bb58a291999f00000000000000000000000000000000147c39a5c162afa1f8eef400cfa1bdbe5436bc59d93973f50384022962f828ac934a4f88ab7c3d505b0bc3bb002f5efe00000000000000000000000000000000141bcdad53845a7eb2ec08189a55445059dad24ae5d39fedce869791aa28459f05a6cdf9575676cc6f3dd7d6faf077240000000000000000000000000000000010e9f539a9ced860661472f53147d0347927f065ec09bc32e00c5bc157b07f8b41b05aa4e0eedd1f73c7a287b2d0e5ab +00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d700000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e,0000000000000000000000000000000016479eca30f48bfdaba4c8afca63ddbf59fe3367b2d3c17d15a5869dd2956fc67ebde964530926598cdcb62cfc993d32000000000000000000000000000000000650b4fd24ffbb953ccdb1b112799149d29e2377ee233b9ac97f4db432da63c98b8aad751f6060d04fe1f9262b75fca50000000000000000000000000000000004568dc0b9b430596f2fa59291ea6f923d552683ab9ab93000788145cd7c468c5576efd981c9ecee2ee0c16eca1ecdbe00000000000000000000000000000000154af1490463930d6b8261aa1d066eeda6d65b742cb53c65348e5cd766d86982a1489ad191d1b126233f193d24823b9c +000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b60000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee,0000000000000000000000000000000000df692ca763a74877352af3609c8cdbc184eb71bd35fd86334cb88543637b40b3adbb5802dcd7b88f4d722b566aba7700000000000000000000000000000000181495e709d1617f2d912f43487ad3920ac5f8e47395ec4b58bcf0b2d986c674a0c7838830a039bfb5bb59cd2fee2f5c000000000000000000000000000000000d20b482dd8aad583bd5d08ba9c61b3e954f022d48f9f4f62ddc9f5015ac71dab7d206b1d8b885d5e605519bd33d93a20000000000000000000000000000000010d3deccb9364ee386eb35c7117bab373a76d024627b8a031f96465d5f75b029fa992e29ad4a170c4473cd1df585429b +0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38,000000000000000000000000000000000a007c914ed40c7f2719fc70def0d4752cbaa775cedae9365c5afb61a5e1a2854f9e1ce19af9fc85bfbfd2c33f5bf095000000000000000000000000000000000d85b0d173c25c2915fee429d2468a9eae01ba43c0f1a661f2ef83c1acd726865c00c40ccbc3aae306f93074e5e7858e000000000000000000000000000000000b3df302ec532c8100c121c9a3455392c713ec60de1f9572b040b0966f8ffb888e8cd768dcf6d63d4835a52d13a730c0000000000000000000000000000000001123c43dda8717d03fbc02fa53c4b1c9a931db6b274162cfb02ef5eec602bd8161dedc37c7f6217c8e82236f06e49e2e +0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e60000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d,0000000000000000000000000000000016d8d9b1b59a22fd830f88b9850576488f75672a87ccb766e52da77f187a8e66071130c7e71f86675f8379b2a8802c4b000000000000000000000000000000000aa4ca84aa23f01ec536ffa25c4b7a6c822f588bc75a4a72ed9237c0588ab892c8474a0f23afc7ff0dbc3b08f8e35b60000000000000000000000000000000001425e759e2537d9e5f0f356ff1d38128eff3a771fa661a839f7a8d0f548347438574ef7d592cd4273ef9b7269c9c5d7f0000000000000000000000000000000012cf1c67d1ce244ae22eec0bf4a400a0f356b9dd075d87a6e61941933872d7c0e42c1d238b2c1704d2cdb2df75169f39 +00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7,0000000000000000000000000000000017f6e2743cb30fb93816d0dc802c24509315363c3652b0244e1395cb9200efb4d7b9fa7642e8d165d28a00740f1a83be000000000000000000000000000000001483644fffd3989ac98cea71843e87b8e446a3d497630419afe99b3f1729a831fa6a49bf763b0c410cfc5390ac4ac1db0000000000000000000000000000000018ad20ae5012266d771b2c86f891f498c2e90a7df19561be240319edc1fbfb316948fb3f8a6b0e3720676b076eb372e10000000000000000000000000000000012f404211899d8fc1221ab5b82db9042ad37e63348871e5ac6cdbddacda0a564888f89d22712069b6096b58c5935edd2 +00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1,000000000000000000000000000000000d4d78f992f12aefb0e3a6b18fbe2411108327a9befe4a822618fecca4def3169972b4f1fb254cc4656a676529d554ad00000000000000000000000000000000145ef33250240a5c9434d4b2cf2404d9e7cc51b55e482ebc6a8aed85caa21ed00623b3cb2d76ce2d96b2f346d395dfc40000000000000000000000000000000011af2ee2514c58078da335c0273cd18b98d1ac6f0e67890677403f71b0e06863fc72611c0cfba39ac894ae500edbdbae00000000000000000000000000000000186863e7c24cbeb45f7a66b5dddc9b57c7e22c5139aa6bdb82e77cd8182bb8d2fb7bddd7d3516b5422f92e08d02606b5 +0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122,0000000000000000000000000000000002af4a301e90c71eb375110e7fe23f8f05e2ede86b1a9b240e8d1d4d70e96f1dc3640fca7ebbcde9918deb91f3592de600000000000000000000000000000000058b5f36cfb6b0adb14b397dee4c3769c7446426eb5719aef4965cde2dcb70e6f2fa60101a5f03517c0040093453d092000000000000000000000000000000000f77b560469cd42c5cf3458ae13020c6678af3cddf9bc559372d12bc5d6b930795e1eb09f27cfdb8215f39fb2a11b30c0000000000000000000000000000000003308985946c742af7bd7d29abc2517ff1d225607b5f11fc66695cefabd8f25e294ebdb7339949d6bc4d98db19533966 +000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f,00000000000000000000000000000000093c936d57135b25900bd5dd55cd579aa8b85b9c1b5e8dac6196c4450b624734d9bfc3fda499cedf2e877d79f2da650b000000000000000000000000000000001832306d3ac1c1c61bdaa73c9b6e9c2ccb484c3baa1de6a217a2884c72b72618e864f75fcc2dfaca358181ecbd3347980000000000000000000000000000000002b2e5ff1ee02657fa88c7d6f23cd4c0465152a9daad8479b4b68c97930acb22e4e2eb0011ec4062b8ec46991a7cc630000000000000000000000000000000000712543547e9d24cc78d1c2e3fbe0b51222185f4c6e513256d1ee066ba50beee20321bfd60462e2587c375a0e9395715 +00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb,000000000000000000000000000000000f52e2f8dff9a93b2985d5c2b8b980e4869af53ce55aa48bc1c9295e557e3b5ff78896e5e6342c2d535d18b11950bf390000000000000000000000000000000013d36cf2805d350c5b748e639d20e592deb4c5bcde99a94fb539dc56d48a862151b925314f21dce4c9130b32e44f54060000000000000000000000000000000017728f485d881b861f626c9de8b3df7d807b266de6cf8dfcba262f40a6248fb5e6506d11e88f460f0b5f1a1907ae5f3e000000000000000000000000000000000c0ab998f63f861c82106dc3ed5ea11a16e98139e8686f8442047a1cf9ac48c3d34b5129263767830144e9a13d4a1f44 +0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c94360000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba,0000000000000000000000000000000003463d887c4d0aaa21acaa308d77f2c7e13d10157efa9ec3fb1586a8db5ff1a9e807c91c86afc4df34c9fcf06e8561d700000000000000000000000000000000128a81efb9f30ed811ea3163c71b6a46ba2cbdbd3a9f93cb8d0f518747cc860431c6e93bdcdf36d00f83838965da4b50000000000000000000000000000000001777802b7c41111b38da3fd8092c280b4925827b2c1592f779a4ddca71f8268858855c413fd5c0057a652155261d75ba000000000000000000000000000000000c88b522d6dc2000cfbb7052e141ddfe15c6cd7fddc970edc4afc36fc59e7f8e31415706a8121e8e84348be0b50d0d88 +00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c2900000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4,0000000000000000000000000000000014be96cfc0dbe09155ac8d8233b71ed584153e279b2b2be88471eb653aa4913fd2c33947547c61f7fd8bedbb552a8b1b00000000000000000000000000000000146b9a0011260e2646920894cf405bdebb101db12da7849b30868655fb5f972113cdf2fc322cc246d3dbd9f20b98fe2f00000000000000000000000000000000104bc20e104da5173dcff3e195f80960819a0d64e922bb484c2739c4b7c22535f7faeb1c85188aa853277740b389eac90000000000000000000000000000000019f5aec599f9ec286aefe48eedca3f929ac6c758c231182b92dc965d6ac1f3db53d93f57d733ca8425a5dde070b0dfa8 +000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa10000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79,0000000000000000000000000000000004ca0149527817b4df0f08acabd4e8c6329c0d1bd9f2e8211cbea25d69b84009ef158c770f948fd67e4609ccadc938680000000000000000000000000000000004101b351e2a9d34042291f38a289d8575872104bcf76f60bf888c60cca5101c34c247da30f7a8db4f0cf2f32abd302c00000000000000000000000000000000167e668de3207ddc60b8a5d5d246bf2f63ceae3bcbc4309e73eebf4d4234c2785bb13e4d5d8fff9c5f205e4fb942a2f6000000000000000000000000000000000491b965ed005065abdac53e3065781f2fd23f6159debc64f01c9f62073c651da33c05ed84617efcb5ffe08ce05e3b2c +0000000000000000000000000000000003f2dd27e3f0ab503a8752c0802ee14c655271e8cfbc734905b4331fb4e70cdfe291ff71053fbaf91680b1dd108f458f000000000000000000000000000000000c62014b7694a3e81370761e0adcc32430547a1bbe33746637e7762dc24f8d04b4bb955f17ca901659482c622d777642,000000000000000000000000000000001541320fb6f8a8c3c67278a7ad05ae7927d3555ad562bc8addb54c6693c51fb1c7355d2e74ff10f6bc3eb182d8f5b88b00000000000000000000000000000000172b65b110935b116ee683c8680ef0a660afdee43b9b8fce08ef3a70b352f8710c06b820348c338fb903a165cc5376da000000000000000000000000000000000df529b0e274e2e8993dd89ffef487aff23d31f502a19dd7d383de08fc77f1308a59ac5bf7cc899e81d377b2422187850000000000000000000000000000000010b40c9063d174b358637ab710d15c80d9230a1b3a056cfac4d583ad8c5b79c3d9bf22a1b0a4e0f629cd09ff7586f886 +0000000000000000000000000000000014d1491a45b4b0914a6cb2e4dc7de9d0962f5c175cd571057cae1e17d2c943954d119690ea14f5815f858d277a9ad828000000000000000000000000000000001650771e0f7b33d235f229b7d49a7a5a0f00f78e5f4abaa70f39ec452370198a8532b5873e41f17c449f9c565e6adea5,000000000000000000000000000000000978ff68d94d33703488298658cf2c1b6034d3d8d21c175d71a0545bc2f99eaaf131f061f3e4f55622668e686e691f53000000000000000000000000000000001124804b252f8187178435761897d00c43cf67b588ca69f97c20b0ffad3ed94acc2c0f85f900713dd6ee9f38e5ca94490000000000000000000000000000000010ca2a8ce71b9a096c132c4a060a17365475b6556d4fc6284266ae787e217b3ceaa3a32bdf751375eaf6ab49800132fd000000000000000000000000000000000a43b435b116d9480497f6b2e1bb377550cb1a7ad59e4214bffacd517afc6b7bf91112fe57b17a02a86876ea07361bca +000000000000000000000000000000000aeb244909654b3e1df7cbeccf297223be57c2f514474edf0740dff48dcd5898b6e49eb65c787aa56ef79778249f4e07000000000000000000000000000000001007c89a66dab07f54313db8682f9e829baea229b030b4514d9c93686747207939c50a198e83ac2cf50315e02642a24f,000000000000000000000000000000000c3d87b1b78fab65cfc853304c682b39b6ec2b4ed005e9108f69daee5aecbd586c9818c37cdee865ba53eab9302320ce00000000000000000000000000000000062a7203cd2fd04a957cac8b6b6bb51e635ed7165c547ace10f93a32b7f37747a2e63d5767d966684409a6c748d4ee6c000000000000000000000000000000000526b44af8157dd68725aa8743684e020c1e385af7413c9dcebb320568663d18b6f29edea26f2628358852b794ffcc8e00000000000000000000000000000000098126f486ff55c21f64421e85b09a1b54f42d3499dc0e198db6f3bf7dd8476cad97c02b5b366e5ea20d8f83cc223f7c +000000000000000000000000000000000398d86b5206bae4ceef0bcc6335b1f6bf5d17863ef3a5e8463aaa69d9f73f8227263964659d4b770d6d9813f9399b9d00000000000000000000000000000000096bd18be1176e16a0d80e60f7d7ec9d3b6162f683440e3cde70082a73605da3783c8a058bf76d7e25056f5cd95c31ed,000000000000000000000000000000000f3e76e7d1cadfaad08d16457b02d89c40c157225eec7916d306faca8dbda008f41792888c647dff1acb4d4ba3b43c4900000000000000000000000000000000132bf730456e2afe745a58cdee689e37223292bf682d5b7dafa7df99e40d385559d0b3161bdda0bf5173c43ee46412dd00000000000000000000000000000000141b36ff6890e35db0054358bc0731b3aa0efac1a247a51daeff3515746456216975f44769174a4be41c109d35e4be33000000000000000000000000000000000ca401ee1addff8fe87b600e057ae34ba297886f92c5be8a8c00b360ada71831e31bc4ea1c309c7da31cb28d1011ecad +0000000000000000000000000000000004ca5cb60c32edfa385baa911ccb7fd1f383824c22b945944b0f3f7011db8c123efd8fa70e4fe699d40c6716021f0151000000000000000000000000000000001339adb0dd8d83574c2008f0a7ed001b0808d2fb639b5e57e1d293884247d5c66c948ecc60caeea7bf440a3a44ed296d,0000000000000000000000000000000009d0af77517b654ad97de3ee1dbf69ec1eee901facd0f8c39b4af393d0e63957292a7529b461f7fa58909acad32ba3a2000000000000000000000000000000000fda17cd878ec0f8c294daec1bd1d56c63e875b002a81c9c41146dbb564bab6e4eae2717c9fd718af1ba816a1526e8fa0000000000000000000000000000000017563b7ff22b50b6d9e24b1e0d89ca5c72e68d4d3cc24cce36856191111d087c3dfb392070462dc7850ef5a1422931c600000000000000000000000000000000020001fcff638504055ba35230b360e6d3cb5777b959c194d6f9b038b58d3ead0b82b28bb215378abd85d357b85ea260 +00000000000000000000000000000000089211892a61202b1ad3a85aab9f08f8d028f3e3deb16c1de4d62c1a403fa63c6dbbdf8cec37f0a9d6f346b1c7ee179d0000000000000000000000000000000012a9fc2070b326f4d7e64804b3a2e977f4bb36b6a4afcf27252af757d8535e8172a99dc909fad5a3ff8df23d6d6c5948,0000000000000000000000000000000000d51c77c2443f00d965c0d7ec9b5a8a8003c2a77b0ffce3e47bcb55420e8690a9c2ba9235b62a4b351d79d216a3aad40000000000000000000000000000000013cd46e3ee6cbb3bfb771ee30b5f5faf0a64a9efa1f8fc57024c83ad07a9b25e513f211ea604cfdf319dc42bf4c067d300000000000000000000000000000000009fbe1fffc67220067c948e0c80de23795e045fbe8031c9010eaa69356ffd8e5741cfe12731ec13aa236630f1b1dab4000000000000000000000000000000000e5ecdf808d10d47f041e4b078e79b32520ce9623b50059a3bd8b59daebf9103c31425659ecbaebfb2384d1c2f1b400d +000000000000000000000000000000000b37365748fdb21fcb46f94edf86c586f17d0e042c4683e68c6cb83e7c0ed2c30ed260c15af2c9dce77bb705debfa7590000000000000000000000000000000010d7c02c6c1ba3cf6ac09a05dfe043905e1a7eb32b67f2e8a5dfe82eaca66ef46cce43aaadeff58ca85345dd0d3bf3cb,000000000000000000000000000000000f3e4d2559261829c0f4816f8b571170de1f74d75d74997cba56fdad42932db73504691f9e001f5b4604705a8c1a38e40000000000000000000000000000000018c72136bc7d3050ee693270668e706ebf70f990e447ecc6153a10625cccc9deaf5ae82d2a656b1376bf33b1c1fdc2c9000000000000000000000000000000001754f2725bfa76e92a74ad5b520ec2aa82a1f86e8623a054ebba489adfc9e71d1f14d4692ff9fdd8acc3d768b67e1b7000000000000000000000000000000000096f1373434a8822569cba0679dbd2abf619bd9a8c73e54e078688d4e2615d45431ac8cf3da5e15a83fe77d14b339e49 +000000000000000000000000000000000aeee59421c8ee65f8070b9d036e6bacb39dd2537d02960a3a57da4f0985cc7b27784d60fc1613f5a83c34d2250395c1000000000000000000000000000000001715ddcbaed0a05b38b1c820724405a713cc0215a4c497892f00746c0f9af28b440a3686178d9bfcd41944a224311306,0000000000000000000000000000000018d515b8c99f541c7dd448c3564c1909b84517b662d6a2d1176d3bf5e70abc0a2995c73ae3f1614bfed2f64229e173e80000000000000000000000000000000012126ab671420933cc4fa9206311200cc5241ca3eec54f5d97a426a72642bdde32a65c79735446779cd1744d112d544100000000000000000000000000000000190d836312ffb0d6bf493f4c942263922659abec46ac4de639efc311753148b445509f808c2fd813729b1bd96e0e663f0000000000000000000000000000000006494f9a451460ac658ec17710bef79d59b6e0fca049804c0954c5fc472bbef520f75d34408ccc62cf2da3deeb79acc2 +000000000000000000000000000000000ca4b3e1a8351057ba4a2ffaf0cdf1c3c0717ccfe26433f6c40e2cc29e32ed884f63d25979580fb555a5a86c9147bcb00000000000000000000000000000000010c1db593af38aa14ca9dd588f54b219ff1fc9edd25b3d16c595662ffa7939879244326b14d978e0dfdd25e37776964c,00000000000000000000000000000000173fa567aa952bfaa9a60b8232a185475cbb36761ebef49ea5fce900a06043d0e2c1b6024e40eadc9f4bf04b077201450000000000000000000000000000000010fdc32ff84f79fe39351cee1ed6b67dbcf2956020e2518d5bb5b367b61f86f1bce36f75516d9551d74cc3a567e6c2be0000000000000000000000000000000007abdff8a8967eccc4de6b4ce142173841c0e8399f5a67dcf0f7b5e5b4133391b44bf4d41d3ae3426839b19aa4c5d40c000000000000000000000000000000000c99f160062566418c09f10eb80f005f2c8c12825435f354f1d65bec0322e9b8ee968c009a84ba792a7ee7334b32bb3d +0000000000000000000000000000000017cd94e7e672f0dba9a3c1db742d87cb18b9401e8311c4badc24f811a8f40c27942da6485807701c1a12da58076c756b0000000000000000000000000000000012f6de4ac9883e78f9d658cede4c70b44bac6b4c9734cbf24298ddf0df0cf54164aca245d8e313be4aca66ba3cab5d70,0000000000000000000000000000000019dc92f1da66d0855ebc8e7a2ddec623a2f843a97c7385364a631671be7ee3387a0f98940b5a51c8d9e23eb27e3133b00000000000000000000000000000000008493903c5c68b2847869b8c3b0fa9b8ba15bf1f11a40a29e6e82942e2910901044254cc8e8c3c3bf56e1f1b6dab7e86000000000000000000000000000000000bd3c1e302a191094059a6493e59a11ab05a49faf333f36f7680ec9b1043e59dfd7f0fabe9f334b97cd638dbb8bb664b00000000000000000000000000000000141c9b07ff33b6ab55b320dda6be54320082f0057c446236cf3d3a51e674c26a5241f2c702d9989adbae9045942eeab6 +0000000000000000000000000000000001b2843d9852feae3145b242cd0999877c07785bc72cc2626f388dca32edfb112bb90f9aefd6953eb15a0babe748573d000000000000000000000000000000000a69bfe809a67ee853cb96b5a7a72798748cda56936e5664d509001544539730f57a7541ecd2396c9225818b9dbfa3c6,000000000000000000000000000000000d0922466c358cfd756727e134b5e64d211244587e4eea036f0959e78570dce3ee264c703cc356cde20637c7560369340000000000000000000000000000000011a66d618f79fb662ac2b2d3b50750a5567e36d7092dfcc72d8f340c04df75ecc0ce4a01b410ea775dc548b8dc66c3d8000000000000000000000000000000000cc49cf4be5e2df6b43054092afa2d6acd66f5a43ef0667f6a2d660beb7fec70558ce02d7acbcd090df91fe833326718000000000000000000000000000000001270b0519db083f903a3dbe0b1b1bd5ce0b0059ea2c2c50335dd80b4bf154fc23a3de1ea753b0e279145254d8e5bd045 +0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc7,0000000000000000000000000000000007826398b4ec35ab58ba9fda5c15ada2a41d3854677172ef6a4a54087b64d0f73fc875ad62236eb7fdcbd94f14c8895b0000000000000000000000000000000016b14fa92de5f6e43988829ea2f851746efd6680b0ea1283264f803c8ffbe85a343bdd42225caefd1b94b8b311d2f4950000000000000000000000000000000018797093ff82bc10e6db60b1da50b9a60da01d67673e9bee8c7af2bfa2d57f409f7b06f53944938e5c73b049c2d3c6500000000000000000000000000000000000c66dcc3d30f35c21b8a9369c8f6de28af404e8b30d3c9a7f09c461b0272ba6d5a29e716012536dbeac1d9672af8427 +000000000000000000000000000000000e6fcc48312831b910e52aebbf19869b3b32f05db8310b68905bb244ab784df5732db2e72183de5d231d803d92aacff9000000000000000000000000000000000f61f9e52fe3afc2a6bf12e420cebf83bc55a449d6a167779e5b6ba3281c51d790a045643aa75f2516eaf6ae2a816ac4,00000000000000000000000000000000191aacce60a1a83f2c453fe196bbe5839a3a1178b147580435f7de8a2b0b4f65b3e280ac7a67570aba0fdbce6c11ad9700000000000000000000000000000000075ddd6b256f53a6ae6758a5158508540aa99b78ca069378f0ae3f5621ec24b9acff1f9b61d378334a63682a33fb0561000000000000000000000000000000000b06e11c9f858446fcc90c69d05cc26c33bafed0feda19adbd838c9c24bbf567b673110a1b248d0ee97fc682e561298e0000000000000000000000000000000018c75dc203493e12e1523af50f85ed648130ce5d3e9757f713850c867cc95c7acbb66c9733dc4f53d6a0e64bfaad5832 +0000000000000000000000000000000018efc6d366d79a09b7d56c81c17c2eec2ef7395fdb5991f41273329cdcf4537d342bddd83c3994a40d5c18f6afa054c600000000000000000000000000000000127021ce28627a9d6a492720f728acef3b38c12b951f70a932c7fc0ce3f5b6c80783351cec55d7d1bc4ab964bb4913b2,0000000000000000000000000000000012931f51430bea6e96f8ec456ce6b3c9e058b0bd3bbfbfe8b6e84fd6110c3bbbe0001018064e8981797f9c93713a0e4400000000000000000000000000000000196b6093dd2276098853ef2bfac84f0cad06b67a12484e98915dcc756310b818d8136954de1b602eb825ab29a143cf4b0000000000000000000000000000000008284beaa877b25374571dccb218c401cd905b351dd96700853f01920e409d11c4e440e90dc175cdf0fa807cb9d1e93a00000000000000000000000000000000063c6c238485c291fbb60bd2824154a9e23dea374292966d271ae94875391b7ceeee813e3fb9504223bb86f0ea3b6cb4 +000000000000000000000000000000000a0277228ab4e880c12f957a6fcdfe49e2155083f3f93d3f00c68622415cd1f5bae183b7df9e08328a8139392772cdc6000000000000000000000000000000000de0ab426e56029790a5ff72f34da97e11c028dc5d31e448c49ede004102804d2bcc36d509640247a4c8bfdf5104a781,0000000000000000000000000000000000f7bd0705cc4ea96ca38314cb85963044164b83a506ffeaea6e5eb8f7c4967cab1f1658f33b5435191427aaf9605bbb0000000000000000000000000000000007a93e2a5c118aff6ceaf2370ddad52a82854946ae595d384ee0b2b4935a574ba758736d84b0ae792f998ec6a707dfbe00000000000000000000000000000000090936add00fe5c7556610b28ecb4466ffc37b95b5cab43e072a585920b3cbe70faad01ef75d1dcb4f7d00d900bd99600000000000000000000000000000000006ae82539c68b7af3143e23229fe320924472c2b3e15a2e27e94cba674d30f083dce94706da094435c53285a43f89e56 +00000000000000000000000000000000170b243c5aa49a0134bf3d6494cc1e55a1c6ebefc6117eca3b343a48ef0a2b077c443ec5b9201b198df015a38e66b7910000000000000000000000000000000019a8ac8a3be1d45318801bb0a654963b312540d24aafec46bb7661cebeec27b0b345275fd53c041d02b1ebfa27fc3854,00000000000000000000000000000000024c1b869fc13191b71d7159a07e869f1b13c11c73231b82e9bd0a7b4c32d7b376fb73d54f7231dd4974713179f235140000000000000000000000000000000012b9f95af661e8452aa5026302a7c28695307f75e9e4e32365caf378ed394fcecc831a3c47b443172188f4d18338fa75000000000000000000000000000000000f52675fb4d112d1d39ff953a253b22dfa0b73d972e756ea7fb673bf87aa992883c5baf32be6f50f880b03dcb740f06c0000000000000000000000000000000008b57726e17c873e12834dc291cff6bd95307f50e7b1d0caebd8c1eeb6eff4acc0520b135bc8e35a257133b7dc640db2 +0000000000000000000000000000000000fbbd5a10eeb2f358f2b167f1985d4084c4b12accb1520d780ef1c52f6fa80e97aaf190e7a7b241ef96fe8289fc0a9600000000000000000000000000000000155687114e7aa786ba27aeada830fc705aed069c4e3a07e88d7f33923319f416ff3caf6533cbb36e5bbb1b93a191bfd0,00000000000000000000000000000000061938df3365bf910884ccbd74d3cea7c30416bddc1a9b65e7723c15d89aa657da36a45fe10ed50bfa0c2769bb98aa2b0000000000000000000000000000000007b3981054255715826cf8f247210521ac681305aad3928b69804117fc143c5101383eab7017127c8452a79003a857d60000000000000000000000000000000004c745113480fd87212ed3ff30ba43c8716b32e62c1f0091bde53bd4a8fa8fe6bbcf0904144f4791ed1bf12dffa1f17a000000000000000000000000000000001237ba297c7f69e5e240846a12d86c8276a9a6ceb4af977edadc7ebfba3ad3f4ecc0b875da0ea578c83fc3b91f9f31a5 +00000000000000000000000000000000115edef357ccc3432226d9bad796a42b1a278d9c8adfdddc5a0f8a36d32ea1787183877f8b7dfab71424cdd10b63441a0000000000000000000000000000000014b369ce61abe60d942346e049644b95a0fda96316446b2fe7ee427641af52fdd2a654bf125ff6c8c7f3dec98f6cbfb9,000000000000000000000000000000000a0cc3e328b4cfd01afe53dbf971ad78fc74d951050d76210e4c84438109622f0531747e762e185e3d7ecb9faa7c3255000000000000000000000000000000000622ad6092caa727d069b8921f4124d5996f3019705a908ef95d23092c5bb148873a22c227aa25ebee361d4184cc38a10000000000000000000000000000000002938d2ff50cffaab8c056c2844c50013f5bcdbb4f91b3f823836edabb39ba17ed1b8b5862301efad04bd2f5d5bf599b00000000000000000000000000000000072e96136afebbf8c06a37cf9b20c85ef8cb3f7f99d5c71b05a187c193711e5b76f52863c7ef080a1b64b2120ab2ed84 +000000000000000000000000000000000d22b7b36ac66b10adb4570f8e7521ed76de2df2a7b94b2d0b9ee4514cdff6fa7c74854d16e7e70f054a91df93c7ebaf0000000000000000000000000000000016867c9cba66dd9f1d0332d31c4e46f8e393eeeeb19af7e6e01effb29ad999b3086b599ee4b371de557d4fafd5da3794,00000000000000000000000000000000142ceeefa9fceb903b25d4dc68f6755833d7529752db0f125f7f65f2b7aeea8c90e599ac409576e82f7b9d6f83c43aa0000000000000000000000000000000001664acd89b482aed04ef40bd4d1ff9f39c80d7738771e2b3ca731af01aa230d865869cb05d83992e94ad99549fd0b8550000000000000000000000000000000013d6ace9b492c014d9a7504b5abe442e3bba13b1ada454aa53177990ec44f616e091f1382d36db87b7e794c11570a9bf00000000000000000000000000000000081b7a8a2906435f8a9242f573225ea62c5429e903bebda9fe9973a18ed2682185d72aaa6584b9848d1cc45ac907dd27 +000000000000000000000000000000000db9258e1257e659e60bf8569ea90c8247a53a1d1eb958481623447a38d0f1f1686c3e40c8f15bd06cf5be9c02485452000000000000000000000000000000000517c87f3df032ff08d960f524939e66f7fa69b4168b0f2507baf7d7231a70dc5690a02d317b26f219365ac3255bee78,000000000000000000000000000000001182e4230f0c360c07913349f89f8436c01841c9615348a0d7057336c7483342024b0369ae52f39d4582f9885f552b5d000000000000000000000000000000000d15433ed130163a85f8ba87468c906aba88ef8610fcc1a8d6b3308cda29907acca351fd7fb19799184f1ad91c751b5e00000000000000000000000000000000111089005c4c5370863b0ea6b629197a865f978f71becb741f50f9b4e49b13162ca63c29aa26287faa9c923f57f4ad4c000000000000000000000000000000000dce405ed2a79ad433123105ad01a26ee85d1ba4e5f3b4e0339fea787058c06e9a6b10f5ec8f6eeb85b211e18b6ea076 +0000000000000000000000000000000000b6573c743989fc8613d4ea09c2e500ce965b50cf0c8975ff703116464082efff4b42828c8337809f6938d7cdd3f66e000000000000000000000000000000000896d316629c80ce6e5f240535863b9e064728665c0815f39b21675c236f6995e7dfff1e4aec9ad05861e2122469ea56,000000000000000000000000000000001694cb615d2994a903a13645ad44a63395320f286503902b6009e7c795dc8f024260e0c45bedd864edc9fcb9d1ca6bc1000000000000000000000000000000000f20538af015bd6d213f90fb1a1ebde4d9e2ab2defaf80d791a1f70af2ca7ea1598d43e9eef1cc982f468cf15d223c9d00000000000000000000000000000000046c62bec4c6876a67f5fe68107d677db8fa4d59ac0cb7afe6e706864c6e94744bedac6b34a68e8ebf89c231307b86d3000000000000000000000000000000001839f3b8a6dd8fe8028247670fe5b491bb43ea8fda53116dca87f97da96573a5e701a703fb5fa7bca457ef88a827e061 +0000000000000000000000000000000011fd2ccf6883b78fe19cfe7beded503cdbe1cd5dc9ee452aa6b329d2237c2529df6774334b132cfeaa616f20335f88680000000000000000000000000000000009eacceef036ec500e7676f54af703016fac93d69ed19c8943b64ffed2db498b19cd255a0a3437b568eade0f497c7b17,0000000000000000000000000000000009d8725eb8757828a94969ebf40545a62835686897d4504a66484a3078b2f15e39fe918d8dc01bc7560dcb005a7a0dbb000000000000000000000000000000000954a6cc9b2dedca1cf280f72fd0625184b8f83b78ee1ffcaf0f9178ce97900d759e4a74b914c3ddc32f84c3f4c3a8d60000000000000000000000000000000014121b83d2a06390ce7359e570e1593d5ff097cb0e44c38bc74171fbd8a8da0dfffcc2bcb95fb2d80a55933f696a86cb0000000000000000000000000000000016f71d24256de70618a02b0f016c6f31a21d2cc42855886ba30176584a028c2e12367be19b834bf41356cdab21223314 +0000000000000000000000000000000004a851380536054f6b69ef7581b57dfd753d1e6201069bd1218ae5766aada087b4b08f65d43b3ce0215640e8d34633310000000000000000000000000000000013579671b64f2d9a2c3ac2737cf95c2148acce3dcecb3db6d019730010c50d1c0504ba4ed42d93771ba296b0b07487d7,000000000000000000000000000000000cd47f0982904ccaf4f3cdaa37091a08e67a5f04af09033b864631300bb6c2aacbad105eca6ddf68a643976fb555d3d80000000000000000000000000000000012332ddb0e91f0ef9e085f21634c6d69576e60d3d24732a0c91a560906791f60f79d09ac0ebf448bd39f047b1dd428450000000000000000000000000000000000a756a869b3cbc5624f0e08019170beda35fd2642a79108b284a503942f8267b75868636302e5a12b4f1505331b15f9000000000000000000000000000000000f60724f6c8200edff41f3299ca003e9ea03b97b01a3e8c63763bdf67b9f7677331a7144915312458c40d041be97b3c8 +00000000000000000000000000000000021dc1dedded9b0dd90afa9ab7fa8f9c33930fe4ae68185ea4cce9ed97ce4cc9ff93f96377b11f8d42b02e759a10b06200000000000000000000000000000000034c963fda3bb80043d6d7887661ad59b3c31c88c958b451a8e11684770c132205db6655ad7cbd604ecc3225b0c128b0,00000000000000000000000000000000095cd509e53f10b1ee18b2120e2d18f0905a202a992a9c62480beb6588275fc8b5b151e6abf17a12b6d9cd03a8b37a59000000000000000000000000000000001723bf1a3d79935eb4b39f7feaa1e05cd8f3e7a32e2c406625053d8d8fde33eefec231ee00adb00b0acac16a83dc77fb0000000000000000000000000000000004af528e886dad3f9fa7232605936bc22a6a22622828367791920ec9d31cdb2f290e37f5fc79efaeaf96c86b3f6e39220000000000000000000000000000000015bada14a84fdb09b77397cd2e27836f9f88854924af0cafc6f9125d32be848c8325a3eee1a26de8be8eb80b601f1ad5 +0000000000000000000000000000000003e8d1be04f8dbe5c7e1c7553cde8355ae16d26c819dea92fb543cbd9fe9e726359e1e4be0483a7720343f34c6a3fb9200000000000000000000000000000000062bc5fdae812802bdea09e4130c3d9bf80c7518138b116a4b6a302c155b97226a6ccc8a3ace18744e7adece08781f72,000000000000000000000000000000000d8f14042f36bb377655b63dbc37c50e0eb5775d4e4399972a6758cdfa9751cb4b733745ed1a47fe5f2cc434efc5af81000000000000000000000000000000001384016829d028f823e6d062898c042a461bca13ae4627c983d9b5c9e8b4ffff7eb25daa1c52b39e309b9c1e7e4f2e920000000000000000000000000000000004f7904d491a0c2018b1361a9cfec4fc829e607402859fd9b9ded60adcee51e9b522d302f9064130a4eed1327f49bb4f000000000000000000000000000000000ef4fe949fca569b31fc57ae7d0166ea53318c5712311076e052c2967144116f5490fdf56f26adf64aa01beb4f6cd214 +00000000000000000000000000000000014b922157b19ed9debd9ae95cd9435f7980b8d4ea33fd29f99d5e7fb1a96f6d99ae13f7f86239c4bc286c3927d3522a000000000000000000000000000000000f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1,000000000000000000000000000000000f20b3a6505784681331208b573d3a241706692db71b5daf4e9c80adb1fa9bb87023d7ba7f9c65158653c735dee9dfdd000000000000000000000000000000000f7f357407ca6cc5c5fae4b84509d71b2f4de9af226cb4038b4820c0541d4999b7396608efd2f322a00a768129f9800400000000000000000000000000000000138dcc1b9d978adb5eee6356980cec5d18cfbfbf18cf6fd14f4119a563f473f5027af06342e84ea858223ed63d1a16af00000000000000000000000000000000012b63f0d2e8ea361d55aa617a99e066b5feef3af1930b83d2a48b527e0ef304ceadf7cba1415db80c54fdcbbcf66d14 +0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a4d00000000000000000000000000000000051c6f0ce621e8e27e5017628690fb68f0fea27d67726a0a77b0caf9f524936e123ff096168ff2079b9990c07fa80354,0000000000000000000000000000000015ff2aa94f802d8f9c60ddcb43aee598239cf3ab7f90f8289a487b673f6065f8d9bc92bd4cd28df4a7b0d3bb78fad243000000000000000000000000000000000884b5d4ca3c8abea737cfca05878528890b6cee9bbac0bf027df5d4e0add431829caddf4c1e001818581ce08686eeed0000000000000000000000000000000019b91a7738fde9760240b335457955e963030848e85717858f22dc33ba5a4721156cfdd7341aa86d10d268e2fc9a1d26000000000000000000000000000000000af85e60161795906f3cf705f5e8cb8c15083a90836eac78445c6bc27ffbfc8c2df3009b436989b46b271dd8d1dbc282 +00000000000000000000000000000000094e958d9b7dac39fa4f0143a333b2ccee09046cd23e6a1c0712470a3c2e61d2f8b85aeca37350f71d7ec75aea2b3b6b00000000000000000000000000000000080743cdb5e359e8b8ad3485d53ea286669ad66d841945296edf80dde77b20a158e2c1529dfc33a1fbecf177d75a0c69,0000000000000000000000000000000001bd1fe6a6c373cfdc2bfd488b0c942492b77d55b2560824edef3a91c711ee336bc1366690be40949d04edd39ad48a7500000000000000000000000000000000161476946a5687113c74a34284f49b0658e323fae57aba88b039eae584d6ef28adca669fb083a2fe8f0ef664eb5b957d0000000000000000000000000000000007aead870ae09a04cf9c9fa49d0888f7010782cdc5a0ade4c1340ff15d99cb39b7412d66d4147b95601fcf5a39c39bca00000000000000000000000000000000095cce83dbfec12973e27627bfb2d93fa9a027a2c2af4259a0879d6bda055d74559fc93fb3b4f6b0088f702af29a7643 +000000000000000000000000000000000dec04526dbf7666d2c29db5de1ef0b3da85380a171d871a57ae3df364d2754fceabf9d4d2a3da1ecd94e377abc78430000000000000000000000000000000000d19875fe988ffbd0cf1e9bfefc7019579962ffa3a585ee232615e4a5fce0a07bce0537b203ea00010a90ec05d5b8de7,00000000000000000000000000000000133cdf684c3ff1cdaf07ff787b57a66c215eef06acc2aec4d726a086480e7b2a5dead2cb357d99e298df32d4c6f5029b0000000000000000000000000000000019cd65b830fb17880f40e104ed63a7d49b0fbad8eead7502f18f1b9f85f3f6ba6c275b8a242effc61a7a5d770a4fdaa700000000000000000000000000000000039aeacd163862e476b17a22c76042d7896a04f158489ae71afdd35d27106a3ec276baf5c08e3eed4b3f0a79c3c458d200000000000000000000000000000000125a9bd770c1fea2155a581211bd71d55eb1966645cc892a05d32cf1e4e5b23278ea2fb1336bba7f2c887debe4a93b52 +00000000000000000000000000000000016dd03f0df71b183e42cc29c665f18d57637b65f05df85aed9a0f7b8aa37f7913f6606c10f24a7a8c570f485905583a00000000000000000000000000000000161e62d8be678a114fd4a99a6caeb9481f5eaef145571152fe8a6ed77a34c06a1b6ff66044102d19a94abcaaeb254e73,0000000000000000000000000000000007843268081f61ad2b3f6653336a99086381bb4da4c23b7d59b9c7827f2d4c196d136508c8a1f3d2f939e8c9799b95e10000000000000000000000000000000000e2c57ad95f762115d8230320810a4ea9978e26ca17decd6af4c112789608967a92fafe3fb3e79539d75d1c0bae97740000000000000000000000000000000010951c9839db9dd6ca5ef95bd1b1b9cf60bfd97cf88129fca23b24f19c9d5c71486dffb762e92f23d2a9e9d462556f620000000000000000000000000000000013d35c17b3763fc5db46ac8c44aef996f3f876c49f5278b7c97e844f23ac49f2d50b3af080322d30ead873af7b4257e1 +00000000000000000000000000000000036efffcb0c6f42109bf9b8b7421e32fa3f855373345341e6000eccaca135ef3b2e1c0151bddbd46ae92185acb847d74000000000000000000000000000000000edbd7a40f3e688eaff5e29800159b8d799df07e81f65d59011e84329b4689a28a15ce11537fb560df705be26bf14b1e,0000000000000000000000000000000001aa1919a50b5bad62b839d672d5a11ad345fcc61f75eccc42990e113deb8a486423d1b27e7c81536d8a5799986b9408000000000000000000000000000000001879295d2f7bb3923ec61c063ee4f96d7d7cf7786259e2f4cbc3ccffe7e114af264b3527a5e06dcfad50ec1e2a9c1ae0000000000000000000000000000000001042632662e406c95f3fd44a6d956e526907147e7e6d4219c1c4b28a31e479974d00d4ad6e683f6a834d3d4a20830f4b000000000000000000000000000000000a29ea98ec25e7827bcb349ccdb2a57926809f3cce44d5ff6cd636460278c8103b0db78fa580e9edd4ecd0bdb21018ff +000000000000000000000000000000000974c7d17cbf91947ad435b30ad2b639671a43d67da6a4edc7f8bdc11fe817d4d42f687dd642a2be89c81bc36e8df592000000000000000000000000000000000efeeb85860877abdabae35672a77ca9d2cf0ed18ed209fb905b591a841c900ed06d2c32c56bed5f7efd469d369b05b8,000000000000000000000000000000000c67498c6751cc27d871b8711c4739398c501a5bfb688d7e1a73dc7db5c47c3e28b633078cb83745bf5b0d5d2dde3ce2000000000000000000000000000000000c205c03305422bd44082715b90e0a0ec178003d6f5e14a0d13bb0f2c38f2270816b884b4870b75db44ab080f88a35e2000000000000000000000000000000000257f378935772d326710ec6efeb22f8c9b6b549c8a4c0205b75740047d750d73da4e71aaa8ff33b9bd8ab7621b08e62000000000000000000000000000000000c386a15f09c849be9f449a59e1332a1e7f16a9394c8de198c01399a05b0f963921c4c57d49916407ae0d202af8da32a +0000000000000000000000000000000015333364f4d0d173ef35e447fc189b9d65ef71b9fc4ecba25fb6c8c1bfe8467f26bb9c55ef10bb34125d714b94aa1df1000000000000000000000000000000000cbba9d8ac191032f03c0746f13108962130c9e2c01d47f01174a4c4d3daa7631268f7dcc08dfda317bd249fb6e73e8a,000000000000000000000000000000000864da537fd94a9ff1bdae733f01e145dc97a894733d0811cd67c2648ba61d0b187241f9ec69d8c011f514894a05a608000000000000000000000000000000000a53ea4ff9c0ff71541ee21127a33daff2b39e74301946a86e51dc7834717e7d8784cf92fa5845bc0613b6b869003f58000000000000000000000000000000000582f5a1fcef3067dfcdfabc6af33871114538abcb02fcad761cb496020c7b423fc52f0075916f160fbe03574df97ea4000000000000000000000000000000001244ede8ba0dc09aacdc5d9f886e59bf963a25885dbbe2c3d1f611bfae82debc556ec4c94f0606492c7b8c7bf976ec34 +000000000000000000000000000000000781e980c167c982c2fc8d0baa3907bc5499eafca675ae20a10b25063c9088fd06f6769df505e5900bcaf99e266c052c00000000000000000000000000000000183c12798438ea92db75d5bf76cf29d320fab3653e4131989205f2817aebcb1b13f161864c084fd13a11459d7d5ccd92,0000000000000000000000000000000016c334aec0e19934665596f0ae37eb398f1d6f0d0c9f08189f1ccc219230395124a9da03858bdba13ec5366da54228af000000000000000000000000000000000b156ea34ae7b5c252dd90997f1c693773a463c26935a69bcc0599b95bde9e6aa31649c48b6ee4ec1f0a56b19273a5170000000000000000000000000000000014b2d69e02418844effcbc0d564b2721deae2872cd1f27f61d544fc0ebd5cadc77c6777ec944ef0500db181a5443618e0000000000000000000000000000000004f0d48a25c1eb81233f385af17ab6abf554e1285b669eeb5e884c64d5815fd5fa1350bb361997cf2e317f7c5e9cd19a +000000000000000000000000000000000879133a3c0e50c90abf1a6ac75bbeca1121c865ef39e9224ddb160eb725e0850a34aaf9014e42174966773e40e4c99a0000000000000000000000000000000004c66f8f5bd462cb27e9f5e1d93e322bd97582b9e81d07b2877103420262e4cfe6d0e3bc07f9f160701fd754793eae33,0000000000000000000000000000000003c0d6b721cee4e5fdc6a02095674a58075f81b1d28163f81d5b258c82634297009e6bfc8193969e23e196cf7a99ad6c0000000000000000000000000000000013229818411c8e55e50a63df6983150c1d5ead828711131d9c81841850ed76e4712954d3225eb6d7fffd3cb9924f7497000000000000000000000000000000000f42d6e4d5a28dbfda87c806cb0b1bbabb745e63e655c3c6be50411da4dcdc745ae50f71d56e88db8454d40375e325810000000000000000000000000000000000f663ab791b48f76d358e66e8cd8fa40848dff2bbec758ce1d7b3fe02d1f6b3f123cef644d4fd86d6a77b8155feae58 +000000000000000000000000000000000a7e855324ef471b8fefb31967cec84d57953407ba555b672fa59364b303030cb02b6c77933cc63fcd1b8c107b263554000000000000000000000000000000000b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e7524,00000000000000000000000000000000197865f685e78a8842fa79ddc728d507e9f31b31666d1952a46f6422c97c83fba3087be70e3bb588260556014523f74000000000000000000000000000000000131f5d85ad3beaabd129d5a5675d90ea911ebd02cddb5ddc7a8be28c33061430d684d123d5c516785d21ebf756c99195000000000000000000000000000000000c7a14948f3aa29f845e5ca9877db9f0477af376eaeb45324c21e6f99e738aeec96b89af4df942bffbabbf50172d8e5b000000000000000000000000000000000ed4aea3cb585b0d36972f9ad6943172ca7375b44d1d6e80e0bf97a0b25d74deca4d35ce865c8747f1c7a2771a37c667 +000000000000000000000000000000001706830efca18d3e75ea0f1ca8af23a816017ceeb045694cdbad6d3d9aa5a9ddb123f5097a226a217166de3a82038393000000000000000000000000000000000402132ac383a2fcb17fe73398273ef0c2f2d0d6edabc78f31080d3ecbf7c249ffeef28bb8b37a6ef2a7d726c070dc41,000000000000000000000000000000000a795c2affaaecab6cd2cfd6c8fab6e35cdd646e9cfa7b5e02400ef4abf839a69924ea80152eca7810a5041d1bf58ee800000000000000000000000000000000121426bb945d6f6b385c98a5247b7dadaebd3375dd8b2bff7aa77fddfbe603de89e77baf0e8f36a924c707c53d29a1450000000000000000000000000000000007a6fcb486634186f001c8b99874f0a07a37f1ff4b30599d2f570f1bb4ff290b816547f6ce8b3c1ed33e57630a1d57ab000000000000000000000000000000000fa65924a8f17414eb7dcc54f2a4134568484e91533dd21fd33cbcc37a920f2804516a64f1986e9d887ca189179d07c8 +00000000000000000000000000000000024beda2b950efcee233435f0c748e33aa928f54ff29d3db217d7e32b1aac5f4ed11705da4fb8fd38481382486e4aef7000000000000000000000000000000000c85283ad6e35a72d07b74775df1a4660113d50b51426451f454a575adf9cbf9d7be3f521649f6c367c4f4c74b67ff6b,00000000000000000000000000000000049d9ac43e31faa3d02f8255d207b82e4b27e8a9a61ba45fc4f9ad8048e5f89b58d25d98253aabe29334e0dc09d1cd6b000000000000000000000000000000001544f90a0baea38b48d89bcb337cf5a80faaa79334733b7e6126f55358a7e498aeb61419065b9434cab9d10fe8e7fd9f00000000000000000000000000000000139bdd668462a1b5d3ef1299d47aa91ed141ccbeba5b08a8ee31b023aa78c16514a97ba08abf5c8bb1abbd85b3fe87350000000000000000000000000000000005c7dbb8a22403a96aee634cfc67ee6f1069cd61a1e1831e8faa9d7e1aa5e4f7623f51f2e5b739f7fcf3b4ba77c82ff1 +000000000000000000000000000000000cb18f477abe58af92116101c3f52ad4f6074ed92a08c3adcc6660b555df9cff09dd8b34e032ed81e868a62bda50378d0000000000000000000000000000000013c4ab1558dc250c3b5d0f0fae3db62b8df969bb41e9ecc24c10e1e51cb399f1368bed7375a9b9ad9c7653c868eecfe3,000000000000000000000000000000000b8b8bf2b25c2386e5f3be4bdb387d8005cf055e68ab9a5606f17dbedc4fbd7a11314fd646d08bbd6e394485d4f56f5f00000000000000000000000000000000173a45d766682f82ec2d69aed1d80ede2477c276ddaa8fb97f5f4d0515b2c2e370c615cd81c1e361f95db855c9b1b6e200000000000000000000000000000000115868a9187a0465a9309054e865ef224ec3c88a5eafbcc25f9a912ee3b19084757a90b72a4038ba71b10f59fe2f93100000000000000000000000000000000006c5476eb8aa1a471d289af52c7d1df55f6bb1ad53d7eaba6bdc2a97fcb24ec480f9d8e12079d366f2213194c861f016 +000000000000000000000000000000000188f650fdc51b970d16c0637ad5e97aade93c7f1398751439484ec6cc56613814908e51cfa7f68da9d980bb9dac47a400000000000000000000000000000000081834f86f1310135a2cb03265f37d9b7c9459bb149bc54b5a61319a7cde36c6a2a0fb49f8f1fb9d80f07b84f799119f,0000000000000000000000000000000016e8fea4d09831146fc35bcad28e441f2c02e4d17838e04dc7cf909b2133297a13f07ee927722f3d78e36721d6848e3400000000000000000000000000000000114dee8b3a47269e9ada05ee015a874d1cbdfff4acdf5310642f829efd08f78dd6110e1c7a514e7d76aff52046f4ed140000000000000000000000000000000017b9d23f7a865a3ca61197d841fd9195805a9e883d79dc7d36e82f504e6689ade0e84c70a5c5c516fac3e3c643942e160000000000000000000000000000000001ab82b2a0986dec3211507b8adca351829b0a13f25e281f98f54d9e0e32280ea4c638dcb74280eb747a0d9af43b6b74 +0000000000000000000000000000000006f66eb49f95f51ec90df86254580f0ae57862bdd8b5f2759ace63c5f14f8c5a762207f744bb82a8962f8c4fa410dfdb0000000000000000000000000000000004e02a80628c30ce336eab890fa37a227f77357a60be72cb87cc2b7442d2163d395fdc59350624ca1322bfe8619a2efd,0000000000000000000000000000000006bc2ae646a603a1f4524b445cdeb99914e4ed19cd0676d511764b828bfe126e81cad2cb566655f04de1a302c14d70bc00000000000000000000000000000000023bd509aabfa41385e90cd4b1cbbfa45d066c4defab56993aaa386dc5b7707b1a3a7d444b8bd295a30d0b8f4bdc572e0000000000000000000000000000000006f82e60e18cc958375cce6f465db461ff46ed9d15cfcc01a3aff455d54c77ebba5a654c2ec788b6ed8ac53c39defdd3000000000000000000000000000000000896fbe6492c4c297f8b6d60295a7f2565734d69eea67b2675211a203fec043f0d181b1348bea425a068b7bc12676ed0 +000000000000000000000000000000001451bcd19495cea3a19393b77760f688fbf17b107dc131c88cbb503eee2a804e2978d6e8a4720d144083d28be73371d70000000000000000000000000000000017db715e8680a0e82e18b513f2c9c7ea136cefe8add71aac6baba146e3e33a498d025c7e0808ced306c915eb02900c61,0000000000000000000000000000000008604a06a198c3e11458de920176842221667d024f9c155892485a37ff56252be1dc629a6fd580fa41f5e598a23f3651000000000000000000000000000000000e008eed25eafeaa67f27e89e1f81b469724a4b00f08dc4ae672aa1587b19dc615330e3fce0fbd98d7526bc2c4afe69e0000000000000000000000000000000015bc1e4ea5ae2a7fde6d5e5c3e58f6ff5df5bcb125ab402f10edd09087bde39fa27dfcdce7d04fd18ce399729e155fae0000000000000000000000000000000006684e9be8bf9fa4badda842a1d8840f0820d9a797e482c64f4004a18cd63986f19abfc93f6bf068d38eb1e491cabbe6 +0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf77,000000000000000000000000000000000f15c3d0b40735babb2e38a2471773faa16b2fa307c3a573ef4cfa5a5559574b2d26cf88b19dee204b77f6e11a1b927c000000000000000000000000000000000d224445f3d31d381bb29c4fdc8130174f5bcb957f451c92f4a652cc3d2b5df985017133a944849b5228a88f99bec771000000000000000000000000000000001338b48bc1fa229f251bcd4828654baec9d149f090b19596ad3b444eacc7bc583f97d9cfc40d5611fdcf89cc9a88e33b000000000000000000000000000000000c30dd2aa51f6577d57175edb3ccc1b324717bc195eb0073c1dff4e5b0d77cf5e41ec233527b3936994e86303f91b172 +0000000000000000000000000000000003379bc10acda5ed1014e2bba1e30cf83b72fe69259eb35476a031b8a898e0183bc32ee853a85fb3d738424208fc880900000000000000000000000000000000175a2e5a44ed62744fbbab9581ea7283470bff12436dfc414ad80b4070f895de3786022cbaed55bdbbc4f68db7460548,000000000000000000000000000000001735e1f2fe905839fd6534c95b95322f8cc86a4c482f1ad7691b9b9bb8f55015b4faaa1f243786aa33b5874817cd09c80000000000000000000000000000000013f1a27931ac513145f2601e009cf637ba4bdb18a7604f89534fa3ec8488f5b6eab9963c5d753fdd34cbe7d2f8eb8a5900000000000000000000000000000000092d8f800e7a4bf6f9a25ddd7f64fc403db53b1695ae59c15f229458f347a8e7c2ebc415af2d3849282b670c5cf6f8600000000000000000000000000000000019d22d694e559c55db63521e7b60a1a2342c3cce868d70951e5ed32ec0f5efaeab0e78b21359110f6e769776b745938a +000000000000000000000000000000000b384a9db472c38a5d246da56059b7630f002b5f4663abce7c5f6e896222a1ca1ac02883a1ec95a4ef09bcfab7d0652a000000000000000000000000000000000de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc,000000000000000000000000000000000d7dc499e5213120b3ccc173c83d3c15dde9e13ef57238cad84889243b35c8e69eea2ac7ef7560051dcd7402b46b733e00000000000000000000000000000000063ad31c17eb17d39cb4b33e45a0b0e951becc11b685b10cb45cff268b6dca40b780f7e1532be91903372c413a11b5be00000000000000000000000000000000140da959456cbd34e041409350d6106ff65ce6dd2ac3149f04959b16eb83dd0456ca11e5990daf4a1e5c23d3f30a6c4b00000000000000000000000000000000195d07ab127d49baf89fcf5eea1f5e4cffea1a577a5c864c0e637fbdfa10182adc1d5d4ebb871949300193e45ae0fbdd +0000000000000000000000000000000014df33e7d3ef2c339b958fee667097ccf146556261f7db4b0b0a3c29897b73a0ca249866cff1461488012bc16df43b0d00000000000000000000000000000000099dda253a43b8cfac580306267d9dfeb2c129ac1818fee43c6df5e582f5fa726ba73e1a2ef6a9e011a387c393529678,0000000000000000000000000000000013ec1ef25b303fe2f10a0bbe9bd77a4b2a055e176c2870c99e63b4baf2b313a835459263351dfbc25c22ea32946d8956000000000000000000000000000000000cb1c3292a2e0c9b1c1ff43cbf7595f39c00fd413b54782681fe75a6f5f231d13912f8d598dd8aaae8159de083dccd8e0000000000000000000000000000000005385f2d4bb6d94d67b2a3bacd3aae31da282707672252c0ab1a12fc85d8e9b9eb75454eb145937542099b860f9d6dce000000000000000000000000000000000e59506f7733a38a7e1da4ea5958de4755b52a9307ba2e5813131b33b86f0e401f97594d9674ff1667068a1ec3c9b145 +0000000000000000000000000000000011c89c8d7e83155a308b2e517a23f05a4a55353332292b44b0a891b6f730fd126bd0b97eb87f0fbdb6c82779791d022f000000000000000000000000000000000da6f02450955bf26e236ec63aaf80a018ac34fd8784bb24a22a1fc5e8bd686244a923009a10cb38b1422534d0997afd,000000000000000000000000000000000f4392a41fb3e58dea97b97fd22e2fe6436c3f9bbcd944585a76a5f1a8f98ea4ee21639208d765b6c3a7d08f8cd3f3f00000000000000000000000000000000002c3d62794996dbb881b665eece98926f41a42c21539125fda6070d9f69e29e0557c886b42e4bcd97b14134d6e9d1d710000000000000000000000000000000004b93f315822aa1be8250c2e736727d390ae3a862c4c7dda452817f70f01c73e6f344df1b0f05f03bd574edecc70902e000000000000000000000000000000000731403981fd6243d00c23d0a42a759016f7907548847743f18421f51b1e72cea92f0c5580328babd4ae3e15bc9c56de +0000000000000000000000000000000015bb227b5c9ccfb8390edcd158b04a69a88a3b99a10ae90e548182751a16448df25493061afde2c9790a0e75e6f409a20000000000000000000000000000000001d7b609155bf3939192eee9642032e6fb10f57d53916674c60211a37b4a7662759899a9569e2dc730febd23f747a7a3,000000000000000000000000000000000b35c6294b70336217eb9334ff1f1bde9d892d109e947de7f4f5681b3830ed00ad1b89ccd7cbad88ce1586449140697d00000000000000000000000000000000032691e5f4597c06496e9e37907041ddcadd18ca8ce64a8b400b1e2e8d63acce5533231edb66b69807fa2dc026c1d2be000000000000000000000000000000000773ccd132cb215cd98aa17d7fc432e0577b08d8faaa35199000d46fdeeb954e8652566384fa0cc5bcd1724942f7075b00000000000000000000000000000000112e951db3694944fc82fb980547cd8b7f2e5ec6fd2051b6aff2573797bd6a28437848ea0627054af1960ad1de0981e5 +00000000000000000000000000000000017599d71686e817cf58b78dd7586d5b359999b32b0dec2d67e33fb6388411418ecfaa2670a2cc9dce3dadaed0fb3364000000000000000000000000000000001773995b540be9ffbfd276a92c0494e4eae296d094f9f7eca975cf4f73ae05e92bd64ea71ac47bba534044f4072a6591,0000000000000000000000000000000018f2eace212eacabd44ff01d886543410ef72b4d27f8d25cb080dbe4b1d4b2b4e57e4dd40723d15789d9b5104b088d9b00000000000000000000000000000000098e9e9b302876ce85ba486609fd028f357314149ce8b530778e6de586ab057fe59648d8c8ae80fe619c4c605b90784a0000000000000000000000000000000016d20a8ca43d37518c8a0f47566ba61a7aade9ea2cdd4a0907ff0ed862c6b7c64815d50397eebec262a05c6010cfaa790000000000000000000000000000000005a70c2fce25acdc4a95fc2bdedb007d71f24b0b5714fa14910ef590215d25442e91a66b6bfea5f7777f0c6d202eff32 +000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9,0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa274526850000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e245782100000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002256031a4cc1bd58dc0e2ebb13b49b5ded8a28170c5ea0fd,000000000000000000000000000000000b2968a345725d07e4658bb0ccd7cbd32a5bc69d9788ddf13991fb32294c07c9a1165b3db7aa17d4de458d2b812684ba000000000000000000000000000000000ee9abd94727b6a7f0c822851105e23f206f158f8577d6fa1106ee59771262729bbb3d83e130df67f6f1b9ded0457e64000000000000000000000000000000000a44a5ab344d272d3a521b0c874ea214df9c170b2b6cf61ca863cc109306f9180ecb8fafdae03ec8e383a2ffa83ca9e10000000000000000000000000000000004155873bb4af85dba10378f6ba1af5c1bd2f32e24b0504e82df19a295b91e7fb964c70d6d845b7e55ab6a6bdb1a8593 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000241031a4cc1bd58dc0e2ebb256b49b5ded8a28170c5ea0fd,00000000000000000000000000000000091618fdadc3d283b730ffd45b49f61c6175f1d16fc1955fb9ab51c53505e69e971a7df24d6fbbd44ccd4fc339c57284000000000000000000000000000000000236623da54d4b85938745ed2a4826960489122f20bf98bd830910eb7aa4640b2628233f2f7ad3b6d00be0b2b54c6cde00000000000000000000000000000000083566a73001823430116395bc30cb75da5e040a45ee9b3e5da2f572fa8c6d49d39d80be0e7871286cbf37b49289eed000000000000000000000000000000000121cd3dca2eb6a9c89dcec747e6e7fe98642d868eaa0b1987512c09f8578d77fb4f6d4d9bfc15fc3077e11c1d0ca0d05 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000241031a4cc1bd58dc0e2ebb13b49b5ded8a282560c5ea0fd,000000000000000000000000000000000d7c951be6bd4472c7347272334c5f03f231b34c3a72a1afffed7dd6a33e88e952b212aa48f0a8c6c8314734c257fc16000000000000000000000000000000000eb4bd4d602a1982054ba202775c75d3193612ff734b3216cfe5104cb28f4d6e06959e854f45deb425ca20c0308835e8000000000000000000000000000000000e988ed9b5bbfaa3f26977adeea87890398f35cfc80de17286ee8e22cd0020745e0494f9254aecb551ef10860ff8cdbc0000000000000000000000000000000010ca55e3294de902b5fe599a994c8c4527ee2e84880a263b82932e7fe825af1f95190b8d623d68481b76d6f98396977e diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp2_to_g2_error.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp2_to_g2_error.csv new file mode 100644 index 00000000000..382bcbd77ce --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp2_to_g2_error.csv @@ -0,0 +1,101 @@ +input,result +000000000000000000000000000000002e4180463511efbfb553c83ddd755906c74df82d16a9d1fa49db4fdd4b2a1667dc38791e7e2f080bd451dc68b8b63dfb0000000000000000000000000000000028896d9d7316c7c952f610bd8177b99fe533da841a57b9e390cf83ce4ba5b3168df348b9b36280a79d784a7b0e4750ec,"invalid input parameters, Failed to parse Fp.c0 element, 0x2e4180463511efbfb553c83ddd755906c74df82d16a9d1fa49db4fdd4b2a1667dc38791e7e2f080bd451dc68b8b63dfb is not an element of the field" +9c00a0ea4d6f514e7a6832eb15cef1e10ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8edc3d921e9bd8eff13ee00dcdcb84109e2197394ce21fdcc701e84354b210297e1f7bba5f5bbb1f7603cf9b30003d19e3db928d2a6b1a8d6000194e63858f1d1f6,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002bf2b53e3fe6ff3568472b5ab1d8448892d6a2c797a6d8f09d1028c990b4b0eb0f1f64647cc92dc04e8958fe2a893c74000000000000000000000000000000002354ba399df23ae4b40c6fad0725f3a22c9b2132c7d3e2dadaf29f370b28bf5b921205d1f1ca9adfb5495adc20a4de00,"invalid input parameters, Failed to parse Fp.c0 element, 0x2bf2b53e3fe6ff3568472b5ab1d8448892d6a2c797a6d8f09d1028c990b4b0eb0f1f64647cc92dc04e8958fe2a893c74 is not an element of the field" +ce6d534e4a8c627da1484b14a8c9e28b0267b87563e56bec9a3f938049ef4641b79ca63289c31ca3cadc36980fd091a23b3e052dbdb9852857dc8de4e932cc41b4d571c7b3092e1ae11d9697f82ed833199fa649608972d295befae38d36940663d2b67bb286a3d549c75deef39ef8068728bbba2cafac44c102499601e4bd86,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001b45fc2e5a7eafb3b876e78b43a1a2bcc2247016762134d3803ac9de588d1180cc94dce65a818f5f70b1447ce2e6ca1c0000000000000000000000000000000024c762331fbcab7625fe7069b67f688d10e80018974121d404f9eb6bce4a480857b6b30b16dbcf1560bfd9a00fc8417b,"invalid input parameters, Failed to parse Fp.c0 element, 0x1b45fc2e5a7eafb3b876e78b43a1a2bcc2247016762134d3803ac9de588d1180cc94dce65a818f5f70b1447ce2e6ca1c is not an element of the field" +ab2baef94be3a4bd15df2dd8e49a90a614a41462a4e6aa14dd1399946d3c7ab9dbfb633f62dd749ef5f1eef03fe7f6c99289260c94ebcda82a6689adb31069637f16e09114878895626faa93b9c8c5a316ec9eb24a06ed90cbc51b22b76c7205d0b74a62bf50719a21bd1f725c189107a322696a05b3605604ea0b099f92a900,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c7d4cf93f885436cb2d5be3de185921b9107332cd8d5be8ccee698089534cd7886b00d4201dd1a0c56b17e97e7db96800000000000000000000000000000000237e4b76cb10ce9576becd266f28be5e07e98cac4d2a535419065460f1c8d6be5c583d6307f37714cb4df021c0a00653,"invalid input parameters, Failed to parse Fp.c0 element, 0x2c7d4cf93f885436cb2d5be3de185921b9107332cd8d5be8ccee698089534cd7886b00d4201dd1a0c56b17e97e7db968 is not an element of the field" +288a2d2117823a60f7536258eddcd931139d9e550b504e5086aca96328a60c6dacccdbc6213ff05632de89ce0d46fba178e55c8d0957fe4d86769cb4f5d8c46e1b25130888956b0cdd344de9b465944715027b5a16d68fece677154d1fe064ac5abba6672ebe34ddd48ef21ff73bd7e28d7b50de12f3f21d90dde04ab8e3af9a,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000029236547ec215f1d6ab62b91126b16537c5dc5e480dd52366b34e598cc17231892ba434772dc0a535e78838be3fafe46000000000000000000000000000000001e390e8d71694e024d88cfb4856e7aab8a875fe45739ba5b7ca855a7703d78af81dd9170dadbdd66aca05cf35435ebc7,"invalid input parameters, Failed to parse Fp.c0 element, 0x29236547ec215f1d6ab62b91126b16537c5dc5e480dd52366b34e598cc17231892ba434772dc0a535e78838be3fafe46 is not an element of the field" +ba2feefc919aacdce6059a27a1e2efca0c1b5a4ce8bb61c3c1d0c928487bc91a10b5062d0ec0bc4040707315e80dfdf392a4f82e6572cc3d5508752c9b1c4f04a5e2a2e6f558681de7b4e84eec171cb019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab1,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002762361b13387bfbd41aa2031b859965646563ec8308e757b239b4c87ebfb74c04e460be0ece27c2c3fa425b90c05a50000000000000000000000000000000002a27bf2138efbcde519c6b8096a79efad41093b069c3a87043efb9d811cf20ef1bb966acf672af5e8374f06f4698d5c5,"invalid input parameters, Failed to parse Fp.c0 element, 0x2762361b13387bfbd41aa2031b859965646563ec8308e757b239b4c87ebfb74c04e460be0ece27c2c3fa425b90c05a50 is not an element of the field" +6331f9816f8575d93eb72b720d003e530b7e9c44a6543dba9c4cc7c114f78494ab8f39ed49547b0ac44d2b52242252ae3214358f8cd5e2d23db2a087dff7ec86590e0b7016bc063f3fffa93e1e35484c08c47351d234da736a9b995d4bb4812134fa24ba94107c26cf20827456cdde79c3bf354ddf4c624f68dd95ae86efcf7b,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001dbf4dfea9d6ef66bd393e94b94799abba6f49c9768bd23e4d475a0b866c4cbbe3634fe46a9ed902251241246f50d879000000000000000000000000000000002ee7495f5decefac94f67c6bc894b10dce8b06be0d51adadd5dccb555408d99dd933364b2b818cbef9a65c1b3d4cfa50,"invalid input parameters, Failed to parse Fp.c0 element, 0x1dbf4dfea9d6ef66bd393e94b94799abba6f49c9768bd23e4d475a0b866c4cbbe3634fe46a9ed902251241246f50d879 is not an element of the field" +62c9b1e1f32b5babe6067d7ab5acc94c161c92d04e84304bb49dbcdacb87235d270c7c70eac2ded5102eb0c7ddd1313dfd2b32b8d525e69ac9485e9b754a1969c7b40b8728c3b121c0b67310a80d9efa0156335352698cd93f444d602ef63fba8d50efba377f28b048af5be759b6f531d38733674e13a8cb664f4eb30ab8b0a4,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002bc4ab15ac917ef7372bf7415056032aaedd3b408aa73a3f1d49d87189961861d35f5292c4510f5198b37d7f9f4dfd9e0000000000000000000000000000000028d433b37b8fa01df914bceee53a8a0ba37ac5b020259a6195a84e07e0cf1385764f6394c7ab014439eb7946c8295a22,"invalid input parameters, Failed to parse Fp.c0 element, 0x2bc4ab15ac917ef7372bf7415056032aaedd3b408aa73a3f1d49d87189961861d35f5292c4510f5198b37d7f9f4dfd9e is not an element of the field" +e8c791f14a43eec67ffc7d4febf66fce102ef7830d0ef57abc97716f8a25057fedc1c2071d017cfa2e2fb59d7163d8167d38f9ef1c52dd45da3485aac3e5719b6a4d843a26b052a040c79659b5e8637b14233b617441ecdc84cc1c4610e6303e734c433dc083bd99e3c42cd73b03f12a3f3a48c0c7613677b1f1db6cd3807072,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001e39e089ae37fa4368dcbd0de651d90120ce935990d4aec61157f2c01fd9ae1d072ca5f58a4338d58111be9b405722d3000000000000000000000000000000002153852b908c5e035237f4b0c3fc8fea3cb7731708143de097685aed50685db783062ed4fb8230be6d0ba4ccbebc9f81,"invalid input parameters, Failed to parse Fp.c0 element, 0x1e39e089ae37fa4368dcbd0de651d90120ce935990d4aec61157f2c01fd9ae1d072ca5f58a4338d58111be9b405722d3 is not an element of the field" +ea6309d6b395c94fb5a43dff3c6bfa690c0e2726326d63de53eb4e4907c82b95619ab79ea13b8bac48fdd2d8bb78857096a91d7e369e4b7467f8802250f2b4d5ff02d386fd49420b2c273b1233e9cdb105d5bf86e027fdd728fc416262740976f5f62c4a0a047f5e299b3f9793472e2f8fe4899736f148176a03d85c22abdf40,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000246a97bff4e3879eba62bef8e3ec94c94ec7b4422dbdb0aae5ec08a1e670ceca1aa037cb0fec90276601f9fb4af6ab62000000000000000000000000000000002247c58843490acfa99f9aac7cfbf2d234c92542260a67909ec587f9d0e6d9a7e5c93791f0d10d2481c9c39d8c7a7908,"invalid input parameters, Failed to parse Fp.c0 element, 0x246a97bff4e3879eba62bef8e3ec94c94ec7b4422dbdb0aae5ec08a1e670ceca1aa037cb0fec90276601f9fb4af6ab62 is not an element of the field" +4e624105d6cfed378783d5c040ed91f603480caa94672024a2d2187e36a3379cf6c51ed2ab3fd0074b7514381884a20493fde3cd717b8ca64079f88ff560645db1ee0d2b90782aa0d652b8c0e0bd3150070d8207f01f734119f150ea77c21a8331d90ba196ce325c7c546b5d388e5e81f900d7cb18ca7e32157db3adefdfec4d,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002448584a7ff7205936b7044a2b660580c68e0644959c740a4406aaeb6026b527abac6624b0a1388532f75b184c5b231700000000000000000000000000000000235a39e7881422598b737cb0201d558d07915096193e551f34ab08e045ace7f0d41e227d6078daa70756ef4291293b56,"invalid input parameters, Failed to parse Fp.c0 element, 0x2448584a7ff7205936b7044a2b660580c68e0644959c740a4406aaeb6026b527abac6624b0a1388532f75b184c5b2317 is not an element of the field" +a8ac95ebd4944b9e440f20b24c9d91a0010245a79ad9033c990b480580b0483ecbaff19039f63488bdeff08d8c209ebcd9202248e1879c46b39c4f241777cfe850c5f23df0ebf1fcfce4bdc261301b49090154120012c78d19f379c1dde7f0ac5447fda47fb4baf7c251d3468eed9492392dda47e4b522803cdf3d64c86d9241,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001a75be03aff507909ba6995a77dd0182cfa088d7612801cfca60607db46592668f27434edcf4761957771285ad71e84a0000000000000000000000000000000021b4e686fb8a7135b3d6d136e9ccc7782c09a6a2fa3920a7393dd94f308aab9de87b204253a76f8cd59a942bec98f4c3,"invalid input parameters, Failed to parse Fp.c0 element, 0x1a75be03aff507909ba6995a77dd0182cfa088d7612801cfca60607db46592668f27434edcf4761957771285ad71e84a is not an element of the field" +e3a91216b646c54a1225c84ea819a0431742000d425bfe96f353d9a009475515e087414f4a52a3bd84cf09671271664f1c42257232da982d41508f261abf1414bf2db2842d626647bdb3346196e9420a03139bd292d2eecbdbf6846aa943231192e1ec62bfe845baf41f585d285783da54810bab55652fbff76c608a861efe3a,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000020ac413ef85aeee602fb60691c11fe2a5f01899058b4f26726caf030185f04f0f9efddf6bf51f13804e63d2c6d15bd30000000000000000000000000000000002d7270a3873a9b1f069a8ee7e5e027b2c7c913b1f6afe0cc03e7f149775607b631a945d84ff206d6c478f940ae7c42ee,"invalid input parameters, Failed to parse Fp.c0 element, 0x20ac413ef85aeee602fb60691c11fe2a5f01899058b4f26726caf030185f04f0f9efddf6bf51f13804e63d2c6d15bd30 is not an element of the field" +e991c78ecc13a2a5d0b0f30fe78d473e1fc4b30ffb096dbf7894a63840dd2b5a4e3f0e3340f0eb502a64881fedc67bd9ca616c889df992f664bc4e453c13e24d2f3959c0208d3ab6b2b2a8a42887ca6d16656a6f17a56c80784d90536e49dcde4a39b52cb6a4647226e4bcb76f8b5a5656117f1a5e1d6a23c4ac3823c72def3f,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002aa6d45e80e34171419a6d59e0760a4e0af288fd56e254957cb4aea13d9b8fd2cab89d61faa611d04bb254d75b964911000000000000000000000000000000001fb010383254721bf3bdf8e9d7e7f8bf42964b3509ba02d7e2c0ec54e029dc998df20997e0d4142e832535d108897f8f,"invalid input parameters, Failed to parse Fp.c0 element, 0x2aa6d45e80e34171419a6d59e0760a4e0af288fd56e254957cb4aea13d9b8fd2cab89d61faa611d04bb254d75b964911 is not an element of the field" +aae17c3a166c4fd55849bffc842c2127030edf80f55a60a223b6b17aa79e90943038e0551d69c5a0223b8970578fcf54f764eab3d6315405e4fb9a37d25373928c97e6a8b8b5b20209a1b2857da85999157ed7954b04cf6f15d65678c0af4cd423dcd1ba6c18f3bb6c4a52675c39920abd87fa9d12113689181a65975808c473,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000271637290b42acdfdddffae8d18de43697243377418cfee585414458623a6e43b964aeac54a820af97b9b2a15b63cd5e000000000000000000000000000000002d67682aff3be624f6c794b0f473a678c929133081982e782bf43ea0612362b14699178dde03ad14488f8b978b63b2b8,"invalid input parameters, Failed to parse Fp.c0 element, 0x271637290b42acdfdddffae8d18de43697243377418cfee585414458623a6e43b964aeac54a820af97b9b2a15b63cd5e is not an element of the field" +55500e20a95f3c96ffc1769359dfa103010cf1962744d57780715641c5aec1a3daaa6e86b023813373eb7234fdab34a98955cbc191870a6287f6d90ef92a9755f2f556326d78d8952908006c06ceb9180a8359889f3c145ee8be8886629b7b17bb585acd631f936800822fe62ba26a01d84d08b3175ea2d3e9844708632f34c4,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000217d174cb2061c18a8c14615665ad3bed2af01f80f92ba0aba78c5848437b80c77beba1c15c7edd9c94d7f7fcc599cb0000000000000000000000000000000003179079cf8cbc7af103ece29567f005509a610769f23adc373d2d01cf3b9c50709f2e7221e2c4fc982400afd4110518a,"invalid input parameters, Failed to parse Fp.c0 element, 0x217d174cb2061c18a8c14615665ad3bed2af01f80f92ba0aba78c5848437b80c77beba1c15c7edd9c94d7f7fcc599cb0 is not an element of the field" +d9bd0117ec83481e8dca9ff432bb483a0354a7b61096f7e09aee110db6aa6085a7e7b83bec91010b430da57ccc873612a4fa8051a01ca8e31a32915f786e0dc35e1284cbb9497368d12d0ac6e9d242da039aceda5433bfe6a6e1fe892478f254f7e56864a9b5abf879f7849c20fa2b8f7f8799ac437aa2045be95954d47e7daf,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000235bda3c75cadc01c0e4596177aa8c1d0917cd247d071112269ddf0b29c71dc41202bde92f5d1ddebe84938c8c0cef6c000000000000000000000000000000001c43d1c2df04035d721ded245d8c5a2a5e8b1ca695d8934f1208d5e1aa89ee7eeeed6ba1c0adcc9fe4727504b4cede1c,"invalid input parameters, Failed to parse Fp.c0 element, 0x235bda3c75cadc01c0e4596177aa8c1d0917cd247d071112269ddf0b29c71dc41202bde92f5d1ddebe84938c8c0cef6c is not an element of the field" +ec8c0bf73a16b55834508dc39815cbf817e3c55dc3e3607f2dba0c7b7e2b33ee23cd4f7802a911fd2f6d3693e31677f0558e1cef365619c602a8ae4594e2e4d97a4467dcf21ab62f61529338195b665f19a29533c2aad3580ca0799429d695420f376840e16fbf97dab37772f7fb49cffcb50f07968a215befa2eccd9c05b89b,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000331c899254ed343b8bdfd27768c15431c1aa56f4e018e56a4b5e0d62ddde416eed3ae2dd8102419bfe500e260feff68c000000000000000000000000000000002f43473eb3acc04efcd9b2086e252a8b0f9403b9bbd3d7225bc946c4aa7ed62df59e49a08485b686336dd585013f09df,"invalid input parameters, Failed to parse Fp.c0 element, 0x331c899254ed343b8bdfd27768c15431c1aa56f4e018e56a4b5e0d62ddde416eed3ae2dd8102419bfe500e260feff68c is not an element of the field" +a5e481ae7d0979a50f474e8f4051c4e90ea49065a4ca9163269ddea490afceabbb76908bd2a7ef1c66749b598b7599545fbdda49374294982a948c0386539427ddbd3acb77c3b388a0040cc5dcf7ee070e37bb05e1e1e1ba0efbca4685f695e867789d5cd56b8d3839ddc9dd7764a6390e5567e1a91d85e0e96d29025e179635,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000025d4b3e81050b3b86471c7210a5c3cf58f13c94115fec1cc0a94aa969157fd35da9d3cf76326be744a0c72dd2863553e000000000000000000000000000000001c09d26985b788438c361a8d82a1da8f7b614dbfe81b3739602fc5d1b3b24a635dbc0b76732bdab2b79e393f704ec90e,"invalid input parameters, Failed to parse Fp.c0 element, 0x25d4b3e81050b3b86471c7210a5c3cf58f13c94115fec1cc0a94aa969157fd35da9d3cf76326be744a0c72dd2863553e is not an element of the field" +7111899ac07a71b7c38366a6aab5471605ee114f1baab60c9bfbdbf4f658f3a760354e71bad402cdbe89eee7d1a043198ac0ba3668a7631694bd448ca33b1cc0420b289a9a1a612605fb554531f53b8c19522fd76b56df53814a15396e6067caec6e19c9c13577aa60f699e4bb69fb53d8befa2142e1600ad71edd1e04fc765a,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002dbfe256476dcab6630fa551cc82a17301be54a9b4b21fbad0459a710e7bb19ecfe2e1e58681f2fbb6bd2e306f04dbde000000000000000000000000000000001fc2f031c793b082a90b717e99f9cc63d6e845ad398f22f09264c0c60b6a71a289d9ca8de5050bc912b428a764bd880a,"invalid input parameters, Failed to parse Fp.c0 element, 0x2dbfe256476dcab6630fa551cc82a17301be54a9b4b21fbad0459a710e7bb19ecfe2e1e58681f2fbb6bd2e306f04dbde is not an element of the field" +69a065c3a5671a7a54a852baf21df9f415af2c52ba4cfd53616025c882509a0064e1467f798b116b2fb932ffab0803c4b5fdd5485d7f9c7805d85cbaf17fdbd1962cfc94bba26d748d5c30754f9994a403fa115f113cc37514de4ea2a4a78b8a1bf27cec8c2bd4674f9b5830ad4b9a285f5668b5939a6335ede389d73bb9c89d,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002628d2f5fe2d46db948aca89a84c4ebfb7e2487efa38d7eb6a028303fa08811d55a34959869e02f9e0da6a1a4d04c2b10000000000000000000000000000000022cd456e2c947a48b5909c55157eb79cdfe496f480e262a2f0c6a88129daaebe0995cdc2e1bd56c36723d84d3566e3d1,"invalid input parameters, Failed to parse Fp.c0 element, 0x2628d2f5fe2d46db948aca89a84c4ebfb7e2487efa38d7eb6a028303fa08811d55a34959869e02f9e0da6a1a4d04c2b1 is not an element of the field" +131ec5b5b825d0f6c4ff736b0f76db8c176b817f6c2d962149d6c8fca02d2f4b0d2452f1f5a91160b98e62f579dc441e1e1f6b3211bc7a63dc52d3f643752cc9d5535835924719c156be810c3fa86e350561872c61b2e581da7fb20da71f4721a3b041199940b36f485e51a19f04bccc9ece35bca74fe5e1afbe3aea3228b6eb,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000028edfb30e9ba69d43c7ebe7fbd47ec9cbdd0b819a175c30697a6d609883c5874b45e47dfbc72bc3682ba3d85371a6595000000000000000000000000000000001c2dce80de9e7019fa17e7a8c1b61341c23cbd0465d0c224c9a0498b47738303b50a388d72a2354d61a5aca35f7e622a,"invalid input parameters, Failed to parse Fp.c0 element, 0x28edfb30e9ba69d43c7ebe7fbd47ec9cbdd0b819a175c30697a6d609883c5874b45e47dfbc72bc3682ba3d85371a6595 is not an element of the field" +f56f4b1ec8d34603c718651715ab786b1346c13f096bb94d5c16adc1df00730c2206e4f414fb68ea0dce5d2089372090a11f73b04daacf32773349186ae78abad41bfdc6d97a35cb94c29c552ca3656908b431b820bddd77921275918d56bb7f9ad758999be853f6768b1a84b2b09d1b6bea121540482098d6bb6c34a1d0e50e,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002a4b27f70ae78e5b6e80babcdb5911fd167f5924acd4cf655327ea6dc56652b3ffe6a46a2d390ec08e633299bfe82feb000000000000000000000000000000002e5b70cdafd8e89d29fc4b1159d3b5b0316aa6016f03f24c2ec99174965b2ab6579977bd8c283332c7fc0d1729b0469e,"invalid input parameters, Failed to parse Fp.c0 element, 0x2a4b27f70ae78e5b6e80babcdb5911fd167f5924acd4cf655327ea6dc56652b3ffe6a46a2d390ec08e633299bfe82feb is not an element of the field" +7805fcabb21ff8c61d23b39a5cfcee1a186c72364911457a9c883fd2bbb277e4b82de6b8658a7883f95451ddc963807876497f44cd68966cbc039fd1cf15f004bef9b866f05b63afbd1f0960261592180d026ca24616f8934fe1abb20f65f0d6830c319451d8e5a09a8edf7cf7037c7293a3af1eabeeef724d0b4a03fcd84506,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001ff4f99784652fbdaa026a7e52c29767e3ca5eef8e1b6ab289da1b615c17e999958bffd69bb2b8a71c950daa9293eef40000000000000000000000000000000021d7aab6e6d44f7ef392004ce9777b0a65a4c8a268b907f9f7c31156dd0dcf282f6e767d62d444d15f0f89b05a2d19a6,"invalid input parameters, Failed to parse Fp.c0 element, 0x1ff4f99784652fbdaa026a7e52c29767e3ca5eef8e1b6ab289da1b615c17e999958bffd69bb2b8a71c950daa9293eef4 is not an element of the field" +ad3afe81569939b0de25977e7426cd5611ad6ada670b3ab964a67b90cad1c97ddde36aac9f9d72c7d90a5e74cedf5e78fdea02da893b16195f9750635fbc2d9400798c33e040a91328ad03b11a0c10180e51e1cd27a6266782185aa631784cf980f7223592b51786f40a6fd339d8d403689e495ef41b5aecf8fa109f6b3c7bf8,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000020d5aac9b7279ad4d196c2b18086aa2605f360b26ea630abf01acd15bddc9bfc3ce8be5808603273ba149488fcac5f1500000000000000000000000000000000253b3097024ae186c4e34dfaf270c54c7b897777c5fd42158ecbf43882bc7f226a8dc76887eab4069f4baf6b24f86b52,"invalid input parameters, Failed to parse Fp.c0 element, 0x20d5aac9b7279ad4d196c2b18086aa2605f360b26ea630abf01acd15bddc9bfc3ce8be5808603273ba149488fcac5f15 is not an element of the field" +6b7a3c57a0df5250a868007b1088ac0205308b026652626178576aad0056acc9951d7bf234278b181533126c682f8f3436d1c7f2535e6a52621469fc90dced06e5b3618f0ca06e428e0cf0f590f77d13147497791367cf0514af27f5ff41036c83ef104a056dc0abd049a6a38f8f781f77f93b3fa75141856f385cd2c18a6157,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000266ac2f315c39b0558c992131080900e1abdacbe5e97cc6b826462282700b6697a345ce3824bcb12947a610e433406d6000000000000000000000000000000002672b73a772905356ebd42d294303bf2dbb1fefa4d70493a15768276beec21641dc47b7d4cdf980ca7812261cd6b4a1a,"invalid input parameters, Failed to parse Fp.c0 element, 0x266ac2f315c39b0558c992131080900e1abdacbe5e97cc6b826462282700b6697a345ce3824bcb12947a610e433406d6 is not an element of the field" +5ac60f574e9f071070f1de7cc5e6a2cf16dc71f12aea7eeebcb24ac1066b0e1c7df1a1a58075c811064a1e9084124491d568c6d55548903af0cb4821ec7804a65d9c6b544109a08ffb1946d91fbcba19174510b2cd54cdab088c6a9f25b71fe7594e73d3fc02cdc80d819913d07df05b5182c8cc4fce72b8b0af6f2255522645,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002b0ad7dec0529658c1e4e5792a73a99b5cc935ac24e282582f1aed1b5ca085c446f3764397320a4175c7c43806fcdfad00000000000000000000000000000000316951df8685f60fa68fbaa9639d8a9b394fd76de5ba95d00e69d425fd4e8d56094b28d4ec324678d6066ff50d8dfe1f,"invalid input parameters, Failed to parse Fp.c0 element, 0x2b0ad7dec0529658c1e4e5792a73a99b5cc935ac24e282582f1aed1b5ca085c446f3764397320a4175c7c43806fcdfad is not an element of the field" +6f4c36f4b25b5b1bae04af9ba3a5ab080a9f745cf3c6a5d3041dddff65d4385836e220adff2bd0baa8de15ddf8faf22b0aaa152b8a7a4eb97e22b1999346e31aafbc33d1cf3b0d1808e559e394a9c1ff0c81d126b3b25c718e9d88d716e30abb2eaa1c75da8904ac03f4fbbed93e1e80cd7ee42dd7f4078083bf2e1839408870,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000263af7f2d4fb9139256698be4d287bb798f2ac956719ac16cdd3b549069e4a12d6542802b9bd59b29f91e59a41b6091600000000000000000000000000000000337f0cc8a737ce905421f043a9fbac934546b63ee5e74f9cb1b3509bc5c941616cff656f0516ff628c813718dec1c7e9,"invalid input parameters, Failed to parse Fp.c0 element, 0x263af7f2d4fb9139256698be4d287bb798f2ac956719ac16cdd3b549069e4a12d6542802b9bd59b29f91e59a41b60916 is not an element of the field" +aa557c18376daca6c579dd4f361fed90062ecc059d225f08a8433b334c406d484c43d7e8c941e778533aa38cab3c2ed86dec61e113c8427f47779be688531645419f732c766bc621a5eddb7c0a0ec2310a7eb8b61b85dc5bdae0a2dca2ee37d4fdf35d82afd2eb3251e5fb8b230dfd78b5c37b8c0c438282aefa8f375ea9d26b,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000020ee9f625d3d5fb432e842d084c3665071b7e50388e0d3b747853239dc0cc9fe8a5bed33510bafd015cd8ec3fae85d4100000000000000000000000000000000300f776afe69446368997b42791378c96fae30169c2d01c98ed96a3673e95af414e59a777df4e2b6ae3e25228c185ec7,"invalid input parameters, Failed to parse Fp.c0 element, 0x20ee9f625d3d5fb432e842d084c3665071b7e50388e0d3b747853239dc0cc9fe8a5bed33510bafd015cd8ec3fae85d41 is not an element of the field" +6cfe87edc6f573eb52c3445f0f3bd7c318c2d31c070635d819737d956add766267eab6919bf576340780cc5554ef697d9f4b3ffae9c5c13eaabaf174e3f93d4fad6b0269e667ec52a691b9635e38a46e112c4433f6b3fd903824cd160bf1e6479635661451e253af740afe662958e237108f285c7d1fd8e3445a3fd60cce8913,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000026b12db70b7f697e3a60849144a5266784df7f4e8fcfd4beca9f6483ea1e19ba97338477375a720ed492912ada53966a000000000000000000000000000000002c893a86fe1d0428f929adf442e893f94f8c33559c6eb79cdf3e201122b7f432a53dbebb3c7c25afaa7e668e6c784418,"invalid input parameters, Failed to parse Fp.c0 element, 0x26b12db70b7f697e3a60849144a5266784df7f4e8fcfd4beca9f6483ea1e19ba97338477375a720ed492912ada53966a is not an element of the field" +c30e970031041c3f614ed9a08dfd406d083140e3f4cd6ecfcc8112407da1da6a6abf60a72b7cc83d7034a64278d8da80a83b1ba771becf7e47007e4ed43fe0eaeaa0bf232f01995f0be70a0acd2107d111c69ffe1b9140982ef3e9c232462c4867ae0777989f757efdbe389312ea604fbdbdd74849b82b83c50d5171bb81e973,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001d6c6b26f415c4b33c1af5f523b9c1c463cd7696cdbfc1d774c99535332e143caf9f2915512ed253963efc3da2f89606000000000000000000000000000000002829967b25e7b4bee63bf05d8faf24d622b627d49cf60411d6a0f4e6a42c1291649808dfb28afff8fe39c92c3669a053,"invalid input parameters, Failed to parse Fp.c0 element, 0x1d6c6b26f415c4b33c1af5f523b9c1c463cd7696cdbfc1d774c99535332e143caf9f2915512ed253963efc3da2f89606 is not an element of the field" +3833eacfcc8b02ba347a105bfe18ceb712cd6cb62292d0aebe3dbd26708926318d3bda83d02d871ac747981fd667f12ca2a32d87c5dc9525d2d2c561e73ff9826983f20455636563586cf63c5e52b44a1635fd1fbd9493ce3e5a0ebb96b0a6ef4f8a6972db0c122a677a691296977b4f5d74710496a639d70eab7a7291986a11,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000029d2eb0306b12aa7c34bedf1bca384db835e03db06aeacbb706c0cfb6a990b948f0883bd40ac9aced57d1741428ce2990000000000000000000000000000000032477f06e78bf1d8743756b27abe0d561ad6cec74ae4017f1b3932118aa9a379758a69be8f490705cd5cc2a7d6384073,"invalid input parameters, Failed to parse Fp.c0 element, 0x29d2eb0306b12aa7c34bedf1bca384db835e03db06aeacbb706c0cfb6a990b948f0883bd40ac9aced57d1741428ce299 is not an element of the field" +d85f0eb4394a754d986d48aa5b00fc16100ed53f7c3aa9d8982d2b389b2fd64e2fd0740a4b4fa938fa8bd9e14a033833b64a276361bacecc3f1f3c459e1bb8997fa5537e11778990dde4f11daa0091bbfa4a8e3cfca244880c4e737face65d04c0a99eee24d3557b1807971ec7a0769b46b17c7723d18ec4471d7f24aff6890e,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002dc4aa271d61f6deefe775c90338aefde638237772591cb5265c28e8299da76d00d059822623cb0c6360113dbbd905310000000000000000000000000000000022cc38e8f4b7f2e92009395afc86264042341dc32d4eefde2c2331d77362a94db38f7f18f0a494bd42eb9b08cbb54677,"invalid input parameters, Failed to parse Fp.c0 element, 0x2dc4aa271d61f6deefe775c90338aefde638237772591cb5265c28e8299da76d00d059822623cb0c6360113dbbd90531 is not an element of the field" +6ebf40da2bf0c1aa5ba9e99ea4fb93ef12aa7d0f41d94052ecb1d8a11930f90b9097f06ec1e5196e0a0d1384a17fe89b8c3ad0e2573bec6bc2bf86804f2aaab32a6ad6b903fd5e91f590fbe171aa3f00174ebc877f761603e2fe7efbcbaa87342e23d3e3ba49627f0552e231c8f2f601ed91785ac3b30be693aac37c33b49438,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001fc0d4600cf249074ea0d4b885053b05571622cf72a223d113614b14ca204604066c0eb2c32a1f76568750486f58f7e7000000000000000000000000000000001d745483ff9b0c7ba2c1d9dc0e27b3b353f9e0991a97ca2da6cd33bc7ac9a76184a9f48ce7d1c85e5fbd85e646167ef1,"invalid input parameters, Failed to parse Fp.c0 element, 0x1fc0d4600cf249074ea0d4b885053b05571622cf72a223d113614b14ca204604066c0eb2c32a1f76568750486f58f7e7 is not an element of the field" +48097f55dbb32a4fd8b9dc58a382a7e419c153e09c2204ebfe62546d0b5cba33a0be325fcf1055c42db0a5a23dbd46a7edce9e43319cd76f01200a349e272421c48a487e51c54007bd153b0f1cfd11e07055fb96a46d734613e9f38b5a007d4df6b652abb64bc87f086989f10087c9e386066c9a488adf24657fcc1e5b717068,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000324bd4799c46f6b637652f513926261ea571061ea330628abea073b595ec6634983d59337778f0226ad2213adc87f6d7000000000000000000000000000000001ea605628ab9790fcb14567f0e430d088fc5f56af559a4bccbc9ee306cb6dfb91dbadf13a36c9b04fc184c7d92e3c3d7,"invalid input parameters, Failed to parse Fp.c0 element, 0x324bd4799c46f6b637652f513926261ea571061ea330628abea073b595ec6634983d59337778f0226ad2213adc87f6d7 is not an element of the field" +216b2278158f3df98af5f2e8c39b1cf710c651fe267d79ae46d8c40f7fdc5c57fb1d09e718bf75491540033dda63c02d44b0bfeb17df10d2e88fe1550a452b4ddf9035283f1afc294ee68b2668870aa4188cc1f0ad75429123e1fb3adc9c91ec6d0d8612fa04abe37d33ed851bfeb61dd0b4cc51b80b903e2120cfca041715e5,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000224b6be0175dcb1ca0bbe2f3b7e2a8d29381c04d7185b420819c15bc9e8cf6aa911fd8bdf9c405448087e52f973c34bd000000000000000000000000000000003013bc200edc19e695a7c57460d6451b38dd1d4645366490da31212c5304b82d557e8b501f4a49d6d2d78bafc458b1f6,"invalid input parameters, Failed to parse Fp.c0 element, 0x224b6be0175dcb1ca0bbe2f3b7e2a8d29381c04d7185b420819c15bc9e8cf6aa911fd8bdf9c405448087e52f973c34bd is not an element of the field" +4ff6d13bb0967945ff3b6fbbc104296811dd349256d8e0eeb7cb4fb9f643664596eef1683bab8d1837296c94e033a7100e78fd9960e0a34d79b52b888550b7f5353a5716e5ad8223a0ec7bf9e61a0ddf0f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001dc12ca98e71bbdc0695ef0cfec262c7d1511d62ddef39147a71ed082abef5352797df51737ca8745a0172758961b9b4000000000000000000000000000000002e386a8d40b93879224e14be3eb75936463260a45151841edc74724e2b997e47c103ef988b8560509b3b0cbda5ff91d0,"invalid input parameters, Failed to parse Fp.c0 element, 0x1dc12ca98e71bbdc0695ef0cfec262c7d1511d62ddef39147a71ed082abef5352797df51737ca8745a0172758961b9b4 is not an element of the field" +a9c594fad45be7dd6413f14c3d464ec9170cad0357c51605f005f4c2ad162a710c7b2d490769b5db6b6362d5237d266bc9dca3eb3c4a0e3b94535c3e09f844413ba7ea9ffda87131452b24a9efcdc91d031850c3e9410884ad34caf91f3c1d37f57070487e95d10b4ff53db83437d1e4ddabde2c6cde923e5c034f5e2f0b6af7,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c464c090a12d6742d96d17e74bb28d9a62b4bc3f56e544583b40d0fe3638f8dd694e922d17a0dad61b7e7bb023aff570000000000000000000000000000000030ec46999576c6144aee5ead2c9e9f4bac026a44c9285dbd31fb5b0386a13bd4c31894fc14dd33af64edc4756bde8428,"invalid input parameters, Failed to parse Fp.c0 element, 0x2c464c090a12d6742d96d17e74bb28d9a62b4bc3f56e544583b40d0fe3638f8dd694e922d17a0dad61b7e7bb023aff57 is not an element of the field" +1d1a2965e995bd4380d4ec52fe8e65e716099ee11b8e2e4e7652900c01249da5179d3b3de804c90364cd40b3be1502d270984252d12be8f31321786c6a8b37e79776df26f740cf99209f359df3c36c71039fb37db4d87c8f7014230eb9b0d939059efb259f408999b221a5f758d0b540d7e0f7ff2a1903b5bec529796e9ae167,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000221be7d546f3e4a9e8d53ab8bedfa0d278370a91961bad26120ff5cd9c6ca918cf9ca4b79ff162256ca48e9ff614fe9800000000000000000000000000000000281a75b271bdb1af7ceb9455f5a69bddbdab2abdbeb84a773b76bf56f4d909ca182ccca56e54befaad28bbc816ae9dc7,"invalid input parameters, Failed to parse Fp.c0 element, 0x221be7d546f3e4a9e8d53ab8bedfa0d278370a91961bad26120ff5cd9c6ca918cf9ca4b79ff162256ca48e9ff614fe98 is not an element of the field" +2427787428b3175657424e795673f0b0165b9b33f97f225944c58b1a4f93dcae35654244282dbb2c5193d112cd3227604f7a9c28564665bcd762651eabfa39d9e428fab2c596f23bc3c9e9855b74295f03e4093207a86725fa1323c8741b6adbb34c1b92981bfb738eb578852439e73b40945c4e2dd12795d5b942f07beba5c7,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002a54c6e7781f4b789465c38d8fbfddc5bf0d182c9f738a6e151f24acdd28c1b5a55e951e017aeeafeb168bf5898bbbd000000000000000000000000000000000276539c9195cbcbf13b272446242c4b9770fa6672dd495ce4b9799fc2e9b24bd6d2253b822cd28f1784483aae73f1aaf,"invalid input parameters, Failed to parse Fp.c0 element, 0x2a54c6e7781f4b789465c38d8fbfddc5bf0d182c9f738a6e151f24acdd28c1b5a55e951e017aeeafeb168bf5898bbbd0 is not an element of the field" +5bf25b5070829e3d5a66ad24ba9930f312913390d5807a102287238e5ee442b901d3edde6bdfb04cb6cde05160345f4fbc8f17293a3ad87f6773df65b5592e41c764884db0eecaa2b53e5545d262ad490964f9e557e5d754f1e26f051b91aa25032aec6d7b083a888e0b9e2675806c39565b7f136d4efd3026abed94123d82a0,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001ec781799554496573059d981c89df033decce3edba21a71eea1d5e2f913daf4057ce3bab94df1602a1ed754793e58de00000000000000000000000000000000247f973d5e6f2db5db0b5acfab1a7524bc0c7f8cadda6e2696d66605a9b3f930ced76c764490c63f871a8c107b25dfff,"invalid input parameters, Failed to parse Fp.c0 element, 0x1ec781799554496573059d981c89df033decce3edba21a71eea1d5e2f913daf4057ce3bab94df1602a1ed754793e58de is not an element of the field" +2b8256c42a814902775dd126a9066fd60b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e75240dbaac3f5e25ca3d1d50ebb31258ec4409b28b3cd3b47f0cf5113f3e574ece38c87ca1f4152a6211e79c97a12e48b3fbfdf1e7836da8186e5555877a14272777,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000213de1901132e7b143b8af3e57a6d068999d8411b7eac84469fbee62c08e5c1d6629ced84d438192ea0617363e3ce66a000000000000000000000000000000001cb63508997cc2f16097b92efba21a39a6661a3e2b8ac1e1dad3f46a6bc46ccd90084f3e74a056dd662753b6052b3e77,"invalid input parameters, Failed to parse Fp.c0 element, 0x213de1901132e7b143b8af3e57a6d068999d8411b7eac84469fbee62c08e5c1d6629ced84d438192ea0617363e3ce66a is not an element of the field" +5ed307c01d9e29a0571de07c62d5fcfc11a03ea0131c932eb3cbea5861cfdcc3a6ee533ba6e6872f98a21e608029b7be35035c5b88c3acaaf285cdefa2460d971c1041635017d16beb4d8e0954b00ab91812fe3ea5b2242ebc6523e247d98f6b84ab934d5787566dc8d05b2167af54658bdc81cdea4a2c9e6302545db99ee308,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000205c39d9028132db05683e02043c4aee71f896c339fa5ec98ff3aad0e65cc2db66af7ada4bff9419a373e4b3b027295a0000000000000000000000000000000031f744e060e161acbfd915e23ba93ade72f3c3b9e98a300d97268e2de832e20b8fa8cd5db3aefcbd9d74c8fb05270093,"invalid input parameters, Failed to parse Fp.c0 element, 0x205c39d9028132db05683e02043c4aee71f896c339fa5ec98ff3aad0e65cc2db66af7ada4bff9419a373e4b3b027295a is not an element of the field" +ccbdbbf71685cd63c5f16af4bca293b718e2f93c58cf7f9c048ffe028f63da5cca326d0f328cf9be4aae596d36b7d646bda72a8d8c829cc3052b9ad25e0a53d778077a51f88236dba6d16d7fd681c6310aa7d0ceebce339e41790fa8959b9db5ed5544d7196648c85e0dd01b67e5dedccf0e4cb1ab51ec8cc96ab66f1b021a91,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002585c32357decdc293cf1515e7778cf841da6fcfcc2db20701dbbac91aa75d0058cfde59a2f9115520acc6445c49e2910000000000000000000000000000000029d35384b589ad7328203863e7d0e40a3cbbeeb7b84e918f4b856e637ee05473e336408a346d2df24edf6c0e770377b6,"invalid input parameters, Failed to parse Fp.c0 element, 0x2585c32357decdc293cf1515e7778cf841da6fcfcc2db20701dbbac91aa75d0058cfde59a2f9115520acc6445c49e291 is not an element of the field" +7a16e23e37ecffd514d47199cff2494101099d39d6f0a116d52c2176034d165d6b520bd69cece5cd0751eb2f86376243fc23f758e5fd20f8089984143db18aee38bfe36f80196ae6fa3a03c6e81cb3be019c24c04b03b302055e913dc44f1d7a22ea450381fb11e4323f74cf9362255966f1bd8b2ddb620b7aa7fa31fd41ca0e,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002eefa7331e3833f190ba7d0c2c826a3a98c51cc2d48081625f1ea89c438e8dec6eea13a5c2ae35a29039c1fe8448c28e000000000000000000000000000000001e97f43350e163fe1b11a83e15427efade977fb98a9d9efa6017a936556a6d7218609cf64bc40fd1017ddb7bb284c32f,"invalid input parameters, Failed to parse Fp.c0 element, 0x2eefa7331e3833f190ba7d0c2c826a3a98c51cc2d48081625f1ea89c438e8dec6eea13a5c2ae35a29039c1fe8448c28e is not an element of the field" +23befa3ba8108946fa13805e234cbdff03f6e7289112806a057f966b6de2cf427ab32e4cec0a0887f86664ca5430707c7572ab80f27e0f4b33e9510f83500d0cfd0bc405e3970dc2bbd7dfe0c54b7c640de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000021b788286d6f6576c3a9a4a0e90b5f6ff87319dcff93d5202404db6a2f50da7aa11c5a596586844ee555fc7559c72054000000000000000000000000000000002d130201fb7c74822c354d8874322017f972ac3c3839f39e49ef32a870b3ab37fc4e7142748b78293cc3a3d7bb8b05da,"invalid input parameters, Failed to parse Fp.c0 element, 0x21b788286d6f6576c3a9a4a0e90b5f6ff87319dcff93d5202404db6a2f50da7aa11c5a596586844ee555fc7559c72054 is not an element of the field" +7f4202d670fc3b48eaa92e925f48821d0c3f8af533d0c28e91a3a34db15554515db0e9c2933ead3af3fc6d28533f06fb6ead97ca58c3536a199b7dec47128b7691b6add5ecde655047ca9f090db69a15060b5321cc3a4eaccb66815d10665fcfe37b5c8f15714fdcd9a377720209dc8aedd9b457fd5a072654194748c1320ec6,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000028863c2aadee5d53df14e228083d60dfd5ed149f9180602794f211e4fc52afe1207de4ab91297c9799d285b0f5195d78000000000000000000000000000000002b4485fc02312e9d2806a24d3f5748ae6c0048548d70d622c2cfa311551a3b9d5161283eb32e8720c915546426210d00,"invalid input parameters, Failed to parse Fp.c0 element, 0x28863c2aadee5d53df14e228083d60dfd5ed149f9180602794f211e4fc52afe1207de4ab91297c9799d285b0f5195d78 is not an element of the field" +ccc7b90de37e3833f0f8fc9bd029231307060e722ee27393ad994b68893119472a2d6dc15473dfb73d6e8f6ece2e5d461c23a830ad06cb0852a2125626d33ff1d94959e16f6d780628694075ba5aa1a401fa0f25e4c0266e9115be6c0900dcee1f7714f051121c0aa3871f30598abfd3c147d81fcd3ac74c0dd0b7abcbf9e539,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c1864973e8da5c9b66244e96c438b28048a4ee8205ed2a5f167d1cdbec959e6d74f5875449d3aa8604dab0c4d2d35890000000000000000000000000000000024fd52fc5d3dc37d617acfa6c0b2a934561c9b4ba2d8b31c4b868f6128409c29b2ee0c8ec58a2555cdbd9ce6ece9f018,"invalid input parameters, Failed to parse Fp.c0 element, 0x2c1864973e8da5c9b66244e96c438b28048a4ee8205ed2a5f167d1cdbec959e6d74f5875449d3aa8604dab0c4d2d3589 is not an element of the field" +426a4e2317fee033a226a91a52a5830f119a9f9adb8d76b5af509abf96d4af87d7441d5fdc6e32b6875030a235d4d814f6dc4e453cdca124d309cb35f285e5f4013245c11fe96ec99d4e46eae2d703ac143ae544ae1529708a87a88a69ca1b56322ade377cd975ddeac8f650b70449b8fc03e92037520e2aafbc7f3c621f041e,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000003284cf332f3ed284d0621e5c66c9da9113e88df038948105ee29c684b85ba97d7b6613f31410d11145cabc6f568a89a900000000000000000000000000000000330e2e45902cd4491685a8da6d6dc01064e50ae3b282cef1ffd57cadd1430a6c9b0f56af208492f6009775a737b78c9a,"invalid input parameters, Failed to parse Fp.c0 element, 0x3284cf332f3ed284d0621e5c66c9da9113e88df038948105ee29c684b85ba97d7b6613f31410d11145cabc6f568a89a9 is not an element of the field" +a1d24ee88f32d6d62ff594d097e0340b0ab84ab6421acd0a8921ec9fcf12262ce2b9392c8990b56bac6c58fd03900084ab665cba26454ba74e0bb1accdd1142113a5fa1674c20c97d08608d200f3f7610e04a1904e93af93388b166bd9644c52bb6730150e8a3d2dfb4f06547d53422963c9324dda207988cc221da6341e406a,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002fcb2088a83c4acc4fc38a34d74fdd2212385c136c4d7d73d03e667438a39d22beb5526e78e6d64bd2d3d0d85572f20f000000000000000000000000000000001d228ad9b4aca98c82c08dbe0db874772ae0cdfcb915b33675cca5fa862398668aca24f74d7e007bcd11c74d14e94064,"invalid input parameters, Failed to parse Fp.c0 element, 0x2fcb2088a83c4acc4fc38a34d74fdd2212385c136c4d7d73d03e667438a39d22beb5526e78e6d64bd2d3d0d85572f20f is not an element of the field" +29b83950e79750e9827ed92856e4d1e10b28667837b656fd0d5379b07bb9dd6d7f5289ddde5d571ca2c9d25b91d21c04d8eaa32710d5615e5436060576d7c77976abd8e8d55cb82ccc34a82597667e721473608d46246631b611a383fe6b9705207193ef9083f1815ae91abef0c33905d2387e8ae15e9ca893daa917a28bf4f1,"invalid input parameters, Failed to parse Fp.c0 element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp_to_g1.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp_to_g1.csv new file mode 100644 index 00000000000..a21fdb7f16c --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp_to_g1.csv @@ -0,0 +1,104 @@ +input,result +0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350,000000000000000000000000000000000d7721bcdb7ce1047557776eb2659a444166dc6dd55c7ca6e240e21ae9aa18f529f04ac31d861b54faf3307692545db700000000000000000000000000000000108286acbdf4384f67659a8abe89e712a504cb3ce1cba07a716869025d60d499a00d1da8cdc92958918c222ea93d87f0 +000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,00000000000000000000000000000000191ba6e4c4dafa22c03d41b050fe8782629337641be21e0397dc2553eb8588318a21d30647182782dee7f62a22fd020c000000000000000000000000000000000a721510a67277eabed3f153bd91df0074e1cbd37ef65b85226b1ce4fb5346d943cf21c388f0c5edbc753888254c760a +000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed,000000000000000000000000000000001658c31c0db44b5f029dba56786776358f184341458577b94d3a53c877af84ffbb1a13cc47d228a76abb4b67912991850000000000000000000000000000000018cf1f27eab0a1a66f28a227bd624b7d1286af8f85562c3f03950879dd3b8b4b72e74b034223c6fd93de7cd1ade367cb +000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2,000000000000000000000000000000001672a8831d3e8bf9441972969e56b338594c5c0ede7bdba5b4113ac31ccb848dc2a2c4e23c0b9ec88bfe7165f472b427000000000000000000000000000000000a86e65037cccb5281389512673068d6f91606923629905e895f630059cf87fb37e716494db288958316c6a50de65ca1 +000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de2,0000000000000000000000000000000019adfbc918cb74abc6fa0664dfe60697b233f0663665d2cc133478db4d6c9a41309ff09f9af9240037a7332bc42ffe3a000000000000000000000000000000000d31ffd63837cdf1cf2a7b3fe23a9d86c08f3a7c44ac4fa52d21b8c235f0d45f85c036d80bab332034635845deb31467 +00000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799,00000000000000000000000000000000192a005eb944f391251402ac3d31c30f0b2d77987ed9928d244f492f96c1a0a06a7cd0be4bb3dfe3c484ab8ac5279a09000000000000000000000000000000000b99b9e7f0b51a2e0d12272fd0d9ae65294dfd34d45f30fe446a25b225316ef467b02acc3b6a578e054e612434096d7c +000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c,0000000000000000000000000000000019dbf865a67157efe65fa7171279049864bf6c280d3c3462e93425bbf25f9cbad6c27885d7927b5cdca642df48ceccd2000000000000000000000000000000001606be1ef7aaf56349e5179b01b89e172e463bb3446792d5210452905fcde42522f9719b9e7ddeb8cc3f227eacd55947 +000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60,0000000000000000000000000000000017d476fdf0be6b09206dc83cce64c603a6b911f051e9191a2473a1bc6b1dd2c6e9bc4d262edc936f62911460f0b648a70000000000000000000000000000000016f824bb325ff7f485a8e9d116f4a56ea71ecd2c11b2a4d119c208cf323bc62bf1e9fc609230c571e7830a956e140e47 +0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f157,00000000000000000000000000000000145726f8479d7390e7a21cd31dfee0e6203115e72d04c5a735feb2cb688ff74944bff2b1af1b6368b4d095143662a1300000000000000000000000000000000002fd68d51753faa242bee10148c0e473f4110fc7b67848dfbab7d7105090648534854ba75890e099cb738d1dce604ea4 +0000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac,000000000000000000000000000000000671b0f33b0f1ea3386e6876452989416c7171e283c4b0c375e840ea05e7fda22aa03899b50e59e9ca5a87039b2e732800000000000000000000000000000000031bf8caad5ce6a0d94f14693da0d551dd4bfd2c2163c8e8d5a448956153f63ce2ab72f03b97b560d67933887e83be1b +000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c8,0000000000000000000000000000000007d67999ac2fe6ab93591646905f23aead0d37ca43573ab02dc16c2b199086f788a8a1de6b10aef4f4d772b2e12e72ad0000000000000000000000000000000003700b150ebf60cacbb2b7bcf969b70edb57b34b5c772cdf68d42dc9f1513631799b9b9041c5e94595ea848e195aa730 +0000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3,0000000000000000000000000000000006cf4af50766ec08696c9bc0d9617c1f0fcb0ea1bcb576179cd4537d9d31b373bf8e3c5f5fde2c21e44917cf1f51ff0a00000000000000000000000000000000050a9f7d8490ba2b6e49762cf2bfce557e39edb51ef03128b64267fd3c6b996e95d73b26cf1965d427e3445b1ee4d133 +00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e,0000000000000000000000000000000009fbbc6ba7ec2315dc18aadda7b2e53180b904c5f1cbdca1b2f42ed9c6675da7beb4007ab6639520c4736bbf2ee3f04500000000000000000000000000000000113f0bc737b2f3a141121ef236cbaff2f34502aa13e121b857baf327f7be66be97867fc6f752555835fdd01610e30c77 +00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be,00000000000000000000000000000000047dd479fe99840150e73e4a8fa6be74a9b7d743e21cf33e9d7a9fd8700feeccd5111fb037eb3b15b79d5737ec4c7f0c00000000000000000000000000000000000ba7f57ce062eb9c67d84eee64d64d250a18454bd63dc5a136f5341214079eb9269eea7c4e0d836dd8be63a8a45c04 +0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a,000000000000000000000000000000000e8cf94e68b03d1f6a3d4eac7898f143324d08f7544aa9f952947e9008d2c14e46236667173266d82f5e41887c6f614200000000000000000000000000000000089a1ada37f30b1f6e3a6613705992e9708d0437611f1de72a9f696ea5efea6793f285bd5badbdc20af64df8ba95c79e +0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3,0000000000000000000000000000000011e4919deb9eefd13dd0ba5184003ce34ff6c2bd8920dc49b936917a7b6aaf1c7541780b5d0e380e6c808f093a877eaa000000000000000000000000000000000152dbb758aa5f60b8d0703eb30680857abee717114b8cc5f6466e70856f19c76a88ec6c536e7a679c177986bf636e6a +00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e,000000000000000000000000000000000750f69c43c56df2c8524b4ead9f6cb3ec16d3a6ec913254e585b0d8518e53c18e0e93dd4594adb926c51820de6001c10000000000000000000000000000000011f5c985ed12f72b6ec7e222dc8d93da520ac65476c716e231e7142cd3aca49b25dbd716a8f587006e4a2af31c37956e +0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad,0000000000000000000000000000000012652effba341826ee7bc3108404f5fcac84776c6f5fef5d440454b59f04afc2cc87f243265248445c7c2bfc14493ece000000000000000000000000000000000c0fd215b7c012da4532c882d7d7f83ebf133d58acaf8b5123c1211aae5929c6726410631c7f9347456448df643c9ed8 +000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6,000000000000000000000000000000000c444b07e9ee5dc366c63ba30f1b17087bc4c548963caafacf223f4bf5b5bad1f9b51433bd1942978f3f5e5696d5056f000000000000000000000000000000000453941626954845d89821df34efc6f81660684b08f03fc42da54119d10f1f95357ba75a0962961f1487df45b0c534ac +000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20,0000000000000000000000000000000013d5fcd7e4a0b1d7d8c7b242b46968519521ff8bc4b990a56ece26053d4bf884afd24a00670911f943522e06fe4f87d1000000000000000000000000000000000aab46534de37b5c6d206959a1023ad4f20ed5966bc3fd1750c1758ed806f077444ac70e9943b4e8debaecf208817a5d +0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab1,000000000000000000000000000000001440f44e3964de59be03a6c69affbb3b44ffcf4ec4976361ac49c31a23f9f154f91750533ff2425d5e8fcde0974a91d50000000000000000000000000000000002031eb89620736dea022880e5188145f080537b1aec183db70bf307029be21a167fb6456bd1a47a75626280f78442a2 +0000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175,000000000000000000000000000000000a2d7297376216582c3938c2aab0a26494da7d9df45e1af7b4f826f064467a939ad99134be4c9b804b5bf273e082c4c2000000000000000000000000000000000b0a4da7cc585be1be6c091006fe831edb6f6eadbe3ef611041efa3d14f442c9768feb2958efa161e0adf5c321d7d522 +0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d23502,000000000000000000000000000000001314d7faac7b4d5003baa10cc432108d6bb7f80bb13991f6ac45fd7a772f31cd43345ea100b05f2ad73e3bf583e7e7b2000000000000000000000000000000000eefa97eaf2143a991343a8823d8b362f77d8370421bd13a9a6cc4988544feb0cafd3a797a28d27f4f8d361cb7f49ed4 +0000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3,0000000000000000000000000000000003ee4f3d29cd9f29a2e559a86d8204a1d65598e7788d585b145022de2c19022b122c1f10423d3bec769545d656726f5e000000000000000000000000000000001803f26af468740849a2737a42e53098b48c0709415247023aedb111c96043e3b13de300213e5196cc3b678f8be0696f +0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d9900,000000000000000000000000000000001800b9766f3e621ad7a8d1870ce16c8cd054c87d7fb100120a38c3368cf1879859645874b23297957fef6cd8f9112bf800000000000000000000000000000000091a8b69a1f4eb883a25af2a3a0d1e384ef7a9ba4e8ff8811ad356781c79f631ea20fcd0590e94b9c1841e6add2b848b +0000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844,0000000000000000000000000000000005fe170feabac3805c3eaace41fdaab2c9ae7fe609ba609f4ebce2d24c0d704d847efd510acd8abe5aeff2eb24e781b80000000000000000000000000000000003262879ff5c9831ebdd0de9df478923fee72a8829378f40cfec310a41110ad22faa759276e3b9e015c86c94c3594e0a +0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08,000000000000000000000000000000000e4110b2efc984c4d7affcbcf5cbbf919c55f948ac7412dc120d30774924d6020a2292f27b8e716c2b5045a561f2b14300000000000000000000000000000000194649f6906daa0394fbc1d45355e17d62f6c22a9e772bd7fa5149e29ab2ac6060d83dc5d70fad75bf3f2c7917b641e1 +000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2,0000000000000000000000000000000013af2a5f26d1f51da0d80fe7c62369ebbec235faf4565e62ba475e6f58418183efc8b9906196ffda72539506243e0482000000000000000000000000000000000774f3096c99bb826792cfd9243d8cbb1bab54fccc3a6347daea74ff1c8aebafdd971b7bfbea5b9a0bce243372caad6a +000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb8750,00000000000000000000000000000000107c66e91d518789be416606058cfa8e9df478fa097241fc109d065005ae927d83563b72410e5b207d1556c2ee4dd67b00000000000000000000000000000000148c208e55e834c4e4fe20c02f517c21030f60c74b1a3bcf70bb2311cfb9b7548837b9187910bb7e8d1faa40ca8d6d92 +0000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75,000000000000000000000000000000000fa72de55fc2229c0176120fac3e0a64c4498bcc7b67ca40b92d47a76a9db87ba498b72f06345c61d59a3d37c51153a300000000000000000000000000000000001f0e176d0987b8ceb7ca0e5ebb491bab0be17282cace8e03d52c986483026180082f86196fe512ac6bac58ec4cd024 +00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be1222,000000000000000000000000000000001655eedb905670d10d2f979962e864d68e9491aea41d073a6119e5bc0ae74216383501a48343d7099b93601f8b67c00c000000000000000000000000000000000842846147959f0f81efc6e8f515a9c59456637740bc15b2d335e0de45890cdd814ca7057c5d3e49e48e5a250c5dad25 +00000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760,000000000000000000000000000000001098de70e8748daba7bbad52ce344619d3b5374821c1f932a18666ea0a591b24ece05004546cd519ba4d78c9747c57cb0000000000000000000000000000000005f537b6a394458ad51c2e677b2d52974a714bcf6a7474e748ad7f1b28738b6b874b6f49bdf19479bce4ff6c6a47de1a +00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2,000000000000000000000000000000000a27f7fde0c79210f4b6cf59c97ac773c9766fdab289225c97f6cf42179385cf18f47f14b7e481df7c19418c79dfaaba000000000000000000000000000000000874f21294205152df3a4fab2ced482d325274886d8105b61668074dc8fc90240a715c62b2a2864901ca7a30f12e76a3 +000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091,000000000000000000000000000000000a6fd7355965c9514dc7237efd262fb9dfd8025ca2c56165e22675e615095887760ecfed4a2080cd5a2b8041ff26578e0000000000000000000000000000000019b1e02c9258fe62160d92eba8640ffd79b3bffb8ca4d602ca6c059239047c5563049758911d0e6034a25ec5094b1f33 +0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18,00000000000000000000000000000000013e4cd06b8ba7b5efb70feaa03550bfa45c7c2c79033c92b819257b2ddce28d501cc836a5ec81bf210bed671bfa66f100000000000000000000000000000000165d806d235d41f21e961795ec3da4f1b0334ba6e71ce384445bfda9e5d89e448d00253ec9f8d49825a230b25ffb2848 +000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1,00000000000000000000000000000000067c19b7c3dcf8b43d6e83dbda7406f5f88b06cfa0d7d145201164a1f06cb5549545ab28fd1ea8c1d5a662cced00822a00000000000000000000000000000000013aab7ac4ebce4686ad8a05e4eb2f60ebdf03c4f4ca0111bb1cd3dd5fa7558f1cf0dec394d0b616cf557f3811bc2104 +000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f,000000000000000000000000000000001673e66a7e558d533be5b855df7c3bdc58f1fb0a3b268b84b4fc25a3a8a211c4c9c8d884fc62f00eccbadbc96dadd7230000000000000000000000000000000016265b691fd43045567ab4fc7e7efa63c8430c8130761b128f0ba7bf381a7cb81bf05aea2526b50ff6e48a87c8ee9cf6 +000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192,000000000000000000000000000000000b36ad42aeacfa47d77f045de527d5bd4fa5fcf25ca3caca99e3e7980e283278e013611d1bc7694bb0b1b86d8589730700000000000000000000000000000000136290ed913b8669f522e16103ff42733a57c1026f966facf4a2d385b0bd52668925d748760975ca5a132d00deddf675 +00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a77,00000000000000000000000000000000076fef7b61f4c687246991d6f735d6f89c953476ffc193bacc1f3cf9573ed47bfbf6dcfbb3da1ec1bb764a9cc9b1c26b0000000000000000000000000000000012b6bb88e8acd6cd0ef1929a79bf4d8b10ec3fd575fe460686921fe94aa3a472cbc7aea543ee6284c368f5ef2c33ebc0 +00000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510,000000000000000000000000000000000bbc25f7788b0031f1487ef154e877c5ae277a80d56b3a24a39c3ee94eb7df81a47bbff233c1baaf700829919e5254690000000000000000000000000000000019fd9d1237b508d06d7b2ff807c15c3ab36e6eab7e5b9f145bb2c0f2ce8ec96ca3a24932076abfb74eca85744eee4044 +000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b80,0000000000000000000000000000000015eca2e3d36d619601b0f40b01add7a708bbe59d04d5dfbf12d6e473e252505cec0cf7ea1c420000d49221d5e1ba6b91000000000000000000000000000000000cc6045184317aaf2bb8a904755bf48df9e6754e3a864037ebe0218eb3cd1c0a54e50b95f9e6d318799a72fac8d4e262 +00000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5,000000000000000000000000000000000ef0a6307d4a3e92570cad673ca5212780902de416e81d15638ba654951f442e852b53255d7bc4d4e71098924d69f5a600000000000000000000000000000000156abf6f096326c75710300578f0cd946536e16bbf80034c6dbfe454565a501c268135118745989e5274ca2431ca5155 +000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072,000000000000000000000000000000000dc94ea6018ffc5838cb7cb00df9625c0c09701bbf19edddb735a3659b385bdd09e9a7d6e869720b727ec59ff3956d9b0000000000000000000000000000000000a20ea6360179bb6608bcbe4879df186916ee71b3ff7a1dd0fd137a0e9dfb135bfda2c66d1cf8d358d69934012a1a1e +000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03,000000000000000000000000000000000b4e11f70679333c064d06180df6b54dd1df20ea216415ecb9b704bf4b206141fd841770ab77de4ab2400a076cf9dd04000000000000000000000000000000000ad8c02345e141396401221bb36a2ca21096e89aa76fca4121066da74f2f54b3e2c4049483d9855b7f3159ef448c120c +000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb,00000000000000000000000000000000136cd8012cebf1639a396f331f73f0da6c114927559cc595f01bad1a18046ae8364858fa262ae04ae3f3b7d13db55a86000000000000000000000000000000000393a915629ccaa9ea06be749f3053dfd07061cfa24bc0aead12622c7d14c085e2994178bfec98b3f8867ac5b4b7a05e +000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e,0000000000000000000000000000000019008e485a0a9c2f73a79bfe31782a17952edebca308bbc9f90e2ae15525bd501268a1c38c669de0b4e4fcaf1194591b0000000000000000000000000000000009c35254702eb7e3213fcbab62946ba79b7375cc320ee1733d8bf5729d378d1a98fb27d870e27c13626c35cb00a6bcbc +000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e9,000000000000000000000000000000000059a3315f8b6e75c45e32843b4ff2401c41e1f6716a5909894cfdc71a49253d2cb04ec416d204bf0bdda051ace606260000000000000000000000000000000019cee852aa9fe28e1da49dfbfa7901220616f464ba447480c2421fd6d3a5a818c778510a04cb6557d27f7ef9a78f2fb8 +00000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe,0000000000000000000000000000000001d157c963811725ad533539f17acd16ac3aa22917ecb2198d83a3ba396955f2c9654c02fd42e3d4ee6156cd148e9c270000000000000000000000000000000008fd299ddabfe525075f548a31ffc990a3626aba0369bd0accd0e1968204c8e1085c6b287b370808609178ec8ace2d0a +0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf48,000000000000000000000000000000000ee6b51c5eb4dd9c27a61bc2f3480d799cc4fb88414630adb3961508c7067bb186682194af406f811296228c068e6415000000000000000000000000000000000b878c207bc4b61e827ee09a7825fb216a63ddbc4ef0522b8a944bcb673ca368996c31e6513504c5deb5325ef4df0459 +0000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909,000000000000000000000000000000000658d61bbb2273e8969269dc16e16be93ef82be0668c3a164097a1c0816bb4aa94e5f70ed8d96bd15d9acb602d70f8ee0000000000000000000000000000000008f696d49a5c6f3dc971699a5837f7b3a20e222d9559d899eade367ce684b60153dfb75a9a8b81d7359a93069e2d7d7d +0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d5,000000000000000000000000000000001313f4cc65865c367cb5c1c96cf30c7e993207e9ef4b2fce9db01181b1192520f01a0428668bb9d33eb857d9435939df0000000000000000000000000000000006b5e883fc24585de3b0a0b83cc1742050e578cb57e89b385e245da0dd2832852c3fa5f31ccf55e6744e9cae6c2f705f +0000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5,000000000000000000000000000000000f3fdb57966f9ffd0e20b9ad3bfb4fcade56468aa598cacfe388cd3b647d5966350586daa4493de23703a1debc82e48900000000000000000000000000000000044ff5ce3b9bed637709f9105bec0d86b4f0ea2dd86c9c3b1324637cd4c0fe5a4a965021c51279fc03592414e7968d23 +00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0,000000000000000000000000000000001288807e8f49323b39c5d592b97f19cf76f2f642dc4fa704004789d28452ce7a02a45f3f83a8d9875480d380e76df09400000000000000000000000000000000123b15dc7f166cb7c2c106cfd2f7c321a9bea9e3bdd118058c4745b6666a0df2a7c7fea16887a4c85faf860fe48a3787 +000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a,000000000000000000000000000000000adadcf2f074679ef3523c10674260b0e40106cca8d94b05f83e2b27d8da8c00dea4215a30275ea5e1a8fd0beb45dfb30000000000000000000000000000000003c2d436e545163abbb18ff7c8e6db1e55c733c75f9594c695c66656690e88995f9f266c2620e99075d3b78805e3ad41 +0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f8,0000000000000000000000000000000016cf855c1ea449c47236065ffe53a4c6afdadc08f1eaa26a8f79ea92a7a119b26dea1dfdab4db9b02b3dcad2c077338600000000000000000000000000000000071924c7d4e6aa5234dc921d288dcad3e49b44d2f455d207f3641f4b5b5c809b84c04945df08e785b3d99eda1807611c +0000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa,0000000000000000000000000000000016d80d4689e959233f05a3266628e233b747705bf6d6236771d5e697da03a0daa2dfa88aa5a3a5b97bc4517c467e94510000000000000000000000000000000003bc451286fec0e7a01d29ffae4986a2a3371d4aab875547cac05f759f5a52b8cbf84798b5b3d664a8692b212d4e974d +0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe,0000000000000000000000000000000017297cdec2f6a54cb11c1fdac799f252c72dad52ead6c29de61d64e56ea0e0a1d3a60284029323e35d38a4a25f82fcd60000000000000000000000000000000009beaeaf3ce2c9bfbfe5e04ceaee87460d760c4c16caa7b37767e16b8e97cf08bdb6d30472b3027f66803dec1ce40eee +00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13,0000000000000000000000000000000015a163067e8039be1c365804887dfbb78a7a699f0308c8e26519bf1c86fbe6acffaa26f0e5a2a380d1c704fe84d3bba60000000000000000000000000000000013f94e107625aca9c4346102dd5f09d51e445fd44ea67f171048e8f9965ce3496e759610c078404d41add90a358af482 +0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce73,000000000000000000000000000000000b0667e2b7c0fa318c5c0e66425f8cbb8217bec845bfe56997cdb9d0d915131b81e82419a4533eb573ffe103077f35c90000000000000000000000000000000018074b6e0cf144fff9da02a4b5785d21762952d4ed23b1430d6165974f49521b73eaf98973f7967ffb35cee92a2b5269 +00000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b,000000000000000000000000000000000b5e953227f4f5e2070482cde7fded231bb0d4649a626d356cab2bfcba6c1588ef38c62cb2c550719091206727715dec00000000000000000000000000000000095f29eab98321d334f22b4db0c30a0604c5c385fd222a71399763f5c815e04226d9d06b460b9e3b44d1ec127d20315d +000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc,0000000000000000000000000000000017946ce626cd11556f85d15b85044fdab0456e24b5e331886be860bf55411a03886738aed9b19d52e91a94ea5cc5f040000000000000000000000000000000000cbe613ecf3c8ca8a5f0617c64647a609ce6e8fd40ae42f69a928f4ba78f7038254689bac2dcde7a464a03d5e26e34ce +000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82,0000000000000000000000000000000003b425300fc1885f2e932a469a8137bbf9df9560279a5ba87a13e7d4a461489bd8005054f14fad881e06aa46e4333d920000000000000000000000000000000011dcec636ef785d348fcbf9c59a82080b8f2c02d7ab954bc17af1c163a5383a36dd3948ac9110c6afb363ccfde2b6682 +000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df,000000000000000000000000000000000e545a87fb19f7943e18c75f7a173d18ef8129b200222bf6a2ba5a93a92c47ba7accecc4f089c42d6c6bb2425bd1786e0000000000000000000000000000000008c005ef6e5b25e84a8251add6112db49637c2b955af8cd65d029f8e17abfc660794b474689a00b5d2784163a9a0c241 +00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb,0000000000000000000000000000000004cedd2deb72d9168ab5704e21d9a5d85b65ae1510a628515753e85425286d9825dac99922be4a19870700956a65ece9000000000000000000000000000000000f5b0efbb2b327e294246fe862ac01dcedc7e728b938edb9c4a6128740b7d192cf8ad877b869207fb6d1453d85db895a +0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de74,00000000000000000000000000000000031841f58b82f7e44aa03f474f18360128aa5699e748e4e2fda1c29d3cf165dc3542b90f09e415e92d73a162af38ad52000000000000000000000000000000000028cbb3ff58cf28f6dc876c2c1cb147bd6af85f3baabe253e9a1dd69687b3a46d4604d2d92d08310ecd7c90723bc7c2 +0000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5,000000000000000000000000000000000e378bf9d1d65cf3a39dc2b3cd2dca8954270006abe048cc29183c5e7c1cf464b21a548679fdf5af8a31e198b69ded53000000000000000000000000000000000865c90b45eba1979e433f71c93c7b3b8e90d3d12a3c2153ab7c420f507bbf91edb593d3beb3899e76d41674b5ca33d6 +000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276,000000000000000000000000000000000844679db6a74e2a1f7c342771616c446c5e240e40e1f994fcba49f8ab22a7fe06b6909f50ea3c49a8fbebaf2b22b0a000000000000000000000000000000000090afa19255f7b71630c466d6b180b2100f8ea6b7ee2085973e409af8027859b61e0c46b639120ef6f3ee1555aed2f94 +000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7,0000000000000000000000000000000009ffb2b0054536d714944c6c96f8c1ea902e7109d4917a54ec551d811ab15042f843e158a9e4decab9761cb10e7c3e24000000000000000000000000000000000a6c7a862b951aa9f8c2d1e8ba30af8b7909e9721a06479d186e46ffae3ba09f5f52561c7c4c34d121be1304650cfc6a +000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031,0000000000000000000000000000000001f9035574fac4ddc3f114a79938105d95ad4947588028b60e2926a8e0fd78710434edb2ab6b761fec43e458e19f0e200000000000000000000000000000000001e86d391172978aadc652b1c5d28dbb26a5357d1deb522bc280a270cc63cc18284e5b05033cd7ce1a6eb962a5b7e268 +000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb,0000000000000000000000000000000002583631492e3e0bf080a5f67334f7a2907c707a678bf63d53badb3ed90305a6eae895f7842a5d44a2110585d412ed860000000000000000000000000000000018719d22fc604567689870d5a5b043ee7234927b1e878dce88be212a8b0981e64f3cf9e03dea94439f504c846c6e42f9 +00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5,000000000000000000000000000000000fc1acd8490dee632c51e67356601295291b107087efc2483c1e1a41fedcff244114608c49f6911a4249a59a891264140000000000000000000000000000000019c402eaa9ddd6ff3c72a7d3bbc736cc867b437dbf56c9941ffdb2e0cd60bdb7ccbecef3d62aad22e97c1d96a328e8db +000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2,000000000000000000000000000000001785abb82ace5d8024c97b3480fa69a65f5ed48fd3f5416f068690f8f79295d13929d01922c562277f65293abf5d739a000000000000000000000000000000001076dbc521375a1431b24f7d03902491b80b1856cbfd3e759b520927fc559e705801460afaba6991b032d59739c25059 +000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b299,000000000000000000000000000000000bf99b7aa1dd96f57974fd79d5823d1f379bc0e32ce416e6f89a499b82727081aa78529dcc76257d1d699b9979ee23f900000000000000000000000000000000067044e8b0cf455974850859bf76bca780f1908beb06a64a7ee8db2ed54703431c354cc3d7576fde0b45611a2f49f862 +0000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf,000000000000000000000000000000000d102c354adf7380053c8b0c11a5c15b046516a87b3e98d1f909bdaff06eebfd9b0c457ec3741833da262f77d411cc500000000000000000000000000000000012cfcd6910ac046ab8c0b448edca5847d0f8cc2a4633fe42edd223ea1b73ec451de8d75cc3d37dfb741ee35259b34449 +00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec433292,0000000000000000000000000000000013c11f82052df6294da64b16551e689c439d2d27922bef2a067bc49eb4718a392693570f3b3e58158dc0f5bc3a5b8f73000000000000000000000000000000001517ee24f199913c184181561823d7c3506caa09d93d506c7773f9f615169df444c9f09b518e840735c259ec02488670 +00000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036,000000000000000000000000000000000a546a1f4d65a37d7d60468c18f72152473feeed100119b4518f4c778a7a37a23e8c60ee04cc0b39d5a1eb8c908856870000000000000000000000000000000009c5766d9c88dca87768c0aff4160ff0fdc3aa67dde3eafcca030eb295a6736e95e415f3f5a443f2545c7fbd01f97964 +00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d7,000000000000000000000000000000000a197b81c0950b1b802128a01e3b620fb2134115a0d1aa2946a82fd22e91f172785d19017fca385863ee1643bcd332b80000000000000000000000000000000011fba5b82b0b2726bbe7a6157ec9103d0b5a480066ce5ab7120294930b81c04cf6d0fb8b979d17c3e262bd1268bdf1aa +00000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e,000000000000000000000000000000001644dd543ee92960effec90347ffe5f06d6b087f13c6bd73dca93c9e16818d25ffafe3610260cd43ce9909e2ac2e2884000000000000000000000000000000001893436c9dc44500be831076b375d0feccfad2a126110fbcfb77acfb95d6dd6c6615b4b795c007ece6ea0c31915b8e32 +000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b6,0000000000000000000000000000000001aded655b8ba2739b820b894eefd7e60d11889d7321fdae5ddff5dce11551af24acea3f501044562237fe5df53305df0000000000000000000000000000000010f4f3f415891ba4dfb21307798329aac5baea98cdb44354d4263e1ee6436f613a3accf06802ce2c2782e8a15738bc63 +0000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee,000000000000000000000000000000000be0b580d0f12faa809d589ba59c5810c18f74b025e6dd4dc49c83b6a39423c5cf82b0dbb1d750e1801e37a5291692fa0000000000000000000000000000000010891c5bfece55dabcd223518167c5b0663f65c001ed051735635b417cbcf2484a057522e1c3417e43c82095b0cbb855 +0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770,00000000000000000000000000000000152454ae7fed9c971cfd72ed054f44124d71542f9ada5a90f1601114289c93fb490a1c5d99b3e8c70fc44fd10322173f0000000000000000000000000000000017bf9499bdc15ae5091daf41812c74535ca31b56520e420edf9e5aa90795ce5db5fa42a06dfcbc7438e954db83f09b75 +000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38,000000000000000000000000000000001633516081b91621b786a09389e89b274c2d9ec616db5028b009ed5c0a1ab47695a0b95c53a45112144613a4af08e6ea0000000000000000000000000000000014b09586f75c939fd62c3d667ab6263367f8961ad4597f1b92d792e8ef79a469137dfba5ec0a6354d5bfe3a84130bc65 +0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e6,0000000000000000000000000000000000f0474d596ed86a0d664885f9c981228fdc352755d52dd7e979a85fdb1b6dad106d8bc0a1eac04b510829b7da496686000000000000000000000000000000000a72f532897f912eeea707bfd6d183a73786c7b2e2c80a01f3abe7b959467d6ea63093c16d6465382a7808d5f0edd92f +0000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d,00000000000000000000000000000000139ae959f9b0cc2d900e748220c4bfa7dbe22926d8ecb9a10e7d713fa0a6e147fa3463e06b791a5e604c66110b77f7530000000000000000000000000000000013f8d09915f77f4a18854dc2451cf39d7ff502a8184d3b4c59ad3317d62940e903d68836751172ec0b4a796db003b373 +00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b,0000000000000000000000000000000015d065191ab63df2175f821cf62a4b948a6b2389512c7e94e1fa3c99506af624810ee17de2c183ebd69b4dc485ae264b000000000000000000000000000000000fa8cfd94bbfa6d504497866c1e0d9e84717fbf0468a164e3b8ca46348789e2b7f08ac5e8aa2e7205062f3d5083dc5fa +000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7,0000000000000000000000000000000012978a0da7162aa1e8b32cb6ec0eebf2c2e62350cab4534358c6bf80299dda9281e16ee40313e7c52c804b2f4de7f1870000000000000000000000000000000009dfbafc8e40d71a789a52d5f8b80e7c8510c58bc0774cfa84211a9c1417d75d5c7b06d7aa9fe052ad9c1f30c922705e +00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d,000000000000000000000000000000000de98869442b759a382d0f6ca45eb60424eb9aee2efdac83086cb6dd374120941343eb314756113e084f943cb60d91470000000000000000000000000000000019dacc8180e6dd09ac4bb97114d2ecadb04bd2aef6e5f0993742c5270267e42d052d436c99ba61f6c0fd1fd2cd51d172 +0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1,00000000000000000000000000000000123313e3cc006c4b95938f5eca903604ac9272c7a0c79cd932407b70635d7ca5de9297496c27406f180d5edebbb54c7e0000000000000000000000000000000002164460e59cc8788c96e235a6faa7fadb7e6ee9f6b0b95292992973ff54a92147dc7ae8e8f217515b6185875bd0bd7d +0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a,000000000000000000000000000000000393a1b2395447b2e2838c2f49493c185424c4848f888616f16a95552671ff28b5ef223bf34299005f22a8df6efd68290000000000000000000000000000000012b1fe46279922e92d356355752ae0c2f28fc55de39ebfbd317a6c1c507d973f88c6282468571a1efc20c10314ac72f3 +00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122,000000000000000000000000000000000739f563b42648cde5befaf44317468982eb9d2fceee7d2efff1755be973cfc2beda829268246d09cd29fc3aa91f0b8a0000000000000000000000000000000014fe0b03ac5e0e03acd7811270d65742a3345bed7a4790d5f40097dd34050d0043104b65fd4691c251f03e67525d41b5 +000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be,00000000000000000000000000000000128e92c9c10fb9b065fe2c2dcfe365e98aa54eaeb3fae987306c7f0a227171ae0b3464d01a54a8d6b144ff60c45088a00000000000000000000000000000000001beaace4e23c9a31e1e9eb8596b3b05b9d72553f44c61627654757080171b05c900fe1b638193a69058e8d66cff1aa6 +0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f,000000000000000000000000000000000a6099ebb3a1101206bbd21149cf22af2371106bd34671c1cbd4f2e19311fd100bcb56a6d9d77bd834f972e55e0fb75e0000000000000000000000000000000001db77a2045e54b0ac4b3d61190684b4eec9c4ea415e5c820992b70d6ee2e086c02892228c4465c8494f939cc0b7b5ee +00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a,000000000000000000000000000000001101cd37b61247a9859bb09ccf9eb416643f86b7109bb45d6827fbf424956c9a16b2a19c5e198551c43aa1934ad8ed0e000000000000000000000000000000000da562fcb2e3cba853de6d245a1ea0cfc3ac120b316a5f4f7072cc35a6634027409ad08c5d591a6688b24cdc4562cddb +00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb,000000000000000000000000000000000323c3aa4b20691af32696c449668fb6da6a0c2e8eb176fb8fcd8aeebc9b5a3bffc57b28dd35e374811d420419fb0fd30000000000000000000000000000000019516a092385d8c917b46a742f086c51e2648c7e9a709ebeb5a0f8bc29c9aabf99972aa3a218582f37d91f9758a5ddb2 +0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c9436,000000000000000000000000000000000d0351d8557d21c2dd3b1be77bb01df804ebb9e2d7e80910264ff94861cdc0a4deedc1231c61b7503c5d653e31fe10850000000000000000000000000000000005858ee487860d1ba04cfdcedebda235616c2d271ed50f89d6cf2852ea7e10ac825dacd8b00071684858a12459d1705c +0000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba,00000000000000000000000000000000176c50c2fcf1bcbe03a1a1ed2eb120f94ad4fcea34a59607ea595bc2b37cb92f87641191b65d4b5d57f5491ce6576a670000000000000000000000000000000000e177361e09975c98849faf8e24086f75a48df0f257ea47b659cc2a142a57ad1f64416f6dee5cbc4e57f780dadd1cf2 +00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c29,000000000000000000000000000000000dcce000aae744f8b3b6754af57a36786d887d7f9857654f93edbcb6c4416ccfea5e859acc82860b5f706087e87cdc07000000000000000000000000000000001847c32c839668a38669fdbabb512df15cde2b28ca336b0e158d1fd57f74638d86ba40ff68f0a50cead7021e86c5271d +00000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4,00000000000000000000000000000000062a58846d39dd1fdbd34a7117797f2200d814b2a8eac9479885762565a979e93b5313575bff5ada3211eeed0a3f4ddc000000000000000000000000000000000548a24e7af2b38c4d16d8dfc8fb2d7e7669051e2643c44aee113f20d31f4853cef84e2dec20095c273680cca278331c +000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa1,00000000000000000000000000000000107f01e4fb6430e34128e3335872cf40df2b498a63e048d46158190cb627e37833d2238dd72681037ce376384736b43e0000000000000000000000000000000000e1812299403efe0f8d111d97a4b7e7b8aa1f4ec58f9935b1367d81a847fb42cf756154448f9172118123679a41a280 +0000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79,00000000000000000000000000000000188dc9e5ddf48977f33aeb6e505518269bf67fb624fa86b79741d842e75a6fa1be0911c2caa9e55571b6e55a3c0c0b9e00000000000000000000000000000000193e8b7c7e78daf104a59d7b39401a65355fa874bd34e91688580941e99a863367efc68fe871e38e07423090e93919c9 +00000000000000000000000000000000000000000000000000000000000000000000000000000000215a2313a0241779006bce33a776aeedae5d05ea6ee5a9b9,0000000000000000000000000000000018b877f3b7cba860b35178c617d881825d58788b8dfe08c419e4dae83e40d0c4f32dda2a6c3324273836174063236b8100000000000000000000000000000000016edd8350a5da60755599948cbc855394a32a89785f6871c23321803c92f976b8006619d9ab20e5fab21465c1d0e99c +00000000000000000000000000000000000000000000000000000000000000000000000000000000215a2313a0241779006bce330776aeedae5de5ea6ee5a9b9,000000000000000000000000000000001467c2d0500886ee32784b42e15babeed9dd20c16b590b314c0cbdf91c968b52a2f2d7328451bfa850ed1836282be0f000000000000000000000000000000000099207e6e5937ed228251699184e3941403cff8a340f30aa26074a1d78137767aafab1fc01781c02c2d6929eda089206 +00000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000,00000000000000000000000000000000100c6b89b6b826bfc2fa04d1f07b9d9ad9aea022cc97a02deaa89520b2aecb2ff4b8eed83c94e2e2b96369fb10dfdd000000000000000000000000000000000010104bf5b53de4d422731d29a447c804ce49b37c29217f1ab1b33ae096247d775ad8cc4c1a7800901486ca91ccbcd7a8 diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp_to_g1_error.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp_to_g1_error.csv new file mode 100644 index 00000000000..2ca1424c1a5 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/fp_to_g1_error.csv @@ -0,0 +1,101 @@ +input,result +000000000000000000000000000000002e4180463511efbfb553c83ddd755906c74df82d16a9d1fa49db4fdd4b2a1667dc38791e7e2f080bd451dc68b8b63dfb,"invalid input parameters, Failed to parse Fp element, 0x2e4180463511efbfb553c83ddd755906c74df82d16a9d1fa49db4fdd4b2a1667dc38791e7e2f080bd451dc68b8b63dfb is not an element of the field" +8964d5867927bc3e35a0b4c4574823730e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000021efb92117f5ae60b0e20dfb35754bd75b5cb014053fd98c7e2b14558ece0a60734b06571b20231bb595b30d27244b68,"invalid input parameters, Failed to parse Fp element, 0x21efb92117f5ae60b0e20dfb35754bd75b5cb014053fd98c7e2b14558ece0a60734b06571b20231bb595b30d27244b68 is not an element of the field" +c3d921e9bd8eff13ee00dcdcb84109e2197394ce21fdcc701e84354b210297e1f7bba5f5bbb1f7603cf9b30003d19e3db928d2a6b1a8d6000194e63858f1d1f6,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002bf2b53e3fe6ff3568472b5ab1d8448892d6a2c797a6d8f09d1028c990b4b0eb0f1f64647cc92dc04e8958fe2a893c74,"invalid input parameters, Failed to parse Fp element, 0x2bf2b53e3fe6ff3568472b5ab1d8448892d6a2c797a6d8f09d1028c990b4b0eb0f1f64647cc92dc04e8958fe2a893c74 is not an element of the field" +101e5ffee2dcaa870e6e5881a38aac120953a84f6472544a68f0c7f6c3da46cac823d5add44ed01b73c1cc961477c937736605d340769adffb4a5adc20a53355,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002117c0449bed133429b87b1086abc06ad6d2e958f107a4edfdbe351aaf124796909c87dd34714c27c6c212830ff2ee1b,"invalid input parameters, Failed to parse Fp element, 0x2117c0449bed133429b87b1086abc06ad6d2e958f107a4edfdbe351aaf124796909c87dd34714c27c6c212830ff2ee1b is not an element of the field" +b4d571c7b3092e1ae11d9697f82ed833199fa649608972d295befae38d36940663d2b67bb286a3d549c75deef39ef8068728bbba2cafac44c102499601e4bd86,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001b45fc2e5a7eafb3b876e78b43a1a2bcc2247016762134d3803ac9de588d1180cc94dce65a818f5f70b1447ce2e6ca1c,"invalid input parameters, Failed to parse Fp element, 0x1b45fc2e5a7eafb3b876e78b43a1a2bcc2247016762134d3803ac9de588d1180cc94dce65a818f5f70b1447ce2e6ca1c is not an element of the field" +7064d43d6802ad4c3794705065f870260ac65048e63cc4dbdae2c8b37333bbb5ac70b493a3bc0f149dc918cad79951e4390ab30c6587cf15a6c0d9a00fc896d0,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000003196dfeaf677afd5bcf816848be6accee3a640ca92bfbce6831818e5898304da5ae7b620e8030fc852cce818681a39a6,"invalid input parameters, Failed to parse Fp element, 0x3196dfeaf677afd5bcf816848be6accee3a640ca92bfbce6831818e5898304da5ae7b620e8030fc852cce818681a39a6 is not an element of the field" +7f16e09114878895626faa93b9c8c5a316ec9eb24a06ed90cbc51b22b76c7205d0b74a62bf50719a21bd1f725c189107a322696a05b3605604ea0b099f92a900,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c7d4cf93f885436cb2d5be3de185921b9107332cd8d5be8ccee698089534cd7886b00d4201dd1a0c56b17e97e7db968,"invalid input parameters, Failed to parse Fp element, 0x2c7d4cf93f885436cb2d5be3de185921b9107332cd8d5be8ccee698089534cd7886b00d4201dd1a0c56b17e97e7db968 is not an element of the field" +ce635c394249e55c6b73ce0855ad13c0097d398c9190e7fb2ba325702bdd1186a372412759a54094b1d581bffb17e09a3dac3d64569f7715114ef021c0a05ba8,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c2d80192b5c80046c660979555748d4a3c03460ed920693576487328afc3b0a8a59fc490905605faea6c136c2509761,"invalid input parameters, Failed to parse Fp element, 0x2c2d80192b5c80046c660979555748d4a3c03460ed920693576487328afc3b0a8a59fc490905605faea6c136c2509761 is not an element of the field" +1b25130888956b0cdd344de9b465944715027b5a16d68fece677154d1fe064ac5abba6672ebe34ddd48ef21ff73bd7e28d7b50de12f3f21d90dde04ab8e3af9a,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000029236547ec215f1d6ab62b91126b16537c5dc5e480dd52366b34e598cc17231892ba434772dc0a535e78838be3fafe46,"invalid input parameters, Failed to parse Fp element, 0x29236547ec215f1d6ab62b91126b16537c5dc5e480dd52366b34e598cc17231892ba434772dc0a535e78838be3fafe46 is not an element of the field" +e7002f41c6acab677a0ad023bad2a61b0437fca337e96768026d27fe4222cdd42610145f63b4a79c15778306798c828b633191722987dd66f2a15cf35436411c,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000003143bb7811534db6778052274585ad184dae48f75f8b40116d56e3dce3f814781dea85722faebf9ea2811bae531309cb,"invalid input parameters, Failed to parse Fp element, 0x3143bb7811534db6778052274585ad184dae48f75f8b40116d56e3dce3f814781dea85722faebf9ea2811bae531309cb is not an element of the field" +a5e2a2e6f558681de7b4e84eec171cb019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab1,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002762361b13387bfbd41aa2031b859965646563ec8308e757b239b4c87ebfb74c04e460be0ece27c2c3fa425b90c05a50,"invalid input parameters, Failed to parse Fp element, 0x2762361b13387bfbd41aa2031b859965646563ec8308e757b239b4c87ebfb74c04e460be0ece27c2c3fa425b90c05a50 is not an element of the field" +f5795b8fc0b8a2f3d8df3ad5ee1410f01026ad36ff6fd6440680c3ca535bf2236f99482b763e95b0dcbee7371b1e2acafd0d66ae451eaf5ec975f06f46992b1a,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002fd9df194f374ade15c53907243a42f4971a825bd22d6c317a90303ad6f6d20694fe6926efc135d60f8849d957a430e4,"invalid input parameters, Failed to parse Fp element, 0x2fd9df194f374ade15c53907243a42f4971a825bd22d6c317a90303ad6f6d20694fe6926efc135d60f8849d957a430e4 is not an element of the field" +590e0b7016bc063f3fffa93e1e35484c08c47351d234da736a9b995d4bb4812134fa24ba94107c26cf20827456cdde79c3bf354ddf4c624f68dd95ae86efcf7b,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001dbf4dfea9d6ef66bd393e94b94799abba6f49c9768bd23e4d475a0b866c4cbbe3634fe46a9ed902251241246f50d879,"invalid input parameters, Failed to parse Fp element, 0x1dbf4dfea9d6ef66bd393e94b94799abba6f49c9768bd23e4d475a0b866c4cbbe3634fe46a9ed902251241246f50d879 is not an element of the field" +7b4af1978983faebe59a28f34956dacf14e63775246d091249dad4b5854904366a13bb3919cc9aee6eabf8b45d57e379ba87364c7a2d8cbf3fa75c1b3d4d4fa5,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001f23855fe6f874a009a03c6c0b4ae078f63f80f998431d47fd3b9579f7dc2455fa8025b1d80cf64b89c648ebba76b2e5,"invalid input parameters, Failed to parse Fp element, 0x1f23855fe6f874a009a03c6c0b4ae078f63f80f998431d47fd3b9579f7dc2455fa8025b1d80cf64b89c648ebba76b2e5 is not an element of the field" +c7b40b8728c3b121c0b67310a80d9efa0156335352698cd93f444d602ef63fba8d50efba377f28b048af5be759b6f531d38733674e13a8cb664f4eb30ab8b0a4,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002bc4ab15ac917ef7372bf7415056032aaedd3b408aa73a3f1d49d87189961861d35f5292c4510f5198b37d7f9f4dfd9e,"invalid input parameters, Failed to parse Fp element, 0x2bc4ab15ac917ef7372bf7415056032aaedd3b408aa73a3f1d49d87189961861d35f5292c4510f5198b37d7f9f4dfd9e is not an element of the field" +63bf6fe085d0577d991cf632f5d5d8260ed321c9420fb983adf91538a1eedd343f037a2b2ca087a22e777b66ea1e1d6157a36396165701447fec7946c829af77,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002e8419663a86773e2902d1549b613a74100eb94c8d6626244c754e58d510dd24d22983e3dda1425dfdadf4503eafa67f,"invalid input parameters, Failed to parse Fp element, 0x2e8419663a86773e2902d1549b613a74100eb94c8d6626244c754e58d510dd24d22983e3dda1425dfdadf4503eafa67f is not an element of the field" +6a4d843a26b052a040c79659b5e8637b14233b617441ecdc84cc1c4610e6303e734c433dc083bd99e3c42cd73b03f12a3f3a48c0c7613677b1f1db6cd3807072,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001e39e089ae37fa4368dcbd0de651d90120ce935990d4aec61157f2c01fd9ae1d072ca5f58a4338d58111be9b405722d3,"invalid input parameters, Failed to parse Fp element, 0x1e39e089ae37fa4368dcbd0de651d90120ce935990d4aec61157f2c01fd9ae1d072ca5f58a4338d58111be9b405722d3 is not an element of the field" +5f1c596eb966f57867e021d0f3b099e107527341570c7769071c4cfa80b0e312d8402792148f2b213037884c59b76793645a2ed64a2e30beb30ca4ccbebcf4d6,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000291fd10927c95264c0c1728c6cd3090b5f0139ba0b72df69705227ea5e1215d25944a7b51abd024671d7ea4c7e84330a,"invalid input parameters, Failed to parse Fp element, 0x291fd10927c95264c0c1728c6cd3090b5f0139ba0b72df69705227ea5e1215d25944a7b51abd024671d7ea4c7e84330a is not an element of the field" +ff02d386fd49420b2c273b1233e9cdb105d5bf86e027fdd728fc416262740976f5f62c4a0a047f5e299b3f9793472e2f8fe4899736f148176a03d85c22abdf40,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000246a97bff4e3879eba62bef8e3ec94c94ec7b4422dbdb0aae5ec08a1e670ceca1aa037cb0fec90276601f9fb4af6ab62,"invalid input parameters, Failed to parse Fp element, 0x246a97bff4e3879eba62bef8e3ec94c94ec7b4422dbdb0aae5ec08a1e670ceca1aa037cb0fec90276601f9fb4af6ab62 is not an element of the field" +50a97562db71b599dd018ab0410ada200846b39e09c924355e83f2f639b045fad051d9bd328554d13794b558da35e383c71d37933f7d0d24c7cac39d8c7ace5d,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001a95310a15d3cbc6432e2eb151222667a3447d7fa8a7a4017e8740bf5e219e0a2b258bffe94a2c0db182e54f14eab090,"invalid input parameters, Failed to parse Fp element, 0x1a95310a15d3cbc6432e2eb151222667a3447d7fa8a7a4017e8740bf5e219e0a2b258bffe94a2c0db182e54f14eab090 is not an element of the field" +b1ee0d2b90782aa0d652b8c0e0bd3150070d8207f01f734119f150ea77c21a8331d90ba196ce325c7c546b5d388e5e81f900d7cb18ca7e32157db3adefdfec4d,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002448584a7ff7205936b7044a2b660580c68e0644959c740a4406aaeb6026b527abac6624b0a1388532f75b184c5b2317,"invalid input parameters, Failed to parse Fp element, 0x2448584a7ff7205936b7044a2b660580c68e0644959c740a4406aaeb6026b527abac6624b0a1388532f75b184c5b2317 is not an element of the field" +8d95d94046678f3bdb4b0ea3d4e3a1a2095927fd4e943bbf4057d4f9dcd1a8b5a31a051125b9425fcd7a363f4efbf1ccb572227eaf24daa74d57ef42912990ab,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002cd2dbbd7eb30115a4edceed496954e4daa5cec87f833fe336ab4ef0a5750697b7540cb465d9fb8b6a096e4b6da20b0c,"invalid input parameters, Failed to parse Fp element, 0x2cd2dbbd7eb30115a4edceed496954e4daa5cec87f833fe336ab4ef0a5750697b7540cb465d9fb8b6a096e4b6da20b0c is not an element of the field" +50c5f23df0ebf1fcfce4bdc261301b49090154120012c78d19f379c1dde7f0ac5447fda47fb4baf7c251d3468eed9492392dda47e4b522803cdf3d64c86d9241,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001a75be03aff507909ba6995a77dd0182cfa088d7612801cfca60607db46592668f27434edcf4761957771285ad71e84a,"invalid input parameters, Failed to parse Fp element, 0x1a75be03aff507909ba6995a77dd0182cfa088d7612801cfca60607db46592668f27434edcf4761957771285ad71e84a is not an element of the field" +bdbbdc35b88c67aae7d0e3e782f8abd107b3d49cc20a8a9b68bb2980a6811aa0c7925b1e06b40de7d20d06ae39d9b579c9cf2043a2536f8d1b9b942bec994a18,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c19c4a9d080b9da26838eb1c46f6b686c08173082e2a35d4683c9ed30397a3919f758b609d04c58f5c0566c8cd4e34a,"invalid input parameters, Failed to parse Fp element, 0x2c19c4a9d080b9da26838eb1c46f6b686c08173082e2a35d4683c9ed30397a3919f758b609d04c58f5c0566c8cd4e34a is not an element of the field" +bf2db2842d626647bdb3346196e9420a03139bd292d2eecbdbf6846aa943231192e1ec62bfe845baf41f585d285783da54810bab55652fbff76c608a861efe3a,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000020ac413ef85aeee602fb60691c11fe2a5f01899058b4f26726caf030185f04f0f9efddf6bf51f13804e63d2c6d15bd30,"invalid input parameters, Failed to parse Fp element, 0x20ac413ef85aeee602fb60691c11fe2a5f01899058b4f26726caf030185f04f0f9efddf6bf51f13804e63d2c6d15bd30 is not an element of the field" +91f3839d5961f02a67f3b357206e406b13715eb94dbab484bb7ee731a2947adb6351c82d032ace0c9cb71ea880a5119212fd45d99e9e06d70a79f940ae7c9843,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001ce2e0057015fc02c534f6eec0ed9bbdd14ff691879869986179c0715cc6fb6740eeb4d53f105c87e1bf9009385a0c17,"invalid input parameters, Failed to parse Fp element, 0x1ce2e0057015fc02c534f6eec0ed9bbdd14ff691879869986179c0715cc6fb6740eeb4d53f105c87e1bf9009385a0c17 is not an element of the field" +85a2bf342f3959c0208d3ab6b2b2a8a404e958fe35e716ec5a7106f0cae103e4f8f3294deaf5b099cf3caa543985aa29d5e36571d3fba1e51bad29e0b807495c,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001eec400b6b1580eef77fb575356da0d1d865c12bc0d9f94d437e64ff04592a1aee7adbc710b0971fc73d3af1383a08fd,"invalid input parameters, Failed to parse Fp element, 0x1eec400b6b1580eef77fb575356da0d1d865c12bc0d9f94d437e64ff04592a1aee7adbc710b0971fc73d3af1383a08fd is not an element of the field" +1592f5b234f88b650c54e56b8e45e7b80e9aa30f22efa5a6d76c7fe468024a53aabd1e60d0a9d728c12816a7454189862fc790cf2ef75b19212e2e531ac73c40,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002a94c5a94a0febefbb30d05e10f046bb4ce2803de8d5a3d2e21485ac00191a7ff97e0ba02e516f5b7954f72973cc737e,"invalid input parameters, Failed to parse Fp element, 0x2a94c5a94a0febefbb30d05e10f046bb4ce2803de8d5a3d2e21485ac00191a7ff97e0ba02e516f5b7954f72973cc737e is not an element of the field" +2c8e86288c97e6a8b8b5b20209a1b2850bb6bbaa346908f7bbc9a178561b02cccc43f59abfac0f92912ed97ab7bf00f6370b6b92b45c4f05301c83d5f73c5946,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001a8dc068a03d6d26742c32550ba9856428cc1902c5cc7968141d308d8697ddcb270f488b41b22eb79c897b994f82fcfa,"invalid input parameters, Failed to parse Fp element, 0x1a8dc068a03d6d26742c32550ba9856428cc1902c5cc7968141d308d8697ddcb270f488b41b22eb79c897b994f82fcfa is not an element of the field" +1a3a4b9909dcc5cc6a0de50286294ee10f2f208031b70df582392bd6d37e0e40c6d9069bf1cba9ec4d5b9f8fe0cc876bf1329fb8bd619d7ee58565ab45acd9b5,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000292f6a516ca853057efd16fb9b3d65458eeb4c34488267eeef7be7701a84a7c802ebb92978605bad9c231475ecad4d00,"invalid input parameters, Failed to parse Fp element, 0x292f6a516ca853057efd16fb9b3d65458eeb4c34488267eeef7be7701a84a7c802ebb92978605bad9c231475ecad4d00 is not an element of the field" +a94eb8b6f2f556326d78d8952908006c086556bb7ad3b5df0e849bf93e1a8027972d6da3644fff2c045c06493e211a0c09bc2be359a3b4050f429c409755feb4,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000030a7703dd62b08a435e3ea3376de3af32aa2f4ce7506ab466b920315097e4ca194b01497fecd0226c33c92fe6b1ca25f,"invalid input parameters, Failed to parse Fp element, 0x30a7703dd62b08a435e3ea3376de3af32aa2f4ce7506ab466b920315097e4ca194b01497fecd0226c33c92fe6b1ca25f is not an element of the field" +1a47861b3174c3b41ad82586d4233c0d03b49bf20a6edfb56a3bc0c3af5aa57d400398e552faee95255b22b74787dce8d66d7d7b2fd929c7b690138df3b4fcf5,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000211b6e42ba56c0666d3a28dd1e3a2b51415f1f03f99fe6dfad162afd40b4fefb67bbe7d7966f048a8e530975f958dfdb,"invalid input parameters, Failed to parse Fp element, 0x211b6e42ba56c0666d3a28dd1e3a2b51415f1f03f99fe6dfad162afd40b4fefb67bbe7d7966f048a8e530975f958dfdb is not an element of the field" +4ae34e6b5e1284cbb9497368d12d0ac60e5c22a2e28eda691f21e4634b28b60ac88f6a1c34f6f03ee07b05be67e57dd7a98800a2b1f89661f2a9b4cd6e234947,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002c971430ef710975d62b534977a88c2a905632134fd2530338c935d4da9341b3ba47a526532be185688cbefc8e06c333,"invalid input parameters, Failed to parse Fp element, 0x2c971430ef710975d62b534977a88c2a905632134fd2530338c935d4da9341b3ba47a526532be185688cbefc8e06c333 is not an element of the field" +5e48d5f4a011a4aa0dbab22ede62c90302f0a3f29f53706b47b6d5518f400b6b87907c07a882b31c4577d224f5c5c4bd43d8e08863891a9459a87eaca36b73e7,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000242cdcd80be6992a9b11371d4043d28fa6576f1506cc3064ae7f437a7e1ee502d8a1c02077a18935face986d80450b5e,"invalid input parameters, Failed to parse Fp element, 0x242cdcd80be6992a9b11371d4043d28fa6576f1506cc3064ae7f437a7e1ee502d8a1c02077a18935face986d80450b5e is not an element of the field" +f62cf5ce7a4467dcf21ab62f6152933819c0dabadcdbe0030090bb36d08bad823a5b0ef0584efe510ddf0bd8ff96fa7b38fa5757ee5cbab304727b5c6208e047,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +00000000000000000000000000000000331c899254ed343b8bdfd27768c15431c1aa56f4e018e56a4b5e0d62ddde416eed3ae2dd8102419bfe500e260feff68c,"invalid input parameters, Failed to parse Fp element, 0x331c899254ed343b8bdfd27768c15431c1aa56f4e018e56a4b5e0d62ddde416eed3ae2dd8102419bfe500e260feff68c is not an element of the field" +9b220c7793bf5bab7b3b9c733ffc71f2154235547a2cd9b4b1be0a522ad97db3ab1cb834c84ec462f4987423b3cde009d6f249a1d331b686796ed585013f5f34,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001e80b3199cd26da10c3acf58ca92f04ad02a89cda24d29c714c1104a815f73fafbe3ac3b3d2f86a5bca925070b8cc11f,"invalid input parameters, Failed to parse Fp element, 0x1e80b3199cd26da10c3acf58ca92f04ad02a89cda24d29c714c1104a815f73fafbe3ac3b3d2f86a5bca925070b8cc11f is not an element of the field" +ddbd3acb77c3b388a0040cc5dcf7ee070e37bb05e1e1e1ba0efbca4685f695e867789d5cd56b8d3839ddc9dd7764a6390e5567e1a91d85e0e96d29025e179635,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000025d4b3e81050b3b86471c7210a5c3cf58f13c94115fec1cc0a94aa969157fd35da9d3cf76326be744a0c72dd2863553e,"invalid input parameters, Failed to parse Fp element, 0x25d4b3e81050b3b86471c7210a5c3cf58f13c94115fec1cc0a94aa969157fd35da9d3cf76326be744a0c72dd2863553e is not an element of the field" +e3d943340e324f6738a593a915a6bddb0208c07f4c37a1a9411a72d73f562db816ea023af4962479f8fef330bd01543f3f100b77c1d7dab2fd9f393f704f1e63,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001f5b517808a97efc645ca2917eedcd737b7771142ae8a837fb724f0bd775ed9c9df33babd2e2a153be6fc613c20300a4,"invalid input parameters, Failed to parse Fp element, 0x1f5b517808a97efc645ca2917eedcd737b7771142ae8a837fb724f0bd775ed9c9df33babd2e2a153be6fc613c20300a4 is not an element of the field" +420b289a9a1a612605fb554531f53b8c19522fd76b56df53814a15396e6067caec6e19c9c13577aa60f699e4bb69fb53d8befa2142e1600ad71edd1e04fc765a,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002dbfe256476dcab6630fa551cc82a17301be54a9b4b21fbad0459a710e7bb19ecfe2e1e58681f2fbb6bd2e306f04dbde,"invalid input parameters, Failed to parse Fp element, 0x2dbfe256476dcab6630fa551cc82a17301be54a9b4b21fbad0459a710e7bb19ecfe2e1e58681f2fbb6bd2e306f04dbde is not an element of the field" +5ab7efc86de7dfeca895aded698d56c605c1de478e13c9e85defc9c856ae1f8c7270fa28460a10312b33ee2514b97b7e6b2dca8f33b10bc958b528a764bddd5f,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002bd2c8f6fc3855cb6cd75ab89e8d0f467932cc7ae888b4b27df153454b2da15816d17172544535891e16debbfa0e8da7,"invalid input parameters, Failed to parse Fp element, 0x2bd2c8f6fc3855cb6cd75ab89e8d0f467932cc7ae888b4b27df153454b2da15816d17172544535891e16debbfa0e8da7 is not an element of the field" +962cfc94bba26d748d5c30754f9994a403fa115f113cc37514de4ea2a4a78b8a1bf27cec8c2bd4674f9b5830ad4b9a285f5668b5939a6335ede389d73bb9c89d,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002628d2f5fe2d46db948aca89a84c4ebfb7e2487efa38d7eb6a028303fa08811d55a34959869e02f9e0da6a1a4d04c2b1,"invalid input parameters, Failed to parse Fp element, 0x2628d2f5fe2d46db948aca89a84c4ebfb7e2487efa38d7eb6a028303fa08811d55a34959869e02f9e0da6a1a4d04c2b1 is not an element of the field" +dedade892e8e2171e652d2a9b52f228808cc3383f31493ae6a74f49ed2330ac57b6d4b6f8d5d4fe38995d5e03329b899eae9cdc4306956c3ad24d84d35673926,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000027a8430e438729f608d16985d6890911c665ca456c5a157d1096135a837e14d24862da7985fc80801970fb26f3638d3c,"invalid input parameters, Failed to parse Fp element, 0x27a8430e438729f608d16985d6890911c665ca456c5a157d1096135a837e14d24862da7985fc80801970fb26f3638d3c is not an element of the field" +d5535835924719c156be810c3fa86e350561872c61b2e581da7fb20da71f4721a3b041199940b36f485e51a19f04bccc9ece35bca74fe5e1afbe3aea3228b6eb,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000028edfb30e9ba69d43c7ebe7fbd47ec9cbdd0b819a175c30697a6d609883c5874b45e47dfbc72bc3682ba3d85371a6595,"invalid input parameters, Failed to parse Fp element, 0x28edfb30e9ba69d43c7ebe7fbd47ec9cbdd0b819a175c30697a6d609883c5874b45e47dfbc72bc3682ba3d85371a6595 is not an element of the field" +6a15d38a24d0a330cdc9c2a7b8dfd669022cbc96a51e897faefc3ff27e6a666a5dc5717f724baf65626f76ea50c28cdf965e388ec14e354da7a6aca35f7eb77f,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000022e64f7c43242c63cc19338420a34ef77e15e713c3e6958e2250a40cc0cb3a42f6bf5bde683a2b6fbf4e175c669e8c48,"invalid input parameters, Failed to parse Fp element, 0x22e64f7c43242c63cc19338420a34ef77e15e713c3e6958e2250a40cc0cb3a42f6bf5bde683a2b6fbf4e175c669e8c48 is not an element of the field" +d41bfdc6d97a35cb94c29c552ca3656908b431b820bddd77921275918d56bb7f9ad758999be853f6768b1a84b2b09d1b6bea121540482098d6bb6c34a1d0e50e,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002a4b27f70ae78e5b6e80babcdb5911fd167f5924acd4cf655327ea6dc56652b3ffe6a46a2d390ec08e633299bfe82feb,"invalid input parameters, Failed to parse Fp element, 0x2a4b27f70ae78e5b6e80babcdb5911fd167f5924acd4cf655327ea6dc56652b3ffe6a46a2d390ec08e633299bfe82feb is not an element of the field" +a0893ece646de60cd66aa483662125ff145a5ee376590202dee0a35b168808d8ccf35a7c7b7edf8cc798bed39faa349238ed77bedad433330dfd0d1729b09bf3,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000003099547bb33cbda5d8901dd36f168e418032182ad7f98c4c6f3486aa894a4dd92809603845061b3bdf2a9f6853ba4e63,"invalid input parameters, Failed to parse Fp element, 0x3099547bb33cbda5d8901dd36f168e418032182ad7f98c4c6f3486aa894a4dd92809603845061b3bdf2a9f6853ba4e63 is not an element of the field" +bef9b866f05b63afbd1f0960261592180d026ca24616f8934fe1abb20f65f0d6830c319451d8e5a09a8edf7cf7037c7293a3af1eabeeef724d0b4a03fcd84506,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000001ff4f99784652fbdaa026a7e52c29767e3ca5eef8e1b6ab289da1b615c17e999958bffd69bb2b8a71c950daa9293eef4,"invalid input parameters, Failed to parse Fp element, 0x1ff4f99784652fbdaa026a7e52c29767e3ca5eef8e1b6ab289da1b615c17e999958bffd69bb2b8a71c950daa9293eef4 is not an element of the field" +e669136470b1b6959f00ac58b6f9bc8807d698ccad5468e4a8765896a62bce33012d7d1d7533f53a90923eb5e65cd90410c2767eb18044d1a51089b05a2d6efb,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002329d439345779798266d89239a4370f34eed658b8b7e5274e45321126723326f53d37b49721af4a4bd3d6a4c8429e0f,"invalid input parameters, Failed to parse Fp element, 0x2329d439345779798266d89239a4370f34eed658b8b7e5274e45321126723326f53d37b49721af4a4bd3d6a4c8429e0f is not an element of the field" +00798c33e040a91328ad03b11a0c10180e51e1cd27a6266782185aa631784cf980f7223592b51786f40a6fd339d8d403689e495ef41b5aecf8fa109f6b3c7bf8,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +0000000000000000000000000000000020d5aac9b7279ad4d196c2b18086aa2605f360b26ea630abf01acd15bddc9bfc3ce8be5808603273ba149488fcac5f15,"invalid input parameters, Failed to parse Fp element, 0x20d5aac9b7279ad4d196c2b18086aa2605f360b26ea630abf01acd15bddc9bfc3ce8be5808603273ba149488fcac5f15 is not an element of the field" +67c1f7b1a7390ab4dbba7d219dfeb3120b3a1eacc8cafaec79c7a644af25187517122bf2d2782f56279b21978c0b88fe4be1c769d696b406e54caf6b24f8c0a7,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" +000000000000000000000000000000002146b000f8b5839b7d5a571f6a55af98eb0c4493bcd88d8f8364a2f9fddbe7f403386b24c04c365f94e98ce560a3f12d,"invalid input parameters, Failed to parse Fp element, 0x2146b000f8b5839b7d5a571f6a55af98eb0c4493bcd88d8f8364a2f9fddbe7f403386b24c04c365f94e98ce560a3f12d is not an element of the field" +e5b3618f0ca06e428e0cf0f590f77d13147497791367cf0514af27f5ff41036c83ef104a056dc0abd049a6a38f8f781f77f93b3fa75141856f385cd2c18a6157,"invalid input parameters, Failed to parse Fp element, top bytes of the padded BE encoding are NOT zeroes is not an element of the field" diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_add.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_add.csv new file mode 100644 index 00000000000..7bb8863a6f0 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_add.csv @@ -0,0 +1,101 @@ +input,result +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,000000000000000000000000000000001466e1373ae4a7e7ba885c5f0c3ccfa48cdb50661646ac6b779952f466ac9fc92730dcaed9be831cd1f8c4fefffd5209000000000000000000000000000000000c1fb750d2285d4ca0378e1e8cdbf6044151867c34a711b73ae818aee6dbe9e886f53d7928cc6ed9c851e0422f609b11 +00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95,0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6 +0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f00000000000000000000000000000000114c3f11ba0b47551fa28f09f148936d6b290dc9f2d0534a83c32b0b849ab921ce6bcaa4ff3c917707798d9c74f2084f00000000000000000000000000000000149dc028207fb04a7795d94ea65e21f9952e445000eb954531ee519efde6901675d3d2446614d243efb77a9cfe0ca3ae,0000000000000000000000000000000002ce7a08719448494857102da464bc65a47c95c77819af325055a23ac50b626df4732daf63feb9a663d71b7c9b8f2c510000000000000000000000000000000016117e87e9b55bd4bd5763d69d5240d30745e014b9aef87c498f9a9e3286ec4d5927df7cd5a2e54ac4179e78645acf27 +0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000c3d564ac1fe12f18f528c3750583ab6af8973bff3eded7bb4778c32805d9b17846cc7c687af0f46bc87de7748ab72980000000000000000000000000000000002f164c131cbd5afc85692c246157d38dc4bbb2959d2edfa6daf0a8b17c7a898aad53b400e8bdc2b29bf6688ee863db7,0000000000000000000000000000000015510826f50b88fa369caf062ecdf8b03a67e660a35b219b44437a5583b5a9adf76991dce7bff9afc50257f847299504000000000000000000000000000000000a83e879895a1b47dbd6cd25ce8b719e7490cfe021614f7539e841fc2f9c09f071e386676de60b6579aa4bf6d37b13dd +0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d30000000000000000000000000000000019fe3a64361fea14936ff0b3e630471494d0c0b9423e6a004184a2965221c18849b5ed0eb2708a587323d8d6c6735a90000000000000000000000000000000000340823d314703e5efeb0a65c23069199d7dfff8793aaacb98cdcd6177fc8e61ab3294c57bf13b4406266715752ef3e6,00000000000000000000000000000000010b1c96d3910f56b0bf54da5ae8c7ab674a07f8143b61fed660e7309e626dc73eaa2b11886cdb82e2b6735e7802cc860000000000000000000000000000000002dabbbedd72872c2c012e7e893d2f3df1834c43873315488d814ddd6bfcca6758a18aa6bd02a0f3aed962cb51f0a222 +000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90000000000000000000000000000000001461565b03a86df363d1854b4af74879115dffabeddfa879e2c8db9aa414fb291a076c3bdf0beee82d9c094ea8dc381a000000000000000000000000000000000e19d51ab619ee2daf25ea5bfa51eb217eabcfe0b5cb0358fd2fa105fd7cb0f5203816b990df6fda4e0e8d541be9bcf6,000000000000000000000000000000000cb40d0bf86a627d3973f1e7846484ffd0bc4943b42a54ff9527c285fed3c056b947a9b6115824cabafe13cd1af8181c00000000000000000000000000000000076255fc12f1a9dbd232025815238baaa6a3977fd87594e8d1606caec0d37b916e1e43ee2d2953d75a40a7ba416df237 +000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b0000000000000000000000000000000019cabba3e09ad34cc3d125e0eb41b527aa48a4562c2b7637467b2dbc71c373897d50eed1bc75b2bde8904ece5626d6e400000000000000000000000000000000056b0746f820cff527358c86479dc924a10b9f7cae24cd495625a4159c8b71a8c3ad1a15ebf22d3561cd4b74e8a6e48b,000000000000000000000000000000000e115e0b61c1f1b25cc10a7b3bd21cf696b1433a0c366c2e1bca3c26b09482c6eced8c8ecfa69ce6b9b3b4419779262e00000000000000000000000000000000077b85daf61b9f947e81633e3bc64e697bc6c1d873f2c21e5c4c3a11302d4d5ef4c3ff5519564729aaf2a50a3c9f1196 +0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a0000000000000000000000000000000011f649ee35ff8114060fc5e4df9ac828293f6212a9857ca31cb3e9ce49aa1212154a9808f1e763bc989b6d5ba7cf09390000000000000000000000000000000019af81eca7452f58c1a6e99fab50dc0d5eeebc7712153e717a14a31cffdfd0a923dbd585e652704a174905605a2e8b9d,000000000000000000000000000000000013e37a8950a659265b285c6fb56930fb77759d9d40298acac2714b97b83ec7692a7d1c4ccb83f074384db9eedd809c0000000000000000000000000000000003215d524d6419214568ba42a31502f2a58a97d0139c66908e9d71755f5a7666567aafe30ea84d89308f06768f28a648 +0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000d713e148769fac2efd380886f8566c6d4662dd38317bb7e68744c4339efaedbab88435ce3dc289afaa7ecb37df37a5300000000000000000000000000000000129d9cd031b31c77a4e68093dcdbb585feba786207aa115d9cf120fe4f19ca31a0dca9c692bd0f53721d60a55c333129,00000000000000000000000000000000029405b9615e14bdac8b5666bbc5f3843d4bca17c97bed66d164f1b58d2a148f0f506d645d665a40e60d53fe29375ed400000000000000000000000000000000162761f1712814e474beb2289cc50519253d680699b530c2a6477f727ccc75a19681b82e490f441f91a3c611eeb0e9e2 +0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000006d92bcb599edca426ff4ceeb154ebf133c2dea210c7db0441f74bd37c8d239149c8b5056ace0bfefb1db04b42664f530000000000000000000000000000000008522fc155eef6d5746283808091f91b427f2a96ac248850f9e3d7aadd14848101c965663fd4a63aea1153d71918435a,000000000000000000000000000000000cfaa8df9437c0b6f344a0c8dcbc7529a07aec0d7632ace89af6796b6b960b014f78dd10e987a993fb8a95cc909822ec0000000000000000000000000000000007475f115f6eb35f78ba9a2b71a44ccb6bbc1e980b8cd369c5c469565f3fb798bc907353cf47f524ba715deaedf379cb +0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac9430000000000000000000000000000000016380d03b7c5cc3301ffcb2cf7c28c9bde54fc22ba2b36ec293739d8eb674678c8e6461e34c1704747817c8f8341499a000000000000000000000000000000000ec6667aa5c6a769a64c180d277a341926376c39376480dc69fcad9a8d3b540238eb39d05aaa8e3ca15fc2c3ab696047,0000000000000000000000000000000011541d798b4b5069e2541fa5410dad03fd02784332e72658c7b0fa96c586142a967addc11a7a82bfcee33bd5d07066b900000000000000000000000000000000195b3fcb94ab7beb908208283b4e5d19c0af90fca4c76268f3c703859dea7d038aca976927f48839ebc7310869c724aa +000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e00000000000000000000000000000000065eb0770ab40199658bf87db6c6b52cd8c6c843a3e40dd60433d4d79971ff31296c9e00a5d553df7c81ade533379f4b0000000000000000000000000000000017a6f6137ddd90c15cf5e415f040260e15287d8d2254c6bfee88938caec9e5a048ff34f10607d1345ba1f09f30441ef4,0000000000000000000000000000000006b0853b3d41fc2d7b27da0bb2d6eb76be32530b59f8f537d227a6eb78364c7c0760447494a8bba69ef4b256dbef750200000000000000000000000000000000166e55ba2d20d94da474d4a085c14245147705e252e2a76ae696c7e37d75cde6a77fea738cef045182d5e628924dc0bb +0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000006a3f7eb0e42567210cc1ba5e6f8c42d02f1eef325b6483fef49ba186f59ab69ca2284715b736086d2a0a1f0ea224b40000000000000000000000000000000000bc08427fda31a6cfbe657a8c71c73894a33700e93e411d42f1471160c403b939b535070b68d60a4dc50e47493da63dc,000000000000000000000000000000000c35d4cd5d43e9cf52c15d46fef521666a1e1ab9f0b4a77b8e78882e9fab40f3f988597f202c5bd176c011a56a1887d4000000000000000000000000000000000ae2b5c24928a00c02daddf03fade45344f250dcf4c12eda06c39645b4d56147cb239d95b06fd719d4dc20fe332a6fce +00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a578768100000000000000000000000000000000068e79aea45b7199ec4b6f26e01e88ec76533743639ce76df66937fff9e7de3edf6700d227f10f43e073afcc63e2eddc00000000000000000000000000000000039c0b6d9e9681401aeb57a94cedc0709a0eff423ace9253eb00ae75e21cabeb626b52ef4368e6a4592aed9689c6fca4,0000000000000000000000000000000013bad27dafa20f03863454c30bd5ae6b202c9c7310875da302d4693fc1c2b78cca502b1ff851b183c4b2564c5d3eb4dc0000000000000000000000000000000000552b322b3d672704382b5d8b214c225b4f7868f9c5ae0766b7cdb181f97ed90a4892235915ffbc0daf3e14ec98a606 +0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f7680000000000000000000000000000000000adac9bb98bb6f35a8f941dbff39dfd307b6a4d5756ccae103c814564e3d3993a8866ff91581ccdd7686c1dce0b19f700000000000000000000000000000000083d235e0579032ca47f65b6ae007ce8ffd2f1a890ce3bc45ebd0df6673ad530d2f42125d543cb0c51ba0c28345729d8,000000000000000000000000000000000b5513e42f5217490f395a8cb3673a4fc35142575f770af75ecf7a4fcd97eee215c4298fc4feab51915137cbdb814839000000000000000000000000000000000e9d4db04b233b0b12a7ff620faefef906aeb2b15481ce1609dad50eb6a7d0c09a850375599c501296219fb7b288e305 +00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca,000000000000000000000000000000000c27ef4ebf37fd629370508f4cd062b74faa355b305d2ee60c7f4d67dd741363f18a7bbd368cdb17e848f372a5e33a6f0000000000000000000000000000000000ed833df28988944115502f554636e0b436cccf845341e21191e82d5b662482f32c24df492da4c605a0f9e0f8b00604 +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000091ee883cb9ea2c933f6645f0f4c535a826d95b6da6847b4fe2349342bd4bd496e0dd546df7a7a17a4b9fb8349e5064f000000000000000000000000000000000902d7e72242a5e6b068ca82d0cb71dc0f51335dbd302941045319f9a06777518b56a6e0b0b0c9fd8f1edf6b114ad331,00000000000000000000000000000000122cce99f623944dfebffcdf6b0a0a3696162f35053e5952dddc2537421c60da9fe931579d1c4fc2e31082b6c25f96b500000000000000000000000000000000011366ffa91dc0b7da8b7c1839ea84d49299310f5c1ca244012eed0dd363dbcf4ad5813b8e3fb49361ef05ea8cb18ffe +00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000000d3d4f11bc79b8425b77d25698b7e151d360ebb22c3a6afdb227de72fe432dcd6f0276b4fd3f1fcc2da5b59865053930000000000000000000000000000000015ac432071dc23148765f198ed7ea2234662745a96032c215cd9d7cf0ad8dafb8d52f209983fe98aaa2243ecc2073f1b,000000000000000000000000000000000113ccf11264ff04448f8c58b279a6a49acb386750c2051eab2c90fa8b8e03d7c5b9e87eccf36b4b3f79446b80be7b1d0000000000000000000000000000000004358a1fabfe803f4c787a671196b593981a837ee78587225fb21d5a883b98a15b912862763b94d18b971cb7e37dbcf0 +0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb600000000000000000000000000000000034f725766897ed76394145da2f02c92c66794a51fd5ae07bd7cc60c013d7a48ebf1b07faf669dfed74d82d07e48d1150000000000000000000000000000000018f4926a3d0f740988da25379199ecb849250239ad7efcfef7ffaa43bc1373166c0448cc30dcdbd75ceb71f76f883ea7,00000000000000000000000000000000167336aeeb9e447348156936849d518faee314c291c84d732fa3c1bd3951559230d94230e37a08e28e689e9d1fef05770000000000000000000000000000000005366535f7a68996e066ab80c55bb372a15fb0ed6634585b88fe7cafbf818fbfebbf6f6ddd9ca0ff72137594a1e84b35 +0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b00000000000000000000000000000000079e5a154cf84190b6c735bc8cd968559182166568649b813732e4fb4c5c428c8b38e8265d4ef04990c49aa1381f51c8000000000000000000000000000000000ae08e682ef92b4986a5ac5d4f094ad0919c826a97efe8d8120a96877766eae5828803804a0cae67df9822fd18622aae,000000000000000000000000000000000a3d66cf87b1ce8c5683d71a6de4bf829d094041240f56d9071aa84ff189a06940e8e1935127e23a970c78ca73c28bf6000000000000000000000000000000000b2adda87740873c0c59e3ebde44d33834773f0fe69e2f5e7ede99c4f928978a5caaede7262e45fd22136a394b3f7858 +0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000008cefd0fd289d6964a962051c2c2ad98dab178612663548370dd5f007c5264fece368468d3ca8318a381b443c68c4cc7000000000000000000000000000000000708d118d44c1cb5609667fd51df9e58cacce8b65565ef20ad1649a3e1b9453e4fb37af67c95387de008d4c2114e5b95,0000000000000000000000000000000004b2311897264fe08972d62872d3679225d9880a16f2f3d7dd59412226e5e3f4f2aa8a69d283a2dc5b93e022293f0ee1000000000000000000000000000000000f03e18cef3f9a86e6b842272f2c7ee48d0ad23bfc7f1d5a9a796d88e5d5ac31326db5fe90de8f0690c70ae6e0155039 +00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000008e5afc16d909eb9d8bdaaf229ad291f34f7baf5247bbd4cc938278f1349adb4b0f0aacd14799c01d0ca2ed38c937d600000000000000000000000000000000006cf972c64e20403c82fee901c90eaa5547460d57cce2565fd091ff9bc55e24584595c9182298f148882d6949c36c9d5,000000000000000000000000000000000caf46f480ae2ea8e700f7913c505d5150c4629c9137e917357d2a4ba8a7a1c63b8f6e2978293755952fbed7f0ad8d6d0000000000000000000000000000000002e62e715b72eebbc7c366a2390318f73e69203a9533e72340aab568f65105129ffc9889a8bc00a692494d93688c7ec0 +000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0000000000000000000000000000000013a6439e0ec0fabe93f6c772e102b96b1f692971d7181c386f7f8a360daca6e5f99772e1a736f1e72a17148d90b08efe0000000000000000000000000000000010f27477f3171dcf74498e940fc324596ef5ec6792be590028c2963385d84ef8c4bbb12c6eb3f06b1afb6809a2cb0358,000000000000000000000000000000000dea57d1fc19f994e6bdda9478a400b0ada23aed167bfe7a16ef79b6aa020403a04d554303c0b2a9c5a38f85cf6f3800000000000000000000000000000000000b8d76ccd41ba81a835775185bbf1d6bf94b031d94d5c78b3b97beb24cf246b0c25c4c309e2c06ae9896ed800169eeee +0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff320000000000000000000000000000000005728a219d128bc0a1f851f228e2bf604a72400c393cfb0d3484456b6b28a2c5061198656f0e106bbe257d849be159040000000000000000000000000000000011f6d08baa91fb2c8b36191d5b2318e355f8964cc8112838394ba1ded84b075de58d90452601dcfc9aa8a275cfec695d,0000000000000000000000000000000012e6d6c518c15cfd3020181ff3f829e29140b3b507b99251cc7f31795128adec817750296bce413bac18b9a80f69ca5000000000000000000000000000000000131ee9b748f6f1eb790adeb9edd0e79d89a9908368f5a6bb82ee0c913061cdfffe75d9ba411a49aa3f9194ee6d4d08a9 +0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f200000000000000000000000000000000171696781ba195f330241584e42fb112adf9b8437b54ad17d410892b45c7d334e8734e25862604d1b679097590b8ab0a000000000000000000000000000000001879328fdf0d1fb79afd920e0b0a386828be5b8e0e6024dfeea800ffcb5c65f9044061af26d639d4dcc27bcb5ba1481a,00000000000000000000000000000000111c416d5bd018a77f3317e3fbf4b03d8e19658f2b810dc9c17863310dfb09e1c4ffdbb7c98951d357f1c3d93c5d0745000000000000000000000000000000000af0a252bff336d5eb3a406778557ef67d91776a9c788be9a76cff7727f519a70fc7809f1a50a58d29185cb9722624fd +000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c2000000000000000000000000000000000231b0d6189a4faad082ce4a69398c1734fcf35d222b7bce22b14571033a1066b049ae3cd3bd6c8cec5bec743955cdd600000000000000000000000000000000037375237fb71536564ea693ab316ae11722aadd7cab12b17b926c8a31bd13c4565619e8c894bffb960e632896856bbe,000000000000000000000000000000000d2b9c677417f4e9b38af6393718f55a27dbd23c730796c50472bc476ebf52172559b10f6ceb81e644ec2d0a41b3bb01000000000000000000000000000000001697f241ff6eceb05d9ada4be7d7078ecbbffa64dd4fb43ead0692eef270cb7cc31513ee4bf38a1b1154fe008a8b836a +00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000015653d1c5184736cdc78838be953390d12b307d268b394136b917b0462d5e31b8f1b9d96cce8f7a1203c2cae93db6a4000000000000000000000000000000000060efeece033ac711d500c1156e4b6dce3243156170c94bc948fd7beae7b28a31463a44872ca22ca49dc5d4d4dd27d1c,0000000000000000000000000000000003996050756117eeab27a5e4fa9acdde2a1161d6fbfff2601a1c7329f900e93a29f55a8073f85be8f7c2a4d0323e95cc00000000000000000000000000000000010b195a132c1cba2f1a6a73f2507baa079e9b5cb8894ea78bebc16d4151ee56fe562b16e2741f3ab1e8640cdad83180 +000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc0000000000000000000000000000000018adb42928304cbc310a229306a205e7c21cdb31b9e5daf0ff6bb9437acee80cd8cf02b35dab823155d60f8a83fde5cc0000000000000000000000000000000018b57460c81cab43235be79c8c90dcda40fafcaf69e4e767133aee56308a6df07eac71275597dd8ed6607ffb9151ed9a,0000000000000000000000000000000003c7a7ee3d1b73cf1f0213404363bf3c0de4425ab97d679ed51448e877b7537400f148f14eba588ed241fea34e56d465000000000000000000000000000000000c581b5070e6bb8582b7ee2cd312dfeb5aaf0b0da95cf5a22a505ffba21fc204e26a5e17311d1f47113653ff13349f57 +0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da00000000000000000000000000000000001da65df8574a864ab454e5f2fa929405501bb73c3162a600979a1145586079361c89839cc0c5a07f1135c94bf059f9c0000000000000000000000000000000002560df402c0550662a2c4c463ad428ab6e60297fbc42a6484107e397ae016b58494d1c46ac4952027aa8c0896c50be3,000000000000000000000000000000000d7a539b679e5858271a6f9cf20108410eb5d5d2b1a905e09a8aa20318efbe9175450385d78389f08f836f5634f7a2f0000000000000000000000000000000000fb624e5f6c4c814b7d73eb63b70237c5de7d90d19ac81cac776d86171a8d307d3cc8c56da14f444fe8cf329ab7e63dd +0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e0530000000000000000000000000000000005311c11f4d0bb8542f3b60247c1441656608e5ac5c363f4d62127cecb88800a771767cf23a0e7c45f698ffa5015061f0000000000000000000000000000000018f7f1d23c8b0566a6a1fcb58d3a5c6fd422573840eb04660c3c6ba65762ed1becc756ac6300e9ce4f5bfb962e963419,0000000000000000000000000000000000849bbc7b0226b18abbcb4c9a9e78dca2f5f75a2cbb983bd95ff3a95b427b1a01fd909ce36384c49eb88ffb8ff77bb000000000000000000000000000000000087d8d28d92305b5313ca533a6b47f454ddce1c2d0fa3574b255128ef0b145fa4158beb07e4f0d50d6b7b90ea8a8ea8a +00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e000000000000000000000000000000000c8e293f730253128399e5c39ab18c3f040b6cd9df10d794a28d2a428a9256ea1a71cf53022bd1be11f501805e0ddda40000000000000000000000000000000003e60c2291be46900930f710969f79f27e76cf710efefc243236428db2fed93719edeeb64ada0edf6346a0411f2a4cb8,00000000000000000000000000000000191084201608f706ea1f7c51dd5b593dda87b15d2c594b52829db66ce3beab6b30899d1d285bdb9590335949ceda5f050000000000000000000000000000000000d3460622c7f1d849658a20a7ae7b05e5afae1f01e871cad52ef632cc831b0529a3066f7b81248a7728d231e51fc4ad +0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc80000000000000000000000000000000013267db8fdf8f488a2806fead5cffdcbb7b1b4b7681a2b67d322cd7f5985c65d088c70cdc2638e679ed678cae3cc63c80000000000000000000000000000000007757233ad6d38d488c3d9d8252b41e4ab7ee54e4ef4bbf171402df57c14f9977dd3583c6c8f9b5171b368d61f082447,000000000000000000000000000000000c06fef6639ab7dceb44dc648ca6a7d614739e40e6486ee9fc01ecc55af580d98abc026c630a95878da7b6d5701d755c0000000000000000000000000000000007c9a7f2bc7fa1f65c9e3a1e463eb4e3283e47bb5490938edb12abf6c8f5a9b56d8ce7a81a60df67db8c399a9a1df1d4 +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17000000000000000000000000000000001975bc52669187f27a86096ae6bf2d60178706105d15bce8fe782759f14e449bc97cb1570e87eec5f12214a9ae0e0170000000000000000000000000000000000ca6106d6e6487a3b6f00fc2af769d21cb3b83b5dc03db19e4824fc28fd9b3d9f7a986e79f05c02b3a914ff26c7a78d6,0000000000000000000000000000000002fbf4fba68ae416b42a99f3b26916dea464d662cebce55f4545481e5ab92d3c40f3e189504b54db4c9cd51ecdd60e8d0000000000000000000000000000000008e81e094c6d4ded718ef63c5edfacb2d258f48ccfa37562950c607299bb2dca18e680a620dff8c72dedc89b4e9d4759 +0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f00000000000000000000000000000000109f6168a719add6ea1a14f9dc95345e325d6b0e56da2f4ecff8408536446894069fa61e81bdaebfc96b13b402fad865000000000000000000000000000000001806aa27c576f4c4fa8a6db49d577cd8f257a8450e89b061cbc7773c0b5434f06bacf12b479abf6847f537c4cbefcb46,0000000000000000000000000000000014e0bd4397b90a3f96240daf835d5fb05da28a64538f4bf42d9e7925a571f831c6e663910aa37dcc265ddd7938d83045000000000000000000000000000000001695d405d4f8ba385ebf4ad25fb3f34c65977217e90d6e5ed5085b3e5b0b143194f82e6c25766d28ad6c63114ca9dcdf +00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af0000000000000000000000000000000019d3623a7866933e2d73214ceb2e56097a1b047db5943c3ecb846890aa02250126e90fc76a729a952cef895bd154cc7d000000000000000000000000000000000e87c376bbd695a356ef72226ac7ef6a550d99e9693d8485770a686e568ae28c038ee201d3f2ea38362046236ade91cd,000000000000000000000000000000000ffeab47985bd9b3e10ce27c6636bbda336dcf540cd37eccc3faec2adff2d97dd126633bd83a7d3c8c73c3623bdf0ba2000000000000000000000000000000001992eca4b1e924b360d57ca98b543ab496a8b55bd288d23f03bcc1b22f6bc76d95b12f47c3e305812097253c73b876dd +000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c300000000000000000000000000000000163aaecf83d6c77a5d7417e73f5cf9d71a6aedfd194b2f3b53c608d06a228190f4f79ac57b029d77504c72744df4ecc0000000000000000000000000000000000416e6f9ca188d16daa2c28acd6a594f8fcb990eaa26e60ca2a34dfcad7ad76c425b241acedf674d48d298d0df0f824d,000000000000000000000000000000001812bcb26fa05e0ab5176e703699ab16f5ef8917a33a9626ae6ff20f2a6f4a9d5e2afe3a11f57061cbaa992e1f30477f000000000000000000000000000000000680acf0b632cb48017cb80baa93753d030aa4b49957178d8a10d1d1a27bbdc89ac6811a91868b2c181c5c0b9b6caf86 +0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000000aba7362eee717d03ef2d4f0fef2763822115fcc8fb9e2e8243683b6c1cde799ebc78f23812e557de2cc38e2b4a2e56700000000000000000000000000000000170833db69b3f067cf5c4c4690857e6711c9e3fcad91ca7cd045e9d2f38c7b31236960e8718f5dd4c8bfb4de76c6c9b9,00000000000000000000000000000000196ffe76a4b726fa8dd720cc1cd04c040724cb18ec10915e312eaa90d124100b08f0ce3a7fc888f46914319a3d7581f4000000000000000000000000000000000e2612357059ca6dbb64efb98ef19370560c9e83e2aad7ab2d9015e2444fe4d8c796b5577584aac9f63258beb5ae863c +00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da8257721808000000000000000000000000000000000a98ae36c690f2e3be8100f43678be5a1064390e210328dd23f61f5a496b87398db2798580edeabc6273fb9537fa12880000000000000000000000000000000009aedf77bb969592c6552ae0121a1c74de78ba222b6cd08623c7a34708a12763b5ff7969cf761ccd25adc1b65da0f02d,00000000000000000000000000000000072334ec8349fc38b99d6dea0b4259c03cd96c1438c90ef0da6321df2495892de031a53c23838ca2b260774fa09b5461000000000000000000000000000000000e4535767c2477c4f87c087540c836eeffcd0c45960841f9c3561a8a5f8e61ab98b183b11192b8e7ea1c9c7717336243 +000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e00000000000000000000000000000000015c3c056ec904ce865d073f8f70ef2d4b5adb5b9238deaa5e167d32f45cad4901aa6d87efa2338c633e7853ce4c19185000000000000000000000000000000000a15f1aa6e662f21d7127351a1655821c943c4cf590e3c9e60c9ab968b4a835f87fb8d87eee6331ee4e194e5f1ea91f4,000000000000000000000000000000000140fb6dcf872d0a3bff3e32a0cb4a7fb7e60ee4fb476bb120c4ce068e169d72e1c167d7fda321280d5855983d5a9af800000000000000000000000000000000108f54a4ec3ba26dd614f4d94c5c82652583906986158ad40ffea54c17703fa4b0bd7806633e1c0318d06e8dc7d41cde +0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb000000000000000000000000000000000307841cb33e0f188103a83334a828fa864cea09c264d5f4343246f64ab244add4610c9ccd64c001816e5074fe84013f000000000000000000000000000000000e15bbeb6fff7f1435097828f5d64c448bbc800f31a5b7428436dcffd68abc92682f2b01744d7c60540e0cd1b57ab5d4,000000000000000000000000000000000a1b50660ed9120fff1e5c4abb401e4691a09f41780ca188cea4b1c2d77002f08ce28eb1caa41ee3fe73169e3651bb7f00000000000000000000000000000000125439ac3b45c698a98063ab911364bd3c6dd2a69435d00d6edf89fc5566b33038e960a125e5e52141abb605587942fe +000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000013866438b089d39de5a3ca2a624d72c241a54cbdcf5b2a67ebdd2db8373b112a814e74662bd52e37748ffbfc21782a5000000000000000000000000000000000d55454a22d5c2ef82611ef9cb6533e2f08668577764afc5bb9b7dfe32abd5d333147774fb1001dd24889775de57d305,000000000000000000000000000000000037b4e8846b423335711ac12f91e2419de772216509d6b9deb9c27fd1c1ee5851b3e032bf3bcac3dd8e93f3dce8a91b00000000000000000000000000000000113a1bf4be1103e858c3be282effafd5e2384f4d1073350f7073b0a415ecf9e7a3bfb55c951c0b2c25c6bab35454ecf0 +00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f180320000000000000000000000000000000017440fd557df23286da15f9a96bb88cfbc79589b1c157af13baf02c65227dc0a5bdec6f2f300083ff91dae395ed8cb75000000000000000000000000000000000ad09b4290842cc599d346110fdb39ededbb1d651568579564e274465f07b8f77eeaf00fece0c10db69c2125de8ab394,0000000000000000000000000000000007c158b4e21566742f7e4e39a672bd383e27864505acef4ef8c26f8b0a9db418f9c088b555b8e9eb25acf9859b1207b40000000000000000000000000000000016e06a1ace89f992d582af0de7662ef91c0a98f574306f6f6d0d8d5e80166638d2deef70105cce2e9b20faa9d6315510 +000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0000000000000000000000000000000000d7ccc3a4efdfe1a92a88e453933b8216016091f1b9d575faf18a5b3abf90daf077813167a3f4acce7359472dee544bb00000000000000000000000000000000128008c075ab176100e755cbb8de5b9ff0e9a78114f862d26ed030d9c1d1dea1c21ec8ae4d82a84d3ff5ae4c1cd6f339,000000000000000000000000000000000b84f9de79c748e37797c629cb78b86b4b736b199f161b30147b5dacf6eabe0b54afce40d5dacfe9a8ee8da5ef5b49de0000000000000000000000000000000010277ad094bb9a3b96379b1366dd90125b51a21ebeb4f776a81d9d9c1f37ab58c32a884a26fa32c83783ed0eef42b820 +000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e00000000000000000000000000000000008da4a93d5ffcdaa0adc736a59f0c187ae3bf11ecb5e9e6f6aedea976a47757739042200b4c4593c2dd5db555425531000000000000000000000000000000000a6fdb2d4160c6c35223daa6fa10d0b1073de07fe4f2eba28e65ed049ff8d8852ed0538b30759fe7a0d944009ddf9a6f,000000000000000000000000000000000d740bd1effd8674250618af0358ad0b83bbc787f0264af9c2ada72fa5431be909e82155da1de0211f46fb307e9949f0000000000000000000000000000000000ddf62c91d587a14b64feef07da52c081b40fbbf9a0f2eae8b66022e0850fc94de6a467e7e4f580c7f2c806f6c6ed8cf +000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d790000000000000000000000000000000003258d7931a1d72ab6344c7e96c0dbd435a7909fe68cc679c08ca9b62f7a6a04863082cbcfdbe9a736625d895e4f3bdb0000000000000000000000000000000009ee3e470e2b2cebc955ba3444b7e478f887138e36c13bd68490689122627269ea5e7ce22dd9c69792394a24187103d6,000000000000000000000000000000000af674691f5d87655f0066188fac5013f31b4169a0181d3feb7ac3beae0d9a3429d4125f099ee344f644a2de8b941f9f00000000000000000000000000000000042a9603b8e4a6c37d59ede3a1398f5f80c5298da66de575a204ee28811d9f7c7c0dd40cef3769bd72a2156b9eb620c8 +000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820000000000000000000000000000000001833807f1ced52399305419450355499a63411837ee61ad681559d59561db18511eb1e8ad3161e7fe30016b560d18b8f00000000000000000000000000000000198b11b31586e17964a4a4ccdee85703163d2106481833e71f26327a589bafb43578d08d87f6cb19c7a04b4ca92392bf,000000000000000000000000000000001081c3359a0fadfe7850ce878182859e3dd77028772da7bcac9f6451ac6455739c22627889673db626bbea70aa3648d50000000000000000000000000000000000f4e8766f976fa49a0b05ef3f06f56d92fe6452ff05c3fac455f9c16efadf1b81a44d2921bed73511dda81d6fc7478e +000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8,000000000000000000000000000000000f60d66fd1ed5eb04f9619d6458c522cc49f5ace111aff2b61903b112559972f80ac615591463abf2b944c4f99d4c03e000000000000000000000000000000000001a1abfa869be2cda6bd7e05454a8735e1b638db7e1b3715708539c2d14ade53069c7e68b36d3b08cff80837028b7d +0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd0000000000000000000000000000000014b78c66c4acecdd913ba73cc4ab573c64b404a9494d29d4a2ba02393d9b8fdaba47bb7e76d32586df3a00e03ae2896700000000000000000000000000000000025c371cd8b72592a45dc521336a891202c5f96954812b1095ba2ea6bb11aad7b6941a44d68fe9b44e4e5fd06bd541d4,0000000000000000000000000000000015b164c854a2277658f5d08e04887d896a082c6c20895c8809ed4b349da8492d6fa0333ace6059a1f0d37e92ae9bad30000000000000000000000000000000001510d176ddba09ab60bb452188c2705ef154f449bed26abf0255897673a625637b5761355b17676748f67844a61d4e9f +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe900000000000000000000000000000000104ee0990ba4194916f670f44e254200971b67a18ed45b25c17be49df66e4f9b934bac8c1552ecc25bdaa3af55952076000000000000000000000000000000000591094d9d89afe025ca1832d7f3e60444f83e72403a434b42216b6c4213980d29e4ef0c64ae497006de550c1faa9425,0000000000000000000000000000000006db0cc24ffec8aa11aecc43e9b76a418daac51d51f3de437090c1bcaabace19f7f8b5ceb6277d6b32b7f3b239a90c4700000000000000000000000000000000069e01f60ca7468c6b9a247c79d18cf3d88bf5d1d62c76abf9237408edeba05dea744205ac5b501920f519bb847bb711 +00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a0000000000000000000000000000000004840d028d0c0f056aeb37b7a8505325081e9822ef26046f2da72f2155c20987dd51f4b5577c5395e24288b71d2ce5140000000000000000000000000000000015f231a233e997633c1d6492e0df358fb658ae29d0f53928c8a0578484c899a699178ca3223772210063aa08991c3fff,000000000000000000000000000000000fa72bf2d7d564cc4982b9f2cdca743d2ac14f0f1be4218dbafb8b93a9277e55273487a5d2857fd3f731ac4ee469a6a1000000000000000000000000000000000fce44f886453c6ca5ebde9af41d2be92d1126e9897d72978a179dd7eebeed6242b6e9718604ab0c9369529a0426a575 +000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece1738220000000000000000000000000000000004877b97faa1d05d61ab65001110bf190d442cabcd6d4d1b9c1f0e513309aebd278f84a80354dfdef875769d00ec2c7500000000000000000000000000000000187066cccb5008bc2ffd0bcd1b227a5a0fe0cd4984316ba3cfd5113c4632a04c56cbda8d48993bd0dd50e9b7ce2b7ee9,0000000000000000000000000000000019ecd38afacc6b281b2515270157328e18039d51574bae0f7e0ef16c3f6da89f55ddee9e3bbb450ad51fe11edfd9f18d00000000000000000000000000000000088a5e292761bbf7a914a9f723de099035e91bd3c1fe9cd50728a4ceaa4fd3953683f30aa8e70ba0eb23919092aa9e22 +0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000001881f5aba0603b0a256e03e5dc507598dd63682ce80a29e0fa141b2afdadf6168e98221e4ee45d378cee0416baaadc49000000000000000000000000000000000070d255101319dd3a0f8ca3a0856188428c09de15475d6b70d70a405e45ab379a5b1f2e55f84bd7fe5dd12aeedce670,0000000000000000000000000000000011ccd455d5e3eba94567a17bcd777559b4ff1afa66fd6f05f99c69937404290a2f1c83cfd6c2c25886ebff4934332c0e0000000000000000000000000000000010920aa3d5974df25530610ef466adce3d51fd6a508d4b1111739c586dfd7ba9040836e075fd812fe111d92f25b67f51 +0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf000000000000000000000000000000000b53e5339f25bcd31afd091362874b5042c0b762ed7425341331630addbc4dccc299936e1acdf89823c36867d46c6f28000000000000000000000000000000000fc3c6b522268511dd52826dd1aee707413d925ee51aeb0e5d69c0e3eb697fabbc14783b5007e240cc0c53c299a40ada,00000000000000000000000000000000060773b9b8f3babdba3db27089b7be3e6e287a635dbae19576039d34ae18a0e6413278bfa280570f6329ae05cdb693fd00000000000000000000000000000000075fb9527f99a8c8db41e67baaf1deafffd2c134badb1b3478a26b5501b31dca858fad6f0d52f412d5631ecfa72eece4 +0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000001693f4ebab3fed548784264196fb01cf55311399f47cdad74a9543bda5d1ca682a00ee04bb0b3954d5a0f00ceef97a750000000000000000000000000000000017f4019c23bd68e84d889857c417b17aa96c780fec3c1ed6ca75100cc70c97a8bb8272ad4c6de896d76dc2a1b09c7a61,000000000000000000000000000000000a3ea8afdc83794f18f9a9427bcd60a355196925d38fdf74ab09d4a08279647b2da6f1fbe30948a785497d6c6dddc2a9000000000000000000000000000000001263c88f1ca3e574cafac21641432d45ee01e1b05eba95716565922abe28c7f0fb004c255afcbfa10cf7959bbe6b00d7 +00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000005d5602e05499a435effff3812744b582b0cd7c68f1c88faa3c268515c8b14f3c041b8ae322fe526b2406e7c25d84e61000000000000000000000000000000001038eaf49e74e19111e4456ebba01dc4d22c7e23a303d5dec821da832e90a1b07b1a6b8034137f1bfdcddeb58053a170,0000000000000000000000000000000019258ea5023ce73343dcd201ec9be68ec1ee1cb4e5b9964309d801c2bc523343c8ebc4f8393a403c7881e5928f29db14000000000000000000000000000000001423bf52daefb432162ce2bd9ef78b256ff3b24d0a84766b87119489fd56ecf6156b2884c8a7e1220e493469723cd7f8 +0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000002626f28d421d9d1c28f5e1eb5a51ada9610dbdd62cd33c4078d2fdfc18dbd092e2847cf705ba5fcd8c1a60c1cc34a3b0000000000000000000000000000000001f7b8cfdb7e406c920f5fdecae45fb4be736f209480ccb455f972c6b1a1aebdd5ba116903c46ded72ce37cd8836e871,00000000000000000000000000000000081d674f5b9c7c64673c39fe33f4f3d77271e826dcb4dfd2591062e47c931237e8539ef9c886c9e112eccc50da4f63fd00000000000000000000000000000000141b700695839110ed4ced5f8a3f4fd64a8086805358ab4a5abd2705592e616cd95ff01271212ca9014dcb68d8157ba0 +0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df91000000000000000000000000000000000259e307eacb1bc45a13811b02a7aeaaf4dc2bb405dcd88069bb6ec1c08a78905516169bd3440a36921764df0ef3a85b000000000000000000000000000000001263372b675124f6cc19ca16842ba069c5697dbf57730875fe72c864a81189d7d16fe126b5d24953a0524f96dbac5183,000000000000000000000000000000001908aa3a640817e31a4213156fbd4fd39ab39eb931091670a0e06399def71a689e67286f90d38ce9f97cb85f6488d9c8000000000000000000000000000000000764e46b6b82aa2f8862d28e9d543a751a9de855645377b9633cc098c2110ec6ed4fd30f0044ea5868c93f950f6cfd24 +000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000a138203c916cb8425663db3bbff37f239a5745be885784b8e035a4f40c47954c48873f6d5aa06d579e213282fe789fa0000000000000000000000000000000016897b8adbc3a3a0dccd809f7311ba1f84f76e218c58af243c0aa29a1bb150ed719191d1ced802d4372e717c1c97570a,0000000000000000000000000000000004ad79769fd10081ebaaed9e2131de5d8738d9ef143b6d0fa6e106bd82cfd53bbc9fab08c422aa03d03896a0fb2460d0000000000000000000000000000000000bb79356c2d477dfbcb1b0e417df7cb79affbe151c1f03fa60b1372d7d82fd53b2160afdd88be1bf0e9dc99596366055 +000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29460000000000000000000000000000000019f60f2cf585bdbc36947f760a15fa16c54cf46435cc5707def410202a3f4fa61b577ab2481e058b0345982d3e3d1666000000000000000000000000000000000a70b7bbc55e1f3e11e9eb7efd79d4e396742de48d911ddff8dd0a7cf10422423d5e68021948e1448e92c2e07c194776,000000000000000000000000000000000a87e7e115ccdf3c2c1a2716491d449c3f8329e73d264088f4af444d43cf05f8be0410da273ce7eeb32969830195b7e70000000000000000000000000000000010a973d6e4bd85105bf311eb0dcfdc0a5d38dba1c099206b60f2e2df4791fd58846bf19d83769506e1561212920b4895 +000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000109bd6e0636a7f96ffe2ce8e109171efaacfcd60189c7050259ddedd15dd257e11f2585bbd84e4a3f4d8fc5fbc0289cf0000000000000000000000000000000019b420d778da53aed81b48f2c9b9eb399e771edd5e124a41577452b409ca2503e2798cd25d791f489352fc7b7268ae23,00000000000000000000000000000000162bd29f2de10002c1c446bd9583e89751fb91703ad564e7951d41673e28d214729aa9b4b9875c397989df197c912d5f0000000000000000000000000000000004d393181871c93714afab6c33c16f68ec391fbfcad606ac65cc1d070949c099e21f710e2fe0dd4e4f50f99ea2167a7e +000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000012bb529b45ad7875784b62a7281d025002f15e7f86cc33555e7472df60da2cb15d37c8bf628142818c0711ee9047fb4d000000000000000000000000000000000baa801623312d95e2b51ce86373fea516007e468f265d974c2327c1779830db180bed6dbe8a64f0959aad26eaafb8d9,0000000000000000000000000000000010c4b328d264893099d89ba81b0765d0642bf36b0ac043be090c7b4f7987d21a906228c3c208c4ec5123d577efb0771f0000000000000000000000000000000016d08ce3bf755da7d4bae5f4b06b37845c17a717329c547e941be93325a04e9a5095d3f6e6c6f9ec3b1a740f59d88919 +0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000002c9e50f37ff0db2676637be8a6275fce7948ae700df1e9e6a0861a8af942b6032cca2c3be8b8d95d4b4b36171b4b0d400000000000000000000000000000000050f1a9b2416bbda35bac9c8fdd4a91c12e7ee8e035973f79bd35e418fd88fa603761e2b36736c13f1d7a582984bd15e,000000000000000000000000000000000f798f8d5c21cbce7e9cfcbb708c3800bf5c22773ec5b44590cdbb6f720ccddf05a9f5d5e6a51f704f7c295c291df29f000000000000000000000000000000001483903fde5a968dba6924dfac3933cd39f757e2f89120f4ca9d03aaaf9e18252bdb5c5d3939471666b8a42aeb31b4ed +00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec000000000000000000000000000000000332cdc97c1611c043dac5fd0014cfeaee4879fee3f1ad36cddf43d76162108e2dc71f181407171da0ceec4165bcd9760000000000000000000000000000000015b96a13732a726bad5860446a8f7e3f40458e865229bd924181aa671d16b2df2171669a3faa3977f0ee27920a2c5270,0000000000000000000000000000000001c762175f885a8d7cb0be11866bd370c97fb50d4277ab15b5531dacd08da0145e037d82be3a46a4ee4116305b807de6000000000000000000000000000000000bb6c4065723eaf84d432c9fde8ce05f80de7fe3baed26cf9d1662939baac9320da69c7fe956acdd085f725178fe1b97 +0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c50000000000000000000000000000000003ebca978ea429eedad3a2c782816929724fc7529fbf78ea5738f2ca049aab56c1773f625df2698433d55db7f5fc8ca2000000000000000000000000000000000d2477f57b21ed471a40566f99b7c2d84ce6b82eaf83a6c87a7c21f3242959c8423d4113b7fd8449277b363303bb17b0,00000000000000000000000000000000071dc0f985703bd8335093779de651b524c02faca5fc967766abd3f6f59176d2046d7a14d18c0b757b8c9802e44ebcd300000000000000000000000000000000154e5cb66be8979ee276e8e0f240557e3f7dc074c497293af589256652da21d66a6e6b00ca5bfa6f89963fbd5bc6cf48 +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001461afe277bf0e1754c12a8aabbe60262758941281f23496c2eeb714f8c01fd3793faf15139ae173be6c3ff5d534d2bc00000000000000000000000000000000148ad14901be55baa302fa166e5d81cc741d67a98a7052618d77294c12aea56e2d04b7e497662debc714096c433e844e,0000000000000000000000000000000012c4dd169f55dfb5634bc4866f7cbd110648b5392ace6042b5f64aba3278f24085227521b7834864f00d01ec9998dd6800000000000000000000000000000000102d7a495850195424677853da01d70caeb6c0af5270bcfffbc2d4252c0f3680518cd8d2a0a6dbbbc7b52923a5b26562 +000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002218b4498c91e0fe66417fe835e03c2896d858a10338e92a461c9d76bcecd66df209771ae02c7dcace119596018f83c000000000000000000000000000000001990233c0bae1c21ba9b0e18e09b03aeb3680539c2b2ef8c9a95a3e94cf6e7c344730bf7a499d0f9f1b77345926fef2d,0000000000000000000000000000000010c50bd0f5169ebd65ee1f9cd2341fa18dd5254b33d2f7da0c644327677fe99b5d655dd5bfdb705b50d4df9cfce33d1400000000000000000000000000000000088e47ffbbc80c69ec3c5f2abe644a483f62df3e7c17aa2ff025553d1aaf3c884a44506eff069f4c41d622df84bbafa1 +000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7000000000000000000000000000000000160e0f540d64a3cedba9cf1e97b727be716bbfa97fbf980686c86e086833dc7a3028758be237de7be488e1c1c368fe100000000000000000000000000000000108250b265bd78f5e52f14ef11515d80af71e4d201389693a5c3ef202cf9d974628421d73666ead30481547582f7abaf,00000000000000000000000000000000168af33c85ae6e650375ed29b91218198edd9135683f6a1428211acdcbf16bdf86f0a95575e47ee0969587a10fa9f3c90000000000000000000000000000000012d9f5d692c870b3da951b6d07797c186a8ddc89b9f08a1c0b8f0f119f10ca0b155e8df5424cf48900ad3bf09ce6872a +0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d20000000000000000000000000000000002fa19b32a825608ab46b5c681c16ae23ebefd804bb06079059e3f2c7686fe1a74c9406f8581d29ff78f39221d995bfd000000000000000000000000000000000b41ea8a18c64de43301320eaf52d923a1f1d36812c92c6e8b34420eff031e05a037eed47b9fe701fd6a03eb045f2ca7,000000000000000000000000000000000b99587f721a490b503a973591b2bb76152919269d80347aeba85d2912b864a3f67b868c34aee834ecc8cd82ac1373db0000000000000000000000000000000007767bb0ca3047eee40b83bf14d444e63d98e9fc6c4121bdf04ea7148bcfaf3819b70dcebd9a941134e5c649da8f8d80 +0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000002a540b681a6113a54249c0bbb47faf7c79e8da746260f71fbf83e60f18c17e5d6c8a7474badafee646fe74217a86ca4000000000000000000000000000000000fe2db7736129b35dc4958ffd0de7115359857fb9480b03a751c4fceb9ae1b2b05855398badffc517ae52c67f6394e2a,000000000000000000000000000000000bc719a8397a035fc3587d32d7ef4b4cfd63d4a5619ab78301d59659208f86df9e247e5d12650acc51a3bca3827063a900000000000000000000000000000000150d5519380a65b1909b0d84da374484675d99b00b254d03e423e634a012b286e3fe074e9b0a7bb24ff52d327249a01b +00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787900000000000000000000000000000000019d917eb431ce0c066f80742fe7b48f5e008cffa55ee5d02a2a585cc7a105a32bbf47bdff44f8a855ade38184a8279e0000000000000000000000000000000012ee762e29d91a4fc70bc7a2fb296a1dcdd05c90368286cca352b3d5fffc76e3b838e14ea005773c461075beddf414d8,0000000000000000000000000000000008197403ab10f32d873974c937ef4c27fbdb0f505c4df8ac96504705d4851cf951fb0263335e477063884527b21edf160000000000000000000000000000000005396f1affa20ca8530b519a4d5d400969f0c8c8731ecc0944e8086388e89a7ff7c16d9a2a90780972c4762b88a0f0af +000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000d280fe0b8297311751de20adf5e2d9e97f0c1bfe0cd430514cfddbafd5cdcb8c61bd8af4176cc3394f51f2de64b152400000000000000000000000000000000039f511e890187f28c7a0b2bd695ae665e89b0544c325a44b9109da52cc6908d81e1a27163a353ab275d683860c2e007,0000000000000000000000000000000002baea63055f72646189bdd133153dd83026f95afad5ce2cffbee3f74c8d47d5480094b2b58b0936c78aa33cd9a8f72f0000000000000000000000000000000013e600456a2d76f5a760059e0ba987b881c6bc10d6161f388d7a9d8b2031921054edfec46afbd80b1364d8e8f6a5a7a2 +0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000015bad24d12b5d68558e961a17dbc3e1686e1b918e6192ebe6f3f71c925177e61d0162e018ac81126099effa0cadfa185000000000000000000000000000000000de73182569184b3d79dcfa8c27f46ec7a31fe8a3fd73fe26eec37a088461192bdbcf4d4b37b33b6177d6fde015d1631,000000000000000000000000000000000ced641c930387432d512861eefbf2d6131017154f99a0d3d24da880dfd2aaae91c2d9634053fab8b85fc11a7884d30600000000000000000000000000000000122071c0e87fae5031c850dccc4777c3ec9d8463bbc4ed84364d4261bc9d38f696a4320d53eea926a75ed9fcc9789a07 +000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b256730000000000000000000000000000000015cdf7dafedce64aba34e1f18c57b28f297629c07ee96b732029b545cf5ea6afdf926daa6a48d1250c67aa2a8b797d370000000000000000000000000000000004867352f86267dbe8e32806e4ed02f1487e036051068f8e06d02e8dea6d3773b422e065d2db27c89ea69246d0185351,000000000000000000000000000000000e2c633351d627a075acd1e373bec96ba41b047f0307201f4b7c9978c1a72243d0b18113604cc421b8f66d76ec9b1360000000000000000000000000000000000844e258d602bf9aaa35ce46c4c91c80dd9337053d8ab22c1163a0571fcd1488a2ef57476e2b66dd9c26963b28284d11 +000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000077eb801bcde78e9dd73b58d2429a907ea0f5600a8005093d471be373bba23ea70bf828c766ccced6a46db84b440053f00000000000000000000000000000000101af9df2939089d72e42fe2dc3de3e32be8f4526a2263ebd872d0080ed4a152107bb3d2f56176bf72d5ae8bd0c30a3f,0000000000000000000000000000000010205c6be10a5fc5390b0e5ae47a8a822c8e9a7a96f113d081cde477ec0de7bf0e8385e61780b2335e4297edb35bcc6d000000000000000000000000000000001796af180463ed70cf330791c8201ee3f0fe52993f64819291bda33017285fcc3a515669b3d48a411276c849fa021f6f +00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0000000000000000000000000000000019b09bb7dddd11c5d0e304dac120b920601dd3a3505e478c88850cc701c17eb02aa7bfb20e4017a62fc4fb544d4f9e8f00000000000000000000000000000000048ad536cf89576d4cce83ef065bc16c47f1a28ae27bd71d30d8f2177a9c6f8b2ed0cdf872ead71bc5a1252bccb4a7e0,000000000000000000000000000000000fb047098a1996a625cd19021f81ea79895e038756878d8772aaee9b6bbb66930e474dcc04579ad58f4877b742a890900000000000000000000000000000000017da74a4caefc55794a36eda7938371f42265cc1f2d87d41883152db82873daeb59642e8e663afddd4f24536a1f52b3f +000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa30000000000000000000000000000000005f84f9afa2a4a80ea1be03770cb26ac94bec65cf9cb3412a07683df41bb267c2b561b744b34779635218527484633e30000000000000000000000000000000013ce1d1764961d1b0dff236c1f64eabec2ce5a8526edf6b0bccb9ea412e5a91880db24510435cf297fcc1b774b318b65,000000000000000000000000000000000f4ca788dc52b7c8c0cb3419ab62c26db9fb434321fc6830837333c2bb53b9f31138eecccc3c33461297f99a810e24ad0000000000000000000000000000000006785d4f9cdf42264c00fdc4452883b9050eb56e2f6e46c7b8fc8d937dfe4d3ad5072d969a47c4811b36d3887256d0b9 +0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000f0dd7a15dfc39dc2df47cf09761498b0b363157d8443356e768567f5a6d5913c2a67f12d93df2dcf50756bb686836b100000000000000000000000000000000055914dbda5b115222e738d94fbd430440c99bcc6d2c6cf7225c77756ffadf765b2d83447d395e876b5f6134563ed914,000000000000000000000000000000000ac0f0f62202d09cede55ca77b7344b46fd831b41015eb357cac07f0fa49c2564c2e9d5c591630226677446a9100757c000000000000000000000000000000000ca21d0128ef933fc1a48c1b4967f56912513e63a416d86ad40c0a4590b2edf88e4e8a286338b8b176d8b341ea480277 +000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb211000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe,000000000000000000000000000000000b728d4ae4b45fae9a9e242524e95e44f175356726da50f46236f690eec17fdd5edce5df1253383378dc8f9c1fee98ae00000000000000000000000000000000131d28a5eab968c45ddc86b82f220dcdeab7c009c7c61986ee4e55045c024e1bcbe76a4e35000b5699ccec5858ba427e +000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a000000000000000000000000000000000b35fcf625cde78fba1b70904acb97d7eb449d968e8013855d44292e9c3b0df3cfbcace6f292ec3c7717e25490bb4c67000000000000000000000000000000000af57abd87df55034c32dbe68bd1c0b47139fc2c3a8887b7c151e57b57c9002070337c8dcb2ce2687f9f007d48dd68c1,00000000000000000000000000000000178a19966b5b0fa70c138be7f5ea51d5399c7b8dcc5171cbef82ecb1451aeccbd1ed29170a27f404ebf6daa2ec99bd69000000000000000000000000000000000b1b748494806175030f6b5e2977c58982bd6ec6662d69237f0521351653c772a40035f2504ac8949fb448a901379fd6 +0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b00000000000000000000000000000000177a51fcc81580ccb7a8873fa93eaf860ca8fedde13cdf3eb53f11e66a1c1e934b82ee9251f711c5c479f33a22770c47000000000000000000000000000000000a0edc9a58f4bb414aa0aeec7bfa6076fb62bdbaee987192c18855adf4e813e7103b943e1dddc24754acfa90600a5750,0000000000000000000000000000000019195049a2d457709e284c84c72a211224efc4d7d46d25c9a537eea94149b06506df02a2a4e0a6428263e9605eaaacb500000000000000000000000000000000061139f9a70ce7cd87ed3a701163bde247382295f557b47a3a0a880d2780f015e8ac753eb3243f9ad138f92c3a2257c5 +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d000000000000000000000000000000001552982822e0b64a6204b27da0e192873bb5bd2997784ff0b6ed53801b402501a665c17f0a379fd946ab1adfae43c6af000000000000000000000000000000000938359655fe135dd2a390f83e27273feb68387ba94f2b6f7c15389f8272d64231ebe9c8271de90ff2358d935359ba85,00000000000000000000000000000000168f958a40e85341d90012e134976d1a5839e807948410cc0c81a50961552c052bb784c50da4c734f6aa583777c22b28000000000000000000000000000000000d26998bac6ec11bc5fcf6fe7262c984d6500cd5b21af979048b940e20054f8d759f8a011f3e09d01d10f9cf8ab150e1 +00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d50000000000000000000000000000000000d94885dcc21b0b98821b6861a4d094e9eb5d5adcf7ca4275c5b759abbf9a9910f3b38073183d54a0569ecbbc1e9826400000000000000000000000000000000034a54b4bbb3f128608a866f5f5c554cf6ad7899f6650ca663a5bd5f1a3e4471e35a2440644c0e4e0a56080936b46d12,000000000000000000000000000000000d4734ab1bbcf9e30cf142a7aa9e8cde1b3c88d92397b8d7d48c7a7402561feee58a810abf67776e1890489efe7f8ec20000000000000000000000000000000005be9e4af0c0c183c43601339f162345f7c013f5941167cd925057e91c4641e19091a20123a36f2e803142833c0bc1ef +00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000014f16cbb17e7f63284d8a75968a4c8fc8ee7f37233ed656d696477c507c23e7c7eaf54001f44c93deb14c298aa6f94c00000000000000000000000000000000169bde83e861889c50b2138c76531a5866235d515a6fee4da7aaf8e8b903f2848a9fe7bbd55eac7f1c58ce3a88e7249d,000000000000000000000000000000001400f774b2d932c6b990da6e1b3493685e8f51d429e0c53e9af1b4a2d3876781b790bca4a1bc28ce0240ea21be24a2350000000000000000000000000000000004993fcf5723b7e02095d4ba73ff3194bbe36027bc9099b57084c91c7e7d50b76331bfb06d3c678d3e401bc3f7fcc577 +000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000009acc4b4678b4b645fde47d1b75a5dda8caf6696ad2bf312dd5c12d7f3ab50b95152f5fe59842650c8a1a785f345c3ab000000000000000000000000000000000b672989004fe54f4d645e40cd29a21418151134fd2b90a68185040ceff141ced7f7ece1fdd9137c32589fa04b105a0e,000000000000000000000000000000000fcb0ab180a69b0a230d9dba98099fdce4969f82fc7e7ad93352a7c8dd448bb0ba9c7d62f53d5dc80506bc36190d9bc700000000000000000000000000000000047b7306f4a53c21d42993c50f2365486d02dac495f2dee4f8971a4af308396fce6c90f3cfde857bf7a2c6bf5d0d8aa7 +0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6000000000000000000000000000000000198e12ade128447a240e03e024183c401d605cab1ed81f0f5bb7bc4c7cc9c889a2a01f59c0e37a0767a927719e5a95d000000000000000000000000000000001946e39fee9b76ce552108b339b9b24d11e43d3275ac19d2d4bc745c409bdc3f7c473a60c4d3a4d2cc3b598ae0d66880,00000000000000000000000000000000050b45f896fa40099cda8b1f20ab88644915c16f926589cd709e00149b12922347fa7122175424cd44e8875f217b9ad7000000000000000000000000000000001122b7e9b1509efe5616368b14085bdd36fb7adb85cd5a7f23e327548986f5298c045a602b6ee1265d53a4432a4a3c0e +00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac47388620000000000000000000000000000000009c48aa2681b3005b24075bb3a122ac100cbaca872f761f4398edaba9dd9da6d04d4a4925028297dfe5f77c2b0b5c821000000000000000000000000000000000ea95c646fb68aa458e69c267a6ca640a6a24d40bdca0161246e4521d13c46facfc1ac86dfc0a804cfa6665cebeec822,0000000000000000000000000000000005325a499aec678ada9eb673d366fe0475e885d5188e2fb687a96949e8f782852fba962197976b868ec083c512bfb66b000000000000000000000000000000000c4d6fcacc8d82401882bee355b37930d83e3cea2e4a7bc133e65a3e0af919b25fc3f30c333873da9406845ce42dbb87 +000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae80000000000000000000000000000000008e8799a6cc0339e94e861692c81eee53e8a7b326523d5344b416bfbce04290585ef56018834cfd93d234bfa2943369f000000000000000000000000000000000fa1b01aab0878adad693ec769fb68640931c355b3802c51d4a3772300be5b16ceecdc8328a229b3b9f3639170db96f8,000000000000000000000000000000000685ec14da61c48bcb697966aca9e27601db43f0fb1f32e026fb33738eecfbb7012aa1ca3acf36a21fa846730245add70000000000000000000000000000000003fc52a1c3342b12271bbc178545bb20e96e8f1fde673e51f3d27ab5cb42e60aca49c6077e0f687be59b2d25cda9718e +0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000bb3a76287fb98fe668cb0a5de603c768340ee6b7f9f686a22da3a86926d8734d2c565c41f94f08fa3ef0e665f4ccb520000000000000000000000000000000016c02dbfb307c96d5b9c144672fe62f3e9cd78991844f246945ee484cbdef2a4c1b001a017cafb3acc57b35f7c08dc44,00000000000000000000000000000000021796fd6ef624eed7049b8a5c50415cc86104b2367f2966eb3a9f5b7c4833b9470ef558457426f87756d526d94d8dfe000000000000000000000000000000000f492dca3f0a89102b503d7a7d5b197946348e195954d23b8ab9ab7704b3bccecaa2123b8386662f95cd4cfdbbb7a64d +0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f00000000000000000000000000000000127420ff97df415e336cf3e24c39c161fad630c45c7ccef80f1831c4f5ed54da12f2c49a161e72bc70285fa0498e46d00000000000000000000000000000000013e605c21014f72364f8bff392ce64a10078ea537237fa282d5dd252ba1677b84b8c15d7925e54a4ab36f1feb13d3064,000000000000000000000000000000000ae916770455b0a63717e81802f5a7fcfbcc3e260b7adeca02a61a520c338d495eea29c4f070fd6efc1b8d23eb285e4c00000000000000000000000000000000134784e092744df573ba78f7d6f3cf1ed19491a0fc7ddfa02d3ca043bcf102fd40c33ac44b03a947308e3cc7af41c2df +000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab40000000000000000000000000000000016f41e8b098839944adc12481e5f965657a4faedd4f4cdea51a9597a6a0356989e791a686d3d2ee6232ab93683259c6b000000000000000000000000000000000d27b4a56b2cc2216e61eb41061f9a586a704652704906f7fe0eab869ba00d34205ea66f7a02d337d08b916598494e52,0000000000000000000000000000000012842c9d7f4309f6e40124a071d317f5597de419db0d5a8e5324a517f7b61dfdeea2fb4503ad7cdd8deb8aaa5c412554000000000000000000000000000000000ace4d9f98ee6e8a4416ef14d64f26dc49e102e69eced46ef829a352e58e8c1a7e1f083e3f4fc07f24ccd1685dedf215 +0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000019e7c8d182e3b674dfa21539613f7de5d4872d4f4732307a5c6d95ada7e81a01bc25bda34e0b46634e0b0b32cd47e8ec0000000000000000000000000000000008149237de73ab46d5c20dfd85b07f593c0caf2e2e364335450e3ebb478a9f6b9ac0af89174dffd92eda2783a5271f01,000000000000000000000000000000000875289fdaead079a283aafe4de7035c88662642b6bba389b17583f8e3b5801dada6e46bd897af961997665e6ed4a55700000000000000000000000000000000050a6b9c1db35865df0a042d27a042ff4b8d3bec2fba6a3a28a71c5a574620dc05cda0e70932ce9b8966e4592220c147 +000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6000000000000000000000000000000000c0f33f2d76366af661d6fa58a8b5aab207d35ce03899e495f7ddccedf201d9816f270468b207413a2ca70380c798fc60000000000000000000000000000000002a7dc7e2b163e65cadf93b5d682982288c8f36d08b1db8e0b1cb40cd3c7231f3f1672da42b4679f35db2076a8de5b42,0000000000000000000000000000000019ea92820dcd442358db359146797aa82beff6154946b1ea14dccae05e8252b776b817dc044a20764e3514cd22799c0b000000000000000000000000000000000ed929fef2cb11e8b6b9b5d52bfde82080eda747f0c82f33b9cb87019476f0c128e6b918a4486172dee2884ba538ae5d +000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000118fb45274a6b0ca9fe2654821e3b30caa46444f7c64b1921cf16dfd56a43916947d4fb6968d718a59a30ed38d65ce3000000000000000000000000000000000110e8e73e640bbea6927cd770baaf887c8e0e0c58260bca489c39b6dd7a24ab8c0c0a2495133d8ff8c7afb9790b37faa,0000000000000000000000000000000009452bd0a167683e30c673ffd4e750c66a81edf309a8d2d6dd915c358b30b0ffc001c4165b1b17bf157a0f966bfd91d00000000000000000000000000000000015df0b1ee359dd3e35a7b2c33edbb8e92b18804ae3359a369c6a529f5561298e6be9a3498c9477f33353124af7e91968 +0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000005dcb54cdf9635db275540c16307fc9f07b4ca5cd91e3977e4b95b58e8103e40ed9fa74752b2a43d95b6acb6f5fcbf440000000000000000000000000000000007ef8457752a47864ef2698176a53990e4822421ecf83b2716251e3ce69151ab2767d4a6611a0a6e0e40a57164ffb94e,0000000000000000000000000000000011f1ac702a06699dd64b63ebdd8b5381578f63b603c63c3a47413fe764af239ab7024712320f3ea3daefa6bd3cd3dfe9000000000000000000000000000000000918bb83a22b4fc66247e007c17155c4c2ec6326131c10fe04a5f9b82ddeca3d21c7c397a70a3949fda4d766540c85ff +0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35850000000000000000000000000000000006d3335e092616363e94436bb68be89667c706564ba687f4a3494fcf7da62fd9ad8ae68cb76524926c261983711a14ad000000000000000000000000000000000f085a3d013592c402a380e2e8d9019864a775e7b8e8b94603c8cc1eb1def1e91075fd5675f76534397e2a7d76c2331e,000000000000000000000000000000000344951ccb5e60d1838f7793fcf8b765f5f252b69e1cfdb4bd3c20692c8ffa01afbda6950974a65f6ac74afb9da5942e0000000000000000000000000000000014f5f0e6b99a04d1c5c2adf96c53dd41f8c01aab8db4f0e6d7fc5eab27f6c03c429632db4e1c21467c09d8a54066a4d3 +000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b0000000000000000000000000000000019e2ed6e9757e2339d013078fac91c966045f7a1416a56135d75e603c2021a8bebf4acbf6c0d5ba911f66510e9a7ad1a0000000000000000000000000000000008b8585444ffb3bd4fb6ee23e8128142aa72fd574a506151a0eea8979cbd694e03897caba63771b0490d46063bc5bb57,000000000000000000000000000000000a449fb0da911c544887b24860bc5fcaaf054041cc80f16bbb44c796520bee454d0d06f84fd5aa179a44fd4fac9f144a000000000000000000000000000000000fca81401349089caaef9156a86c64271c77235c9efd136dcfad9894450b076cb3dd1a05bfa1e62ef904435eee5d2250 +000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65000000000000000000000000000000000f4a256b4288386545957a3ba28278c0ce69a8a412febfed1f952ca13e673822bacb6b7751ea75893b680ea363aab66400000000000000000000000000000000152379d006e74798199f83b0c6c22a98440ef653d7f0a8c5e3026bcdabec8be59a3cc291ba05860bd0639c5c5f5bee26,000000000000000000000000000000000c427721953e139d4f12ad2a3f8f91a4caa49875a87001b619c8a6e909a7da8ddd9dd026bf56d5f85d49fd17527106a800000000000000000000000000000000018add2816914ef51a289e707ba0224fcf0b7bcfa4001487e90dbdce53f1b596e1f5872de32fcee6f63bce4484ccbef7 +00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d220000000000000000000000000000000012dae9aee13ed6ad52fe664bf7d2d0a1f134f0951d0d7ce5184e223bde164f6860967f9aaaa44fa6654d77d026c52d2a000000000000000000000000000000000f71889d64ec2f7da7319994883eb8bd1c753e6cdd3495036b630c35f07118a1bc10568c411ecbdf468a9cdaa9b4811b,000000000000000000000000000000000275b8efb3a3e43e2a24d0cda238154520f0a2b265f168bfc502b9cd4a07b930756961ae7e4fe3f01a5473d36ce3356200000000000000000000000000000000113403d5a968f01ba127dd8ef6c8d7b783a10d039a6b69c617032eba7122e9297f3ce2360c829ae64fdc9794695bf173 +000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe54850000000000000000000000000000000004e9dd69012ab596b5d3f1f8e4593b448685fcec4ab3394008178b137b762ddf9150cbb8dbb74c8af45bd8baab9a6c4f000000000000000000000000000000001132b66a2127885774062732127951f051c9c3c9b5aba02406e3f3cd4ecfe2dbf6614ebaca3bfe9efbe4f6e5b15ba0f5,000000000000000000000000000000000594c808954bb930bd038806500c9e3fd6460a83554e945baeeec2354a3805f046c76aea62c249080f16ae8e70f8fa6b00000000000000000000000000000000046924a32fb3f2df9a52615e45eeea2fa3ac0e2ccd38458194ada6b4d993ecdc0f441e41d0ea37599254a06aef68b9ae +000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b50000000000000000000000000000000017a81b957a12adf474a2913e8636f169ea9cd10be62c16b88f95f5caf661f158a032a9f7d249fdf2765caa1564bed0570000000000000000000000000000000017fbf2abc62dc2678b65d509e19c9c9c5d961c72565649a078da8dff98be6236ef314e9ff8022f639ff565353345c230,00000000000000000000000000000000002c8bc5f39b2c9fea01372429e92a9c945fad152da67174f4e478fdead734d50f6e2da867c235f1f2f11bdfee67d2a7000000000000000000000000000000000c1dd27aad9f5d48c4824da3071daedf0c7a0e2a0b0ed39c50c9d25e61334a9c96765e049542ccaa00e0eccb316eec08 diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_mul.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_mul.csv new file mode 100644 index 00000000000..167352d7ccf --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_mul.csv @@ -0,0 +1,101 @@ +input,result +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,000000000000000000000000000000000f1f230329be03ac700ba718bc43c8ee59a4b2d1e20c7de95b22df14e7867eae4658ed2f2dfed4f775d4dcedb4235cf00000000000000000000000000000000012924104fdb82fb074cfc868bdd22012694b5bae2c0141851a5d6a97d8bc6f22ecb2f6ddec18cba6483f2e73faa5b942 +00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d,00000000000000000000000000000000195592b927f3f1783a0c7b5117702cb09fa4f95bb2d35aa2a70fe89ba84aa4f385bdb2bfd4e1aaffbb0bfa002ac0e51b000000000000000000000000000000000607f070f4ae567633d019a63d0411a07d767bd7b6fe258c3ba1e720279e94c31f23166b806eabdb830bb632b003ca8b +0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1,0000000000000000000000000000000014f9bc24d65e3a2d046dbae935781596fb277359ba785808fd9ff7fd135ba8c1ddc27d97a16cc844427afbf4f8fc75a60000000000000000000000000000000017e3a485f84e2f2bdcf3255fe939945abe60dca5e0ae55eae9675dcc8d73e06d00b440a27ab4dc21c37f0bd492d70cf4 +0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a,000000000000000000000000000000000827517654873d535010e589eaf22f646cf7626144ca04738286de1f1d345342d5ae0eab9cd37ced9a3db90e569301720000000000000000000000000000000002a474c2443d71b0231d2b2b874a6aeac0452dd75da88e6f27949edafc7d094cb1577a79f4e643db42edcaecc17d66da +0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89,000000000000000000000000000000000d7e5794c88c549970383454d98f9b7cebb7fdf8545256f1a5e42a61aa1d61193f02075dc6314b650da14f3776da6ead0000000000000000000000000000000002054faff236d38d2307aa6cbbc696d50f5b3ffead1be2df97a05ebbcbc9e02eaf153f311a1e141eb95d411c0ec6e981 +000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944,000000000000000000000000000000000ff16ff83b45eae09d858f8fe443c3f0e0b7418a87ac27bb00f7eea343d20a4a7f5c0fcc56da9b792fe12bd38d0d43c600000000000000000000000000000000042a815a4a5dca00bd1791889491c882a21f0fe0a53809d83740407455cf9c980c5547961f9ebe61871a4896dace7fbd +000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1,0000000000000000000000000000000009e425f5bdc7df5c2a72303918e5a3c7d2fdeeb071179c533f83cdcf38dbbdb1ec5f4ebc85f3ed80757641ee3f8a8637000000000000000000000000000000000819a3e81e9ac2baacdc778225129e16344107517157ab2a7bc5e3480938585c55fd2dd7185f52251f5ab191f162cf5d +0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c,0000000000000000000000000000000015e6bea7ecf15d91bde67231f794397502c087960fab36d905137ce2608172b5a5def065cf7ee567ca7fb08a22adecf80000000000000000000000000000000001eed472d6138fbc56e10edb62563c086fdeb9acf6de957f2367db7f1c80d2c23197c09039ed55e65cb56de9fb9be64d +0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108,000000000000000000000000000000000220a71ad70fcf7e47df60381fbd1aba33c03a3f8537ba2029ad8e99b63c8677e0183f0b5bb2a5e1b23bc56693adb45c0000000000000000000000000000000017f26ac6ffc79ded7c08e08673336402f47ab48ef9ee2e46e3265e5cbb790cfc86f41bd1b578c5891eb052d11197c850 +0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672,0000000000000000000000000000000006b27724c4898b4f71be9727b773709a7905997d06a41ee618b7dcf864d7457bb3241046f0139c1d678b6ba6226f090f000000000000000000000000000000000b20cabf58f9c29897e20e91a9b482f5f867bef45ce0941cb8850aaa2022182298a1a24655a4b905f436520cc42a30cd +0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea,0000000000000000000000000000000004745f9877b3a0851df5bb770a54c69d5355cdadddc9d961e2bfdb3d0531d3d0f780f462335289be29ad4c62cb1250a00000000000000000000000000000000011034a094f59212c29e3f91c48df670e7a4021e4586645d250ee74a90f4b7b51510a5048dba3b555511c327ed211f81f +000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76,000000000000000000000000000000000841c1538c1a3b54418c1c5557a5815c9ed74f6e1c8ed70e1ad424220dc522c530e2e48affe6cb3190abb25af84b91a300000000000000000000000000000000167490a2aa6c8796736cbd364a4d18007ecfee403bde5dc13c611a214610e85af314ddddbf05ea129e027e0ae8d89b36 +0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c,000000000000000000000000000000000ea1f952d65dbb9a40209aa89e367d9d75e1b4c3a70a609efda5fbe7f5c5483163671da425545d3f1afb817c6d8c59a0000000000000000000000000000000000cd537dc11cc63dd15c8ff74d15961390eaee59b2d5697b18c1ea6d534d71551f5e195e8a0793140d821dde97dc77623 +00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a,0000000000000000000000000000000004c92b7cf9199f47008dd561e624c822a067c57fdea9d016f79e6c7956dda9df0e36b4e78715f3da1319af9f4f1fb160000000000000000000000000000000000d2851d68617567ad5308f69dc5dbbf37603c2ba48cb3759b70fc4301fdce3bdc9fca076e2ae09562396c1b8558ccdcc +0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054,0000000000000000000000000000000006ed98add25d64f7488ed270e0899ee3633c84b73de26557c552017e7cda4cba1228c15e87efb5a740284dddb8cc80de000000000000000000000000000000000b363e14b0285fbd24eaacfe80b992d8df1abfe83991cc55b0484076385374bc87d9c7860177f06143c600503ac54577 +00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,00000000000000000000000000000000164671460621354cd352d93ca7de51828b3e6db0a37d2894a0ac475a5facdbc3ca5909d3bd7553271dadaa68b7474e2c00000000000000000000000000000000188827c6e2f4e9796c71703ba53ba2ded71bd6e8280e047fb6ea440b8dcafa7c4252d26bee1780ac67790e0d603c8ca7 +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf,00000000000000000000000000000000023b2129ac67abc79966102ba223b982d40ca83e9b1ce33dff681c751b3f0c692f8bf19fa0394eae190767899829d1d10000000000000000000000000000000015449c6b5ee2c9f8b28e9732c9ebf6ffee5048263f7b5050a5ac9a76b034931a5c034f91d24b461636f5b116e37a26a5 +00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da,0000000000000000000000000000000004edac7b03b5861d178bb4aa34e795c776fd95e7c0980f19d111ef208ca4854f73a3ddc219bb6bca173dec67b0e863a00000000000000000000000000000000004dbff672368f86e048c3e33cbe90aba570484b4ca2221f7f6adaa1738c369f4c02c0a10118e84ea8e53cfbaa10fa48b +0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833,00000000000000000000000000000000169d637c52c31e4c62c9563a508869f7bb5adc7defedb5f4ba9f3eabe517fa8c0be2e44d656e50903dcab67a6a44984d00000000000000000000000000000000192b39d5cddac36940d896a738e25c25217768e1d0ca712968718b8fd9ad492bae63063b3cb168368c3df196306b6a1e +0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f,000000000000000000000000000000001608c3bfb131eae485545b7d19b8f42de18dcea6a0db3279eac2b7c008fbead54046bf13dd63835abe9c63110e12526c000000000000000000000000000000000abb41b2f17cfcc2292c5bf559b38af3b25db40121c6a5627997f65765eee1743c204f1161abe3f71ac1fe4de6aec1d7 +0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc,0000000000000000000000000000000016e3125ae97a2b1184e2c6dfe5d9459ac567c686e65674f3b0513df6de5e80d1efbff3c254e509eec3f951b0835b5829000000000000000000000000000000001889481258d3e898ed4e4a43e74c0eda5ba26c0b7525973ca86b896969240ac5928ba58bc86ec17a47f2469d023682dc +00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52,0000000000000000000000000000000017d8c0aa81ca6a1e4de8d0b8b3a13b1d6350f79ee8439da97a5d564d435f4d40bde99138b67284beffbb176daee92352000000000000000000000000000000000a04e0bee6b9681db56604a6dd5e41c072e84f8ee9cb4054410eb610472b96c09802a1d70e325c40c7ab7e248eb2e3e4 +000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1,00000000000000000000000000000000089ae9fc5cdba1a24ca87fe4f1207d1a36c494d842eed330069f988d3bc8554af1deee3a5c59b5e74729097acc1185fb00000000000000000000000000000000002fd95001da3011b48067d351ec8667c2b2390b23fa0948896725292311dbae71b51d6d5d57e173970bc992d11fdd11 +0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9,000000000000000000000000000000000548e7564e09c2bad9859dd63dd1045878c9b257015558b18cf5911d1763325e411c1fb8af52e8766fa7adae83eea12700000000000000000000000000000000111235351d136905fd19fa726eb6626085875c33c98067a01fde9688a5b2c289cb8e3f5d6a85d0829200a355c82f423e +0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b,00000000000000000000000000000000165504769c7ab0d28b39f38f3bd09cd47c63b74c57d39935d1c03e262f9da0e8b0b9264b0d8e2908423fe5c74288c208000000000000000000000000000000001680df1d577bbbb66ffa10258bca54b74cd90a7b3f3d50472e70e18ef54b7a4412e9eb93e39b9b312e3e8e00a52e4067 +000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6,00000000000000000000000000000000087a52e8eadd5461e202a640024fa17e201a9f0a2984be3fecfdeef86abed72d059e8879d0be8789f2a6db0d2cf55d3400000000000000000000000000000000196fe307db05207661a5a5f8f7fb24d8fea18ef91941ea7febbc18819f49f73aef9dd1bdf4fd605e031dc04f16fa92e3 +00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36,000000000000000000000000000000000301caf675cd5359bcc274b6141bb6ac53ab6a86a38ad4f8c3233cc9c1a77723eb0de4a2014e556185947dc1ef6624e3000000000000000000000000000000000136d286e623637f12c8b86cd9fad2bed8479ace5189e064a4e12e6e641447dfb0399757026126ad2d169c05011f5031 +000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc,0000000000000000000000000000000004ce73cde58c9af5d1f76e100849b0ba3d3cc6491e76b39cf4d7b681fed0686396440f6a721f73b31fb14b4c7624c176000000000000000000000000000000000e26b15c1051d7b049e82476a30545cfa4bf0a2075681d7028797c528712c7fba7a59145c9dd9ca9f5e9b1ac8a68b126 +0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792,00000000000000000000000000000000028a89c904f63eb8e68096bd2001458a4b9b32556c93fab5e52ab26ed73d62f0489d6bf1906a62c8148d50d30222a65f0000000000000000000000000000000007e54f21e2ac6d5287289ed9e2a15d457b5dac22ef36c19cb28a6cf9a0d11c981bf6549ddaf7ddc0a59b3d3a4698d975 +0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c,0000000000000000000000000000000008c39ee7c8d86a56ad1a9dbe005b4f0d44849d6fea6bbeb0732de725ad561befd49d465a134bd1a63a39eadbb6e0bce1000000000000000000000000000000000d5c892c92817fa24afb0a0fb319ad21e309edfb6300397a215e34eb3aadf91cb41b4ab1c5273bfea6eaf33982c75eba +00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa,000000000000000000000000000000000ba1650840e24c0f99ddd10a6c3341661e5c96b2e95cb6bda3340e7a0167c906e2f0ccbac6f0be2d7dbb3f9370a5ec960000000000000000000000000000000011638a3d9a81c0fe2ebb547808db758c7cfa8648b4835fb8c4931fd622da3a001fbce9a21d61f98f35b1e907913ffd25 +0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,0000000000000000000000000000000000eccc25cfd8c5a58b330a74b92af0c2b932772eacfe898ff3d391fad5dfba52a3940e8edfc9bef5c4de670207c8585100000000000000000000000000000000095ae48a94c92c332915b0c07511bb0d54c316ff3a0dd2509a18a21320b506bbefa76a459260efdf4c045404f02e114d +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd,0000000000000000000000000000000017a7f3b439a98885994a6832b6394b0ec9968f665b5810da58e3ece3d8e8694c482a15d3129732b43d4b7008660f19c000000000000000000000000000000000195299086d3b9448b26fe830522d520d132ed59744e677e6eb114ba7d7045019a0d0386cf817701ca3afad2a0487a689 +0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45,00000000000000000000000000000000063c123a3cdb92469e7e57a18eaf3e7cab1d85d64cbcb52499d2e611e6ba71c717b0ebaf4cc9208b18c925a5ec167b78000000000000000000000000000000000fa5e78ae10ed8a4dee9440bfc7637d903404749681f85bcb62444d921c4fd809a646ffe3bb7c70dc906d07c62381415 +00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b,00000000000000000000000000000000192b1497c71eb894a7509bbdaf308428e4d5899edb15f9e6e45a88340f55e1b76ee0901a830b66114deccda63a913a6b0000000000000000000000000000000017d58bd474a61ca0ceb23ec392dc08abe5697b8394fd60440cf787f15cddab36aa99c2ec2341bcc06dc1771b5f0fa139 +000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca,0000000000000000000000000000000015f72ad769cbaa2bbce0aecef9559b825ba4ec17ec5be2d9f0dbc7184383eb3e201de5163e71f1e71655acd5ee1fb30000000000000000000000000000000000194d27d9045b9760e66b578af24b282d9aeb28eb51206d2e18dc04bcb6df90553a846736afd92b23aa004f8de90bbf9f +0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc,00000000000000000000000000000000146f12001844bb0ec185e773175634f2e56bfa7190caa851ad16443b629b375ce3967b0c936d30dac2f126343722ce5e00000000000000000000000000000000080e8e90ed0d259ad803269711e511577769f7886b425f9b7857dc90ab36438cbd7435f6eecf2328f5fb6eb56f370163 +00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447,000000000000000000000000000000001344d2c2bc5ef45dc69597e948ed6021d84f7bf2c36119869a3f84288f3bdd6fc3a0de2b9e2564a930c2207c1ee36a0e000000000000000000000000000000000dc4d15ae09642ffa17d77510fb1ad4bf9e06084e9d352f4e234ea35f33458df4f23a209e29da42c41fb9a3cec3e8242 +000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828,00000000000000000000000000000000084f2ed8573d5d04e41909d5c8ed3feb88f572726fc86d17d466276342f01503f7c8552498f8a7e96c875c4928b808f2000000000000000000000000000000000b618ca81b6ee891690099459634e011b5f59fb5c96488b0205139a65c77f15af135b3528a5ca3b794e7b2991d2434d6 +0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a,0000000000000000000000000000000014733ee8425f42a30010366e4585cbbbdde6ed602a639bd299e63c113db3d797fa01075e24a042a060a043c9e1fa79f40000000000000000000000000000000013b44e1932681d238c52e959e1e3daa7a2e1ac67252ebea0cae90e8249f85b61812b9e09203d38d96f4916837b3693c8 +000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb,000000000000000000000000000000000ba15476a1346fbe9be2720721b592ce7c111b95f0b8738495e6c28487e12fcad60006314dfe68789e60f4df2db14eec000000000000000000000000000000000b44b9a9f695c94ad206717daa3128b672924d0db83ae0d47b62b3c79428f6fe151a65a39ae411e18b128d6796b67bbc +00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425,00000000000000000000000000000000070dfc697f7068180a7a792604d7b8453dbd393c993be9829a263ad5864c3575d3fb235692ab12a4dfa4221bc6e0c6d600000000000000000000000000000000123a9d9b83e2ca7c95de9602116b1e14d48175073e1fe766458e3fd4b6676f120adfcc5c497febe2f7ff68b1e3508e3c +000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96,000000000000000000000000000000000dcad6e29cda2332dff09377460c7a2b9d908ee53ab13f648cd892bf68a44ffcc8cd5d501f8b068f506b506d01d3f4430000000000000000000000000000000003aa625a60932474ca3f914a3e0aa8384533723f824b12c686a64863a734d96ba13670c8b355b52b0c01b49fbffb6149 +000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc,00000000000000000000000000000000023909bac6048bff0373d27a06dbbb8aba8ddbada93f4fea65c983598307f3c3a8cbe163462484ebb88165c6b6da41590000000000000000000000000000000002162d8a498670158c23daebb724168b5379d9124b064de871674a3ecd15e6b546366287563928a1e279fb1eb2ea0ba4 +000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca,000000000000000000000000000000000f79050036c4bb6c6b8e91abb300dc49a75b32faaaeb258661c905b4d936f4096d59de89b911de294603a0e3443fada5000000000000000000000000000000000985105497cd87d5ae2698479da55f6be9bc2cf5a2093b651d7305b67e36343debaf19c266ccb55c23f3de55bdae23a6 +000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a,0000000000000000000000000000000017901e77745a98c09d6740597c40f27df841cca6dd95653a1da6d8eb1c57d5ebffa6a7b894369b6b419c61462697080b0000000000000000000000000000000001732540a1bfa4a1a851106209ce4807d7c0a33816d3742ad5e2729229f3403940e03b93121b79bb94c24f7e60539ece +000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4,000000000000000000000000000000000f990d646495fff77d090f4a69b8af0e1762982b53ef8ae9bb955ad8b894942b85c7726587c9fd956ad58eb9e3ca25630000000000000000000000000000000007b7315e1f93cfba8076cf539aae01fd3bbe1cf92daa168a6fd6a2e7c969d35c51fe7eba04f1e0dd3e2020635f2c4f09 +0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,000000000000000000000000000000000de9d7e58919ba6386f32af53ccf36cb0b834855ac8dcc19af3c3c9522c3db2985e51ba36067b61181cb0fe8b47d853a0000000000000000000000000000000010ff0800ed1b4067f8c920462f7abd7361dac2371716f7b8648d64a71cc7d53265db6d80b26b9efddd572a2273ab1b17 +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7,0000000000000000000000000000000011a11cc098144fe9bd42ec8845be76b6cae4b3001a79f4bbbf9f20e8ac8bca5b37ef8006c958318c3894aac7d6bf77e8000000000000000000000000000000000d5c1e6b78c40a356a35bfabfd66a81924d2eae6d428b5caacf8f3992ab980640e857e756e649ca83f5aa4bda7cd00b7 +00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602,0000000000000000000000000000000002e72f4568780fb41858edc3f5796f7936a30ee9ddc7b5034d9341614d301c7906238bfde3bcb77f063fe652a43b88270000000000000000000000000000000006f971f4a8ac554df7ae7ecdfab724410f1948af994d760c5f5977961f891ba4f4e76b27c3f0e5a1471ad017e91a9af7 +000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013,0000000000000000000000000000000014b9ef8878af80f824748389d608bc9d0ffbca96230ed590d8e351586607a614f2658e348ac172f3184c1e5fde50f550000000000000000000000000000000000630f0556407c140d0a05b10ea65de48e4866e040455ebcd54fb6ed6996a6a3ac7a94a6818ba424936fa505c2c364124 +0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8,000000000000000000000000000000000e66c8be115a941ef7adf4490faea39149a3d812c29d4afb36febe3f813c7390a715f838dda90cd73556f89abf3949120000000000000000000000000000000015d85c185cb86af3ca1c526ffa6e9459a9c699c5a4d57278f33b14691e980e0f86b9239e626fc4064890cb610f10e496 +0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b,0000000000000000000000000000000009db6ac72cdcf1f69c6593bc183aaa2b3980ff78a4417e23243f81243987ec6f2636641c9e9c738c7af2a1e9f94149d0000000000000000000000000000000000ca7537c04c06607e42403e84e7d9e55b2a06c730ec342f16d03689bb684918e85f637e7a6279d95cb7774f106139d0f +0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244,000000000000000000000000000000000620b092ea8cb718ae9669da4ff2faf639fb5e657b7759fdf292e6d841b51545afbabf95a98601847f64fc7367f872ff000000000000000000000000000000000a14bfc0e328310d62f116652b1de3a18282b122e0e3965619a099466986a546b73696274e12bd395224018a48b3d80d +00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83,000000000000000000000000000000000a633928be3f3bb4c94cf4d8d7a8169779f8bd4bad31ede895937e8e8b0ddea956d255776141541ef5791aa3a0bc6d360000000000000000000000000000000003dc3b703753a7b8ccf7676b04cac8021aa311233a99e8d5290655d2f84555dedff62f9f81322307b538c3f3458f6313 +0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852,0000000000000000000000000000000014911a8b41cb65cb7ccb940a472cfa58861f1a506a4f719888eb35d48ed9774ea0a0dc3ba38760253bedb4a1acd0963a00000000000000000000000000000000031388c90440f22cc63a1e9450256e5cfcf2f7448641ac66b43d542c4b77e9c590b957efdb1c6d75846b3faccf033276 +0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c,000000000000000000000000000000001968070c01f0aeeb42ab71730f5b78ec122c10ca9dac1764ff5e916fc85a5eb5ed406c03263c57858fb03b15ac0035550000000000000000000000000000000012ecfee330e1cc8006c73e9d41ac1947b67f8704d12faf8c0c05c2519dca68be7bdf88a58eb4825b35a1d270554d6ce9 +000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0,000000000000000000000000000000001469e7ab4c3740701927da2b0e34508a73387aea671857b042dabbc65cb849f8c8ed0b7f8c8e37f80aeee98ba953f4e4000000000000000000000000000000000674212f9f8e1419608ccf1a0447533fbd6fda87a35cb9fb39c8a7daf5d12f450c12bfac9e9f872b2643b1f8f201439a +000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45,0000000000000000000000000000000009c756aec59a68832728b1133a69f0794f6a082e2f0f161e488078bec7420a0da19e812def625df9b12aa36d94d8a38600000000000000000000000000000000014aa28b18771ca07b7627446eb60d53bf4837541da661a0e5cadcfeaf58f5a650a39ac304f48e45d9b714cead9ba5d2 +000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f,00000000000000000000000000000000153548fb1d7f1721c7fbdfeb167e1c060a90aab8f7b6572f4a2707de91b03a7b5e68f792a18d940167ae83d1380d6653000000000000000000000000000000000113bb747eab3987cd195e9eb755735698993332d517890f4e3285bf7274f8579ffcf84908a4758f0bb932021f2c76d6 +000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d,0000000000000000000000000000000014ca98181489c96227f8052a77730ab446615cb7b2b00a600cdd7defe8b3ee1cd53a6d98892ffccda5fd4916e0cf5886000000000000000000000000000000001567c3207cbd42c0445ea96b464dbd9099b85f5df1932d152436c936623d92fdeb009e69919368134501fa9363a0b1c4 +0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a,00000000000000000000000000000000139d093364c313d400603dba5a79479d566245a397f88aae748e110e09e7ab6dd271b8c37a90b86f6b48490ec1d0d8f3000000000000000000000000000000001099d4cb400f2d786dd2dd5d162580d2113c8405f51e8a619a6894d86a7f7ceb237289808acffa274069c24ee27c860c +00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997,000000000000000000000000000000001247d4d3b1625ffccd350a9fc9759295637e91d9167d9bc72bbc1b60b1abb71dc29595b49ee1edc778f5219416bcd0cf000000000000000000000000000000000dfc69cdd0e4e126208b76a4e5fb8d032ae93031dde7da9bb1358507d4480881576c5d7cb7f0b3fa3032c0151650f2da +0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,000000000000000000000000000000000150849c60273de83f9ce2016238c273359ecf486adeacc4450e1d1a6cb79fc0d0fb38974489375d5763da8a5f4e743e00000000000000000000000000000000157ec6c2dd68dc5fb3cef4e935fedb74e1f0e856f1d75890bf995a08ed6b53b52e2e0d412ae190365b139101e7fe040f +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d,00000000000000000000000000000000024b59fbec5240fbdf3fb4e565bbec20f26edbc2a1bf7ecaaeb5278ed9fe13d1e360fa298e2d3f9b2880b00aff827f620000000000000000000000000000000013ca56975d9fd667bab347ed67fb96a433d57836ca4069976e12459152e1369154bd095a15980880e21fd02b1d7e3156 +000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616,000000000000000000000000000000000aaff66eca5ddce81533afa27e2db1c25a2c6f0dc1dd7c2236d4c89cb9d2539e109cd1362dbfee86397156c3703d44e60000000000000000000000000000000013598d8ef4470998aec290e941576f5e94d696f7f0be40e3131b516a1679c5b0eba74dc9ae00ecb8f115e4613a50f3bb +000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af,00000000000000000000000000000000163cf5475fae000c38e59754cd29f1290ab2d6550552e9186555d1ce2960b7dca5834e0347699d2869b8c9bc42f6f717000000000000000000000000000000000b21bd3bfe50e0536135a910359527f80c130a08029c24f990c82f02727def21973a20a2021c95aaa3a7c8a980b44f33 +0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145,000000000000000000000000000000000bc3667c38602e7e1c018cc62933c013a9e78c375b50ba06f0c3d34fead5ec8a9658702a0856625a712520ac99afde230000000000000000000000000000000015c6b5487a52b41ae1a4634c8675f7b847aa5d319ee9eec0c92fc06d8e92e1cacc90ee394f8c90ce3e2c00307f53dec6 +0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a,000000000000000000000000000000001358e1724cb3ec4028a63e4252eff164defaa41b21042037ea9a1e06bc1a0a1e838afc1965ee665de3da0163d22682420000000000000000000000000000000019828e11831e3e4216d843ed3446345edb357b2082b7947fe71932dfd894543928ddddd8649d32b4f1349f63f60bf095 +00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0,00000000000000000000000000000000130e09c096ce8ba86ae71a817426d929c7f9f8bfe00e76668b0041e935d1531d6f58e5eb743df3cf86fe88bdfda8c8a300000000000000000000000000000000187b25d8216fa3851bb6fbace998bf3f23dea80dd6e1cd94bb6a72d335702694804c6ef3d350519c5e781f941bb72f92 +000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258,0000000000000000000000000000000011e61e5158d9a7c59a5007732a76e27d14602e15159e8f62bd13be8b44c96736af5a77495c3da55c8244af6e60eb4f2c0000000000000000000000000000000008deda8447009898c89c6766e8add105892992585724d520c38d0d4f8c833f88d8c331e11b291b6def6847bfa9629d2b +0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e,000000000000000000000000000000001182f2e45f06a729f82442ddb372f2eb8dbfccf12edd8df0764072c9f14cbe001893d932e89b948a643981ea8aa4fa41000000000000000000000000000000000910335dbdbef74b844a6f3b879d14c23c711ff2362213636ddab7eb1a44cd4b687659f8dd521c134b56bc4eed0ec5bc +000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83,0000000000000000000000000000000019576d68ce66218d4c9e2e6fa9985451eea46ce60b11a74cf5ea9dbb9d0e8741d11436dfd77b0a8b490f4882cc5b416b00000000000000000000000000000000088ba5153e91738f7524034a2609848652a7e416fc68537ab2c16b6699f69695c62e5724dfda2f3b4f90277f5005bfa7 +000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07,0000000000000000000000000000000005720fd4bff4da704edb7e317e3d41f1d1f45e3c1f22c1b98ee0b6875af414f6f58793e8ffd5c89bcec2af711973ca1600000000000000000000000000000000051441e34eed472766186a44b2028d86eebadd597cb7e3fa4f935d30aa043f11fb18670b31f0a3b8aa23bc8f05361064 +00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6,00000000000000000000000000000000141a0eb238edd1cdb670737d94f658fef728691620f9c6d98e34ed8bd166b38ae6912b5bd90ea21b091766ad27d689480000000000000000000000000000000002d0e7d2584586ab2f08cbd419df3defab53a287ca467b6b081e474711a23608831c1507bac4f328750731b99a06c6da +000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659,000000000000000000000000000000001227b7021e9d3dc8bcbf5b346fc503f7f8576965769c5e22bb70056eef03c84b8c80290ae9ce20345770290c55549bce00000000000000000000000000000000188ddbbfb4ad2d34a8d3dc0ec92b70b63caa73ad7dea0cc9740bac2309b4bb11107912bd086379746e9a9bcd26d4db58 +0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d,00000000000000000000000000000000187cb196679b6baf78a7908c37d7f31a9fcefa90b7cf165d0748a358e6dd86fc5c2d91ff1c4429a563b5962b821cbb01000000000000000000000000000000000d94711dc6efed34385579532f59964ab18b9debeac96044f3eec14cb36965f380d21d39c246e972aa2d5891ce417e9f +000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712,000000000000000000000000000000001405c27eb28f58e7f66988a300df376f3536723e2ba5934d843ae629669485015c90a8da60ef5c00c63c0b08a00203a70000000000000000000000000000000000a62dc83ce27987849070a6022ab6a06186e2527f39ae94d5a23d2e4d234a465d50e03b0d7d175ed7f53ced0c3bbc8f +000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72,00000000000000000000000000000000142fa228919f71f75df073927d03d9204b36a5177b4ab7bc995b59ff312034f7ff916635e27abbe775379aafc24a35c30000000000000000000000000000000014429fb137cf912995ca785902877e6675105b252a64282412798f883063824fc31cd79b356ea4e4822363b948ec27d1 +0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,000000000000000000000000000000000cf0aa7969ec44cc21bc8cca97fc8a581aecb63054c4fa3b7b69d28e0e2e901fa51c42a629145d9126e63aefe7978c8b00000000000000000000000000000000199d565f26b9c6496a4115eefc75f1066480f498a50314b396685a3ade8e50ab03c7f56316be2bcc02dff8b11ad5e4d9 +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b,0000000000000000000000000000000000bf4256ce2a2a976e35a9eb266d11dc53d043f6fcafb47eee06e120457ea56decab47ef22b251c6cce17df9a7d91e3300000000000000000000000000000000152c438e11fe1d661eea7c631e04e02eb9204ebe52cbceca1ab6a9b4c889a1ebdda01d7505df29fe2204ef5787749a63 +00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035,0000000000000000000000000000000007754a49dcdde1354412d3fe2e108675fde8a1df069c86be54c4bec46338a0952aeed50842c2486ac652202c26a1861c00000000000000000000000000000000023fe3f5e6786e339002e14ac5c9fdaac3c012526b33da9ed314cdb145f9279a71e306f5d51243a0f0dcdf59bc5d55ed +00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff,00000000000000000000000000000000141464b4326b0353aa99674bbd98853b926aa580c1e03673297bcbe9094eb1d795331d16d883e0583ed0551f064d7a0f0000000000000000000000000000000002dbbfb86c4d313bdbc8ebd266c190e38645016aca22261665dc850b0d7db8b240aacebec8af097724e5291ff43e6f90 +000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b,00000000000000000000000000000000118ab56a65ca63becc8aea3f11b370c705f32418d51fb1b1ab64bdb8f0125de2a760cf21e7ffd4d99e9d7cde1368791c00000000000000000000000000000000047674c8f3627527dbb41f51fa52c0fe3a921d07466cb2b5484e4c8094556cae247347a0a1a98499510d1ce5067480ac +0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d,0000000000000000000000000000000000d76cf9fa103355e6f5cd4baa3420e694f252249aa6171569b70cb43c906eae9b60bb79b41af8dc714bd917638bf538000000000000000000000000000000000b9272015e64f292d7b76867714a55d7223bb026f354b20109e81122fa13fd0426bb3aec705b477e7b9560c5a99c9d60 +00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33,0000000000000000000000000000000007c87e6d92bd41b7fa6a6ca890bf0b58304875a79af7959d9226a5be2f4ac2b4531fd09712eb6299c23d7c1c5ba3997f00000000000000000000000000000000164fb86eafac39e06c2403e315bff96faecc57474bfc964736b1850696ecfedbaa0795e537b8f541159d479ac5b52560 +000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f,000000000000000000000000000000000fb01ce0567f09dc44fd473009d2467c8c16da5ea7b39a1f1dba7b3656cadd6bdf2bf68f96a43252d92e428c1d2785490000000000000000000000000000000008b4fa645f3c56459a17c912c82ca36165e730807282cabeadd9c6c4a12c8a592cbac265021ef62c60eb60df3ff61061 +0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c,000000000000000000000000000000000b9c328c8a18113e1d1f783432c857015eaefa724fa2c441d5ef76b158ee6fe0cd1775b0c6db7600754cbf25fea528fe0000000000000000000000000000000019d30c3557af1da2ca169e70625732d9a4396b51f3b4988a9aba1be62538fd51c167c83e921f4876224d361afc90eaf8 +0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1,00000000000000000000000000000000041054430741e889d4cd8e7efa41547eb624bd775fd9fb64cf9e3dc2c6df27c95ffb8d76933ac4fa1952a5820ff88512000000000000000000000000000000000e8a28f5c622482b296a43ddb607e0f25635664fa849f3d6840ed7118892106a787bc07806dfd83935754d2057f2eff8 +000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb,000000000000000000000000000000000da2286b44e7e90e19d51c3c41bef375c54688b07afffbd7c528589dbf7f012e1fd248b9067a3faae9f1c6b626a5c90b000000000000000000000000000000000bfa0a482b0fc445f7b99c52a48116383bb70d5f2ebec5b7715796fbd0da744d0467584bfc1c8a42ace833d57c167a24 +0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf,00000000000000000000000000000000148a7e9b0b4fde322f1177ced0bba34abec4a3e500afb86f9ae0a71bd75004e9c631d4cb26798bf963f7aa367f74630c00000000000000000000000000000000097f4c0893f9beadd66e4cfc6976dd277e527b1e31443e07554dacca52390066a4b37a7f0824cbaf51d3a555d696881b +000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5,0000000000000000000000000000000003e1d921b5e0280f7370d55967e716bdacb7521547e22190e89862dbfcce02dfe7fa7927a70e7bc33448b9321de3d8ae000000000000000000000000000000001163f78de4af8494666c64d47d68a0feb0905c42ddfa024398401202d1fe0d6672bd1bd4222a8d106668ba4617683485 +000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e,000000000000000000000000000000000a94a186b96acbee87f9c1745dc301229ec750c6967262e629924227c6680b1d404e4b23d998611ad0e415610dc8edd900000000000000000000000000000000014da21c0f6930a79c8afbe42f73e048236b6d9f9ef8f270733fa1cb1012377eab37ddf2b9c742fea44020caeb95beb9 +0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5,0000000000000000000000000000000015cc6c31dfa9482c6341f816786562481bc3a4db4a4a00807a9c7c676eb32b9dc7e002ed4971f26c1dddea00d78721b5000000000000000000000000000000001303660b6bcac611b2d41a4f7ac9ecf3f0b4292f83f2fdeba300a060131322ee3c2da3ca3539114114ec8a76dee6a5ac +0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0,0000000000000000000000000000000009f1903e9a7d275487a503b9c968cd86823fe6667c09593b60ac2c88f306e20ccde32eebb5942a03fabde9195c5c500200000000000000000000000000000000179b41dbc2ede95ba7dad512329aeca9ca3bfd4da4b9620070d76d8fe8b49ad7fa92358070dd5098a2eaff490641edbb +000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,000000000000000000000000000000000f9736431073987708757d61927a45cfec471c8366776e140f62d805afd948fd132c4a5f4049de3a1474d0cb52c3c25e000000000000000000000000000000001515b057952696810a90dce1ee8464fd6370e8af5434a99333eacd1fb2884f6e8c568f887030a4957ff6d24ca02f4657 +000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4,00000000000000000000000000000000195460b2d59df32f9f41eaef1139d45f0cb8f35a7982c38d356a8a8412f25e600580026d2d908b0493edba5dbea85f5c0000000000000000000000000000000004b339d62b3cd4cc966c6b4038adb302f997a16d8a6dfebd153295de08e57d1513cf0f16d82dc450e4d6f52621a42fb4 +00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125,0000000000000000000000000000000012cf2bcb79668067b7a265672ca614405868cf189ee9789b9e1e3186d231176dab5fea86cc5865392db8c75fc5d124c900000000000000000000000000000000121bf40feea00e151b718157b8c024f126762d84cff20aac08e7f2a027ab88b33e134a410c2af279a39618f7d21482a0 +000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b,0000000000000000000000000000000013a530f94e7600820dbd8aabefde2acb8b3c74e833457102fbd297317eb532c0622636ef9e9376fac1637dc745fe895000000000000000000000000000000000139eb14d3b69be977413c832bfda234348186d46fe177154e34fe204f62ac79f4b0f59bbef39b0676d81ea42a0946fb3 +000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,000000000000000000000000000000000bf700422a382546a74376b0292f3a49ceff5597f0d2b726b1ff099bcda7ba92238a21db12eff5c314a29dd2387bec850000000000000000000000000000000005e22e3c772f3634b1ccf4e311241977eb20e7269540ef22d379de26ab80c58461dfa3b67848e0d584fb11de1917949a diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_multiexp.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_multiexp.csv new file mode 100644 index 00000000000..4201989eefc --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g1_multiexp.csv @@ -0,0 +1,101 @@ +input,result +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,000000000000000000000000000000000b370fc4ca67fb0c3c270b1b4c4816ef953cd9f7cf6ad20e88099c40aace9c4bb3f4cd215e5796f65080c69c9f4d2a0f0000000000000000000000000000000007203220935ddc0190e2d7a99ec3f9231da550768373f9a5933dffd366f48146f8ea5fe5dee6539d925288083bb5a8f1 +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b10000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e90000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a600000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b2087920000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,0000000000000000000000000000000017479d99909c144a5a5fdfd71721f4a2ee90b2b9654e069a38b460945b9291fc74e6922a7dbab9bb12b4bff9e2d0175b0000000000000000000000000000000015cfff11afe08d76944c9f810017ecf78b8ed54096078195d65a5418f660cf9b2024646a8532e349eac5d32d59c829db +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa40000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,0000000000000000000000000000000001c143e5d7bba56a959b94955f8eaab82a92a2e2b355baac7da0b57281645c689486059fb590ef2576a7a03a7c57e85d00000000000000000000000000000000182b1e16004c7e6f55923dd0b1dfa7346d1243996070db78f45c4c0a2cef95e93c6373903b5e0dc63f171c8164c2fb5a +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac700000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c0130000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f8520000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad9970000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,000000000000000000000000000000000b2997ce4cb01abbb0ae6d28099d20e1f08c33351a6f0dce417a279789d6c581d4bc5a4a261e37e6df31a6928040d1f60000000000000000000000000000000003068e73dbbab6fddfd3c1e4fbf58bab58f15e1630c8c236faf3048be840abe316084aad7dd4ca6ee9d353ea8db536d6 +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d1450000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf542580000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de0700000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,000000000000000000000000000000000ed96265e66875001ebbe888571ded16799d0bf5a6bad0abaca75b94bebf3023487a29fbe26a68f1cc90485df379845d0000000000000000000000000000000001be40cb29d8b722f91515f7e18372f7a0f77bc3ef2852c59e7533aeb67cc4cc4aab0b8e87f9a4982806124462ae94ec +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be50000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,0000000000000000000000000000000008644b6d6adf9d5b6b50d4759363901ea94218881fac2006ea391c41fed2a94645eeb3359df803d740710f0f7842b985000000000000000000000000000000001168ff1897eb699e475b8ca2930ae9ccff139d534c7cc606c7bafec0ed23a6e55c6ddb1efbb1b5f75044d0a7e122d204 +000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000107e1fea76b5f2be2d12e11082fe690f01bfe6cefe22ce67de968e410ef51a6192b5b28a89f222db7e5b5fd5b8bc7c4000000000000000000000000000000000014028a700cbde8bce295c564dfbd73294f9bb65db3db9d38312cdc31410ceaf7151ff5d9420de2a5bc8f0d609893c0612adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c6000000000000000000000000000000000a57058bc228914bbb3e3e8f6a73842533432e0cf226cc02990b9b99a74b0acbad498036d8fb72a163590c75b6041d060000000000000000000000000000000016d275fe8c7e37058f287e1646c28ad1b4a675c0eef9671cf95dfa25617e2f2d515b2fbc04cfdffd5d487b255dfca245d1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000016b6ecca57c78d6595e6b55b9360bd946b2f0061b98d931d82b03ed747998285e093c978015f0b775867ad0d8b4a1f82000000000000000000000000000000000b584f6f00bbcb2432b6cfbd4f6c88e228658887b5278e461ede804fc8a65dc6c997de30efc65b4f43e3d96717b938644c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d3000000000000000000000000000000000d1eac060ddc0a327396051c8c4dcccb77d11da05678d0720dec020d8aa29cb8ac959184417267cd7386feb1c81146a70000000000000000000000000000000003f8b5667ee4707958ecb93a1772849d5d8a4d42a2367ca058b160dbafa8ac0b98d5ea216fd18130237a1f17ce905feb3c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd00000000000000000000000000000000180152247144900b015c3db2d8b26d45a57930a5ca988c1fbf74b63b48afa149347a343f3fc6b1f31ddd6de079391efa000000000000000000000000000000000b6f3ae16d2a580ae06634455302db85fa94d71def14c84cbacc5ef98335d6d87faacff7a9bc14dea342a6a80d9bdfd661301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000001918c4f95a0d0931ac3f254cd61c10cadce5cb9e1ef352edc8e5944c8aa8ecd90c403ed764ef42f646c7ec5e3126a140000000000000000000000000000000000ed644cd065411c63c7d054a57344e7a909e1d0a6b414bccbd356f15d16fc1b42c681cb6b36b143e91b31866387fa94395cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c00000000000000000000000000000000070dfa1dda5ba02e94b29a63f8eb571ed7e8b0d037a0203af9a8350dacec092be1bfe33f4134b2afac77b9a36f95208000000000000000000000000000000000019e11a80ce3f9b3321cc6fd1ea2b314bf0c71d0dde80cd5b4de5f0d974597f57036613829dc777a6f6ecd6f9bef2f85fb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e000000000000000000000000000000000584b7ea99ce0398473167289d34314c60ba913338b0bb690cfbb013496d24854863237a4d716437dc6ae33326240bd800000000000000000000000000000000065964a064e4da56471c9aed383e6eb38b58b9110a2cbf991d6dee869d2f1307cf7273d203d941ead90ed67c923dfcd5bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b860000000000000000000000000000000007d6061bdf40745ef7573917e0e19f240b6e917f7cd4c47e01969b9afdc6af4e3c93e0f1dc2d15790bc2e6f182c01f680000000000000000000000000000000014625d3f2825121a907b570e9aeececcf81137f40ca6d0c00d709ba9931e403c0c2ed262a8f4c2b24305dcd3185b81b0e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d2500000000000000000000000000000000087b5d6595554184fee36be472a0ddb9ac7f9beb20817647fa9978b2e0c3549ece4f061b58054e9191ff3f120c12077b00000000000000000000000000000000168d8d995c1fd032ca7b0aef2ad5c37ef7c7cef8b61ab8fcb5ea2d449455bc75b1b85631fd2ff8f5ca4e5880f36905ded4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b048000000000000000000000000000000000f44cda026dc5e30eb06f12307bd582b271ee695fa68fbff48674c0499dcc875d617471830958e31bcd2c883e97a9e590000000000000000000000000000000002977682ca8ca450df2ac3c3880b1235e0ad8436a36364d319903fe2ca2664e05a70840aaf2d62531cd8c4ba4bfae9124c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d300000000000000000000000000000000107dd39f779801f608cceb4784134894d2d9aee37cf328bb764d8afcb6d1e0f1387b36bf5b7b335099849278eac44e8200000000000000000000000000000000045c985714b519061a9c8d8c9665b582abdc4116a48a70e0d3c4a7709568aaf011aa8ecb893ca483878404b3f8b22e41a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae,0000000000000000000000000000000005605f40a1116742ed53aaf7092715355ba8e41d5d49909f79ec9580955c4a9e696fa30df37e4044de4d23fa1c4121530000000000000000000000000000000015acbfdf914883d16918f980aedc2dfddc428ef8e9897776443ec3cb091f6cbeea31634ef5ed061792710d5d2c87b8b1 +000000000000000000000000000000001696e814de192623543f822a22344258c8ef9b92c178c4bf2404b38f39151828bf518a868cde3db5cffb745f1bf0023900000000000000000000000000000000125bd1df30f4799271a9db03842f98ccb5a5c3acbe813b8324fefd06e3c8ec4e2c0be8c2d24328a2d1169bf6ea0ce46fb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff00000000000000000000000000000000138dc5c034135105c8899eeb61ab54a84cf02919e5650f34518f941aea5bbb6f9df3ee6bb2056d0b9e060158140f990a000000000000000000000000000000000eec8442c8656ebc4696ee13273b12f3e362862acc3b8ec6f2b53f58f74ea23b33c79fbbd2058ad205f4932db2e87ae9064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb334610000000000000000000000000000000008f7f45310b5638221cfc9dece18010034c00b3cf170535008d60dc7ed6ff90bfe87e03d94e9a38201699f0742d8973500000000000000000000000000000000185b62a19864e21e1bef19fbbc21863c75f558bcbfa1b948bf939876188f4fbff5d5f4f756e0ec5348e194bb4f70706ca3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca0000000000000000000000000000000019269dcdf3772ae4969b68a0b4f87c5097aa8bcc9e6155638c3de94fc22b4965386be28e059bcea69f993cc388ea9a79000000000000000000000000000000000b95f44ad9f14cb5e3b9a338d0e4345153c4ad0d42aa00a4c12df117b89d9cf8bb556041d49f94b8f63108f03c56a449bd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000011c86d420b6d8820af8a3ef511d79aed7c82ee08df993a5ba479b29ef2f968919444a7c48a24ec33522e1206bb9ab784000000000000000000000000000000000f4a47f3f14a25108c2c9262466d14e3a8d1f21bd2d3d6f28f03f35bf23a4b5b494a7cafe6ed5f39195e07b1692bb6da562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000091d9fb6493f4441c6e57a5a58210a6b78e86f1a9d204094ba6fecf2c146522cf842219c900d7cb95366cf7e411ff4a00000000000000000000000000000000015254260fb67e88d0067ba7006a49814c74a5369837dc5279c0fd19c8826813c922793c96e0f708092158ac297a368ddaf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f00000000000000000000000000000000027910712cefec94f0fd4de6aa70ccc408e64d5de6b473086009c525fb6d058ea03bc99f7ab49cdbad3a42bc8ec0999d000000000000000000000000000000000c0b0bedbad83ebf6af4f5757035b8292fadae4bfbef9f3bfcadd21dd796d7e3ecdf9685ca6d4d649b2f0702a3280d40e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e88000000000000000000000000000000000b0ae8987464ea0b77201d468db7256b135a5cebea92dddb3aff10e451568e714f1c418b6d53903b89bc71109180b8c20000000000000000000000000000000003050becb4625f8e3ab2cf13dd1eb8f7eefc7e14c16934b87661adbf0139631108d241bcb1fb24c5b989f6d424cac883396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000dba43568347a96f26f2633d9fc0fb4610428a8d4992c2734b20928bf974bf642a5122995884cf11b76126ba66522c8c000000000000000000000000000000000b9bb25b0db32149736b671ceed44df71f36a33c15ed821f591098ecd873355cfb8a39fc7c7378a19d84a5b232227ab92087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000018d46d1a9ec91cc7983b29ac83fe9101c0ca36276d40743d2a6010d574fe1c16ebd9d7f0c83cad5ec2b2f652d3e6cfa500000000000000000000000000000000185f6367fcfa70e7a005c1739c0d0a19b5ec8de766037ec92840e66e2e9db18ba2356676899763183222f9957f48f300f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000016677511c781b2b97456c3059c19b3e12a865cc21ad71cf06979bee1a3128682a4a86f3e07cdbc9ff7b5aa7a9899653600000000000000000000000000000000006307c89ac36a88c6921c020d32530fb69338afbb33929e231fa704f0454d642c47a3b8d320b4266283a8571944d0558624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b600000000000000000000000000000000015a9b215eaed682e4704cd3b1265962ae0e24555a16612ac762040e1fb9b412eacec5130a6f6a31eb670806be7ed775f000000000000000000000000000000000f60035910c438c177a27e6141d0ddae706d3e852d61e37cf8bb2f03550feeefa7213545e3af5ea614c91b51bc2fb378b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb3832000000000000000000000000000000000077b7a4c4644b21ac3ef56db1163f7b2e07a817cfd9d4c6830a97d0ae0b620e0b235376d590162c378899ba12eadb5900000000000000000000000000000000022beafe4b4ab44434c9dabae45a395b5b8da15da2fc2e723c1b30b5efc95e880846844f27eb47dfae8657fa27ab64ef88ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb000000000000000000000000000000000324928100db98f5a1af039a8e1b63099214982f1729ba633b51166da97e861426bb91283b386ed4b465d791e63928ce00000000000000000000000000000000178823756c0facbd4b1cab22f346ea7d1dce0ab687263265350c9939d52abcb5a5000b3395f8268a38027410675e8baf951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f440000000000000000000000000000000008828eea92c3245eea4d60ee385734d3237e4e346e52c5de8b24c82325819be6984da4f0c1ecfc6ded5d0539a6f1f1490000000000000000000000000000000017169bab8970f47a303d2487e3af707eddaf7c4453e9d2d6bbaf953e74947b5fd40663173edd55c0d6aad7884f69a0967056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000787474664b2803e78489de6c5d5f1938e784e552bca4c32196cfe121380aad591c9fe4d9230dbe976b3ed3b3044b8630000000000000000000000000000000000c026547c94cea37793fee439c359cbeb2b985a33559ab25d1b55773c24faaf4fe511fbf7df085bf5c1715c79469cc28aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a,000000000000000000000000000000000a7153c6173dc27b6a241c046a2a9bc25b85c4621667d0a10550cf7a090a8fb914725201876a5bd2af09d4fefdede3890000000000000000000000000000000007aeec94a64ac629673f3be3cf660f471e3e928de0884300ca8271d42c92b965c86bfe76df61d0645e955f40cbe3751e +0000000000000000000000000000000015fec2d82f5286d2067b07d83cd1c131d3fe18628101c3e45caab07f3c775c97e1533836830959cd7e434fc3fc392203000000000000000000000000000000001050e1396a5053c902441cb33003d9c54e6b631a80e3c132dfd37805bfe87cc2ddc495200268fba0376c5fa071fad230a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c4000000000000000000000000000000000fbcd07180f265688329d72ca68cde644a580cc9d698e40f69380065110ff5a61149e4aa9f67056e0e1603bfb9b5b3ce0000000000000000000000000000000006f363a9addd63a59035cad90cd52213665069f540b6c6cb41cfff5711376885e3242b596d051a59f681941bafeca53eb54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000001164abfa75cb4d711ad811c4df430ecbd6329968ab003fa680d235ab34a9565e5c08add76cf412f132b54812671da7a900000000000000000000000000000000141c9858dd17dbb027dde22dd65f6a7cd38a1999eb7977cde87ad762425e364e1395851b1cdb41094551e530d891b0d15849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b95d221c628a77bb75ee5942c9df4b700268c90c4e6330ab5533d13d59826c81aeef7621ef6145f48bef9607d280ad2000000000000000000000000000000000b2ae1b6f916d77c31e4421f8d0241201bdab5339f95eae0e9491b4da5e226f8eb3f754d40be3b446ad6d18f28158b08aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000128989e92641f3c3a914c13e986aea1bad2c87a8c28cf156bbc68bcbb134b25cd672832f2a988f60d2ecaaa1b83159e50000000000000000000000000000000000106dc95373dfcc85d9de6b5b609554b67e8683f90ea13156c8318aa8de0a2355a721b3bd77a6329264ae671c05af4a52c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b2020000000000000000000000000000000009fd9fc9ecc0d1521696bfe7624360d11111523a4ee0e30432a468dbaf1c101691fa527aac5ab531be822ae914b0afad0000000000000000000000000000000016b317ad68ec471b0ad67be2c489c9f5bb0d8bb6b5ef909ea975cb17f5964564d5f1a61d32d60c457923e4680a218b9bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd9000000000000000000000000000000000b982f33980dea4d89b577c9f849f8b8d9cb0c7efec7e17284d45c855638fe9ab2e5bdc52ba79d06a9133f66bf0ea2b5000000000000000000000000000000000c252a2e2769d3250479091050133808a1b0fd20af2b41cdeebe7cfcf7e3a92b9ab17cdf4d370f9fc391981db76de39c915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000001769e8b5fda96ef205750826f34fdda3587efddc86f69d37001c62938a90efc23a3ae150d223ef4bf3766ab7d86d80df0000000000000000000000000000000009ee24ab483300764bccba33b55b8889b084288ffda23d157f650df34125fd803624d88f2bd0c3c3ca51bcb57b9f4dcb58fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000146001b68cd902fbb4548c3e7cfae9cf3c8916e462f1becb9918c8de42483ef65f418d6e93200e8ec95528928916bdb10000000000000000000000000000000008bef4996b8120613292dc76dcc77b07b24d4498d6bd35f5dfb80ad241ad97bd161cb2c5c96fb250b70f8aec1aee5b56c29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea00000000000000000000000000000000019d02e168efb5769416132b0457ee1ca74bd5737f9364623bb270e8218c96e71dc49403584aa0a7e6c15bf6948ddb956000000000000000000000000000000000510c0917796c7ef2e100c7656591d04c3c5968d688b36b93dd690b0a8ea55694157fead964b85a5eef1815cd5932819dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e9000000000000000000000000000000000dd05e53ee40f051037c88fd28364aba276c793047007a20f893d13222c35b24e14f6c74004c3d8070405621380553af00000000000000000000000000000000191d7f1863ab7bc4ad1ebab359499f4df75b8c7a58fae8fe7cca530c7a56e5ee1617b343765960ca4bdc0245ff997a9221c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea470000000000000000000000000000000013320367c29a4f1527e8c0f3047f776d7c892d08988c402c55e90e84b07ed7f0932c3b5fd19f8d133aa839ebd90f6428000000000000000000000000000000000f8396d819d7aabefda680c8ad51c7f907911dc4da7c5fbb7e599e7f3b758c5e7c9e9ab4de1700f72f109d7206c1be0ee1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c96000000000000000000000000000000001413f6a4ec8b21a459a4aa33ea9d92614857df629ec16990939fbb8ab11fcc919a25a10423ded219ca5b94f71377dc2c0000000000000000000000000000000014a3320275a64ede5e1221c78b421c1e4474bd499263aa21e97af103d7cb62335faf4b85b5983c5865599b709e95efc4a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000057c3c7e4cf799d716483f1e8bd4e6ec91ad9566379683c54204ce46a0e5635fd9852b0a83328386643b2017b9b551f90000000000000000000000000000000010e3d5725beabfa7e4843eeb5bcbf6e7a54b4b82fd1768a3c276bba8fb7dd25dcca7e20e74231e2f7cdf0ff50cb9cf7c2c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b60000000000000000000000000000000009d85ce8e918ddbcc47494c4b194649fdbc8de31f5f3299ea4bec7c68ff56c7f6ae916c85118553b6a6634ef9b8820f50000000000000000000000000000000000c9a680e6389d447a4884b4e134a3e025f8679edcba56bf8ea2061a00e34d38c325319a8a5efb556fc2536886e225912908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f000000000000000000000000000000000f34c8793a9ec6c34c704159d18e385dc9a127e0a9b5f95667f58e68f5ddaa272f68f5fb55e105010fb656954f25927c000000000000000000000000000000000fa1d9379fbd273b05aaa8ef5397eae24cc14f83118b2584085312986c192d2c5e3a0fd8fe5c2d82be2ee5b006413a2be8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399,000000000000000000000000000000001223d94bca6cb3225511b4e28797ddbf1b1e2d059c6f7c5e852331f381c578016671b5390dff3898f9048e5868b253de00000000000000000000000000000000093eb1c50064251cf043e7c7af7518b883a8f760deac99f6e10c3dc941fed718f2293ec2cecaba6d58060374bce11a8f +000000000000000000000000000000001429e7011c17bff6df1b3237a06bae78d427720af641d2614f32cfef8c537d5ae9315c0b179af0a114a486e2eff7bc470000000000000000000000000000000003b9caa69b5495dd33139d14146919f9344efe2416b665dc262bd09ab91f3f07d1fb5eaa3c3a94606e74ee747114f347e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a80000000000000000000000000000000012b481abfcf8ecfcaed39a4277492641c420acb65ec809a7d55892091c7f76f82c02e7baf2a648cdd5cdac45113b11e90000000000000000000000000000000015d32649850a5c99a787ceb894a66b58066c9257dafc4a6cfad2887e7a19f8af69f8d1fa69258289e417954d064e63eb5307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af0000000000000000000000000000000006038134150b97e785f33b0accd0d1991c7b97aee1acf9bf671188f61a846a9603f2d3f56d2edc0564d1ea7967e112460000000000000000000000000000000019434ad4fe571da11e2de03c891d19ea2729f4bb7b7863ae0bb8f18b53852ad4dbbbe682da2c8568fbe96c6c9a7236dc7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e2260000000000000000000000000000000013786032ab493b5026cf23fdcc468ecc486cc8179c9510d99a503031d1fe39f9caedb2d42dcdfa17173e693e2344bd05000000000000000000000000000000000f1deaaefeedfac7f708092bbe3005be7c4b56499bdeb8fc742b72be7ffe4d8ca90e605502f1863d89a41ed794e06586eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df000000000000000000000000000000000aff14b235c3569586e67cf5113ac0ab32d442a1c07cd9e249149d719dbd64f8ec1b07c4241af135d3869eae05ddc0a40000000000000000000000000000000013d960e93447cf6df8bb48db45532d567dd2b0756dd674625657e5364f81b4bb94bf436b54bfe9afe8eb5f4bd1be90732c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000f85e9736fd9d3f9a839f701b6d8a6724af55ea74d28f101f97229f4b406016e50f54a0b3d2087117f352bcc28b53d5e000000000000000000000000000000000b2717e98f9fca574ad9202bd76ff6e53c74c342d1b6049fe66310040217563a4e5df460f264769418cfdc443dc31e008dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a02538000000000000000000000000000000000ed8e6113d657b2d3283e50e9d054e612793fcdebfc31c53ef4f417e63c76234900c627b7e8c433addbeb6a79bcc5d380000000000000000000000000000000012f0a3095ae16b5535192a932f188c62c3cf01d2184f8e299794bcba86d4573e423a0eda4e17b4b512c5e06367e470f6146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000002fa5630b261e07326fb51aa2bd897ab49e0b960f769e3207906a530fd759a53db8ae17fa79c8e8c889a923fb38888770000000000000000000000000000000013d49d032b888aeba7e652b200c91042f409a6a824d1aaa04bc402f94233385254a2d1f8605d15d04013ab0de9e40a94de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb9497368000000000000000000000000000000001495234b14a93a24881f3b4425dfd82b49aa1828746b06822097c8338da57db37ddc836a9abc46f7a0cd17ec08d36fef0000000000000000000000000000000013b868cdd5ed7bf90018873ae2ec84e4bc71d002483831ab7a4a19bf18feabaa210a729ebae606ea18ce16458e997497c2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf000000000000000000000000000000000f223490fde3ae0d7b94412b3aa86030e5d9dca805f6ab5b025ce8e9648aa02067fd29ab9a1915c2df7b2186f35a2c74000000000000000000000000000000000aa747ff7e24cf6d1dd2c4fe9db8c031b78830e98cab27cf765fd874fe6b7731c13af69559748c81f3915f9f3a6c63bac1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b00000000000000000000000000000000134f8ec87b5572c062f6f3b43ee896c2e019356214ad397f703a839d39215bec954f02d3f81e3442586ba9762bb9690e000000000000000000000000000000000218735ec0b5bf9b59dee7cfc70ec4c6f21aa129d604fffe824b7ed6b6346dc242757abbe98c19c02d5235da448e331d6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa2000000000000000000000000000000001510f39616d7f576980055d0547c603d882dbe85dd0b634577fae134f210736007345d035d815306db660de4a07fc24300000000000000000000000000000000064d356ad7bd2edcd3622b1fc225fe319f86b5f7da875cd57fe5adc5bdb6443c5b09d676950e2d069bd4303b8f9206928e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca10000000000000000000000000000000014d4184d69d34b8e509f3fc7e7033d76b10ba913d6109bdf842be4c49cc0c29576adae2f75e6fa054bd989e26bda58170000000000000000000000000000000019d0b70eb45a353166bfaabcb661b46eb1b7d8a59a903cbf9e43ceb6ece492e78d7f1765922e981903153072a08bde098efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c00000000000000000000000000000000087bc015b995ff8a840fbbf23db2cdaa8bb2dcbc38e12b588bdc4186a77409fa2a4cd74347f568c5b516879b70552df9000000000000000000000000000000000b15f04955dc27d19ad2a97a99e0890e6d3ad17d29f6b30f866f8cb3ee7789038abcc24c63d4525860e64593af02e39f47f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d530000000000000000000000000000000013eb2ed1d78059beb34c3fce731d42ba28c485dbc74916e373424917d60bc8c402e331e8aa2fdf70360049740e670da7000000000000000000000000000000000eaf5b5e47a2312410035d87aba7196f3f0b65abfaac28ac80accc9d87a1115b7f175e59ea2394198a2876568986fbebfb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b5580000000000000000000000000000000012d7a2e92adfff3d37ad21dd26299188e25b628a9e9d7b54d2eb8a886e80de812a32db9816964f2c0ad25d9f0aa6ae9e000000000000000000000000000000000c7084afff475bdc0a4ec265a3cb3f87d862270b6263a47d869786495abdd4316f6f154b997224d3a895010ce04151c34b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a,0000000000000000000000000000000019c9d9833332c6dd40c200d53b30e92f1595c22568b31882e24b8fb17be41f4d2f9692ca1e63106c59ba574f8b0764c9000000000000000000000000000000001414165b510abdf0d4a32236cdbe51fe2b5c9a31942710a07bb5664592a3f1b5c821edea44bd7fe185cb53b11d6407df +00000000000000000000000000000000038e60d2dae22dee4dad0d9e0658741c13d165d3718c763270292602852625ac83c5ebc1a6d86c181686cd01a1891b520000000000000000000000000000000007299913b59e2d245fa489d92873b7d2bc8921191a34a0d7f6c5774757ea4eb3d667ff8f3e9293f0d2354ef03cb6592b68ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000b07454ff91e3f9707880c1713c69f8a44d70040b44d96ac74d196853c62f264ccbe6d9c8945905092d9bef665e45bf9000000000000000000000000000000000405c965e2e8cb5e85ef9e18927c7e86e63e7aeb49f45b3428089010f34eef9ff37eb005e6b86e20236dc870661dd68c61529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f81000000000000000000000000000000000557c7f55246759b901e4e8478aac7b80d37edd5d6be057e5aeafe3d8da008e48c96c17ab1093a6a4fb39cbe9364fdff00000000000000000000000000000000158908f112d7cdcf867f1a5b05062b92972c2947be213ede3a7fed7a477fd57e69e1de82164f7cbd53a3f4f4bad551d744d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c0000000000000000000000000000000001701edcc472ffbbf157b1f239968924bb91825754bd4fda9f13450162e82932b8f5f39e54ec5975dbe7dc744d6d676a0000000000000000000000000000000017d13c1f6d64af2a808c3ba20792af9ee9c626235ceb9ced3c7acb4bc864ba47e55e0945a430da47da1e87f015dc024724872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000011231262d0fcf5a4b92cc1ed62aa66a55be739eab1316219ed2bb8d3e939e25b840b75f914cdd3f07b3f57bbc07c23e0000000000000000000000000000000001eabe4a5782244ceaa57ea0b58ed1334dcb94e449b7fb905805cefb786e83af66ded006cadc651a7b2cb07c3e3fceb401a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc000000000000000000000000000000000045bb823151b691e26b0e706b8abb248ecd87107a88c728e7a627a962aca7f85d4c88df949b3c53e2d32ef18f60675350000000000000000000000000000000003342b2d1a75300ae9ffbae66326936b19c7e59fc6f597ff09f2e5d50c1942f161dcbcbba00e4a46d87ab51074320132c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000043d72d26ee669ae8e47eaa74199feb37d51f5c99151a8f854362469e5acb2c5f6d2c208e7d674efa189fb90275b835e0000000000000000000000000000000019e6f1b3137bdb49c534902abbf42893fd576a211b93c831dee90723c7daeecdceccd3eb981537d4fe729d6e48d70d6ae62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000837b6a981e486865dc4d6d0c123230ced707e2518277cbfd0be747a8c9c76be6aff8b06df76f7c801fa34d11141354900000000000000000000000000000000011d745300b20c5ff1e607ef3a42ac31cc55e8be979b091aac0396748e607f00f30ff579321f2e660e90e8e5f9efd4f77a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000740837b02d2923815914ee9cfad663eb7246ec8c56e632cdc2dce25b6e475dbb6a75ed2ca6790f5f83fd1a274832e8d00000000000000000000000000000000188034daa9801ea182b712da519f7524cbb9f641146bc0fbf77e72ecd066bd577672c1ccf28a2c4d3cb9854cb2b9e7c80f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce888000000000000000000000000000000000014ddfffbffd0317ba7e248f648cbc98fac2be9f0cc31d6476f41527c25fe8d078207965eb2382ee1e0f08a38fbff7c10000000000000000000000000000000003e492f3667da69d44b35899f425af2ba51130aa6341bcc0d4d9646cc96b090061acece81ed16c7e75fa452818748b119b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c0000000000000000000000000000000005107fd2b5b483173992b0f2f51dc24bdba94b5174c063b52c33a8cf84ce3adefe0efe08e6bf4de3e68189e495b39c6d000000000000000000000000000000000605e8540f1c7f5790c306643a68606581a16a60d33607064dad5572947c93f3846f66afae10a66cd33621c6a2dae30c6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000012eb811b231a07e27e997900be274f73720afe3b0626104a9d5aed39a3931595f2ad57cf6e8f12d5110cf38fc8e7f244000000000000000000000000000000000abf1b8abe848b91333b4bb226b81a33aff5b8f7af70108538a3c706da182476a42e0e5c2fcdf694c8a12f62a996c86c030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d4050000000000000000000000000000000009d2b5044a8fe22a957b6d1eb20454db2cff51e7ebb6357b3c6b95387b1fd810b94eab4aef4f0a0aec4e6a693903dab60000000000000000000000000000000012ccb794eb1174735b5f7700ef95ccb67691cd3673d601dbf6b2e2469521f1b2ed283f2f98a9cd601867de4640c9517988e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000003eb6e7ab6dbf66614ff5b55ed36243e1d9baa317f01aacbd7f3a015bddfd818c6764c0802e97a42063a18edd9dd091d0000000000000000000000000000000018571d50a947e56f63b26a4377678c838de7b315e655104eeee48b7d5e6f5ee5d876b3ebdebcbde4080e022cc88c995326989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000906d5a1691dcb7dfd5d78f0688e95de2e2f06cdc70f8760e43a562365939d3fa23ddaaddfd1ddfbd3bc9777783a7ab600000000000000000000000000000000168422a6171f5ae44b645b6b6e73011494dc75e98793db2424bab311990eb7730a9a45234afb78aeff7778503cf4e5a03d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b10000000000000000000000000000000011c20d0c6140e0e11d3ffb8c28c6bd80ec495d057775f6dc331c98b0b0aba17568e1ba773771c703068dcc6747187767000000000000000000000000000000000f88fde780460bd75f46f593cf6fd0aa25ad14cccc061d9ae2cd8c20398f24e76ef614008efc9ffe1d1884df1122111b5639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef,00000000000000000000000000000000018ed322f140a351069f18d039ebded8630fd019e8c6c401dc760ec5cc6536bc2f52a6cd5400dca0caae95e4b9282967000000000000000000000000000000000b9666fbbe91ec8bd670b2a64571a34d689eac44c5b63a43f60da08df51b003d85c03f0eab3e2042b1175af3501de8b5 +000000000000000000000000000000000b42381a83d4472a3a7a18d2ba5266bcca254fade1170c6f55d442aa2a7674008fb35c58d5a638280e0ff7531617a768000000000000000000000000000000000eb5de05b5cdf9f95c5a3ad30ce068d5491006640be4c7f02b7498963b5769d516efb9a117c60c1c5fb71617d42c977142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000163ee62f1ea9219b921ae7ed0f121426fe9fb8fc0056916c81ea9e713f1a16e3f2bec6ed0e3e552a7173f8dffcde82bb000000000000000000000000000000000f5fa0e4980d3d2b92e98e76e5d67815ce55858852f03ec7b8809b02d4b1e9e1a6c8b06bd481d9d153acc68378e779fa253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b7000000000000000000000000000000000cfbefb41304008b0e7341451f13d65681f0726544f14fd1c0d02433d3c34a4769f1456960cfdb11b6bcf016b906228d0000000000000000000000000000000001adf387f4feeb3845b12449fd5294802ed30ae211d0837eff1b22c3fedf538ec7119c1fe69ed7d595f7c0fdcd54f684acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000d736d3b8b586e09d6ecb1ee2d7eb28bd68aec60234e90611da8f1e1aedebd9c74718d41a89186a4a5dcc3f7cc81e99e00000000000000000000000000000000000ec0e89da57affa4686494e8e0f5517f11532f6e294215bd060c370fc64c26e34ee1e2d77cf341226daf84791f5e3cd9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df0000000000000000000000000000000008fef795f8bfb6de5feee020a9363adb1c26fb521439e405570b4e997f55af5783968b24d2e95144bcb6b38e4ef9497c0000000000000000000000000000000004d4e31720644e8828faeaeff38985ffa4fa2f7bdaa476b5c4d7eee81c89491eedd3f4262effe118a4c204eb555abfbd05fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c3400000000000000000000000000000000195f8fc4b1ca0c7041810b02bbc38b8bcd0711dccfd80de2b2f357f4a732e65492d57f455e99fc810d6f86eeab0ac101000000000000000000000000000000000e3010ed298656b91b5aa342f6be7250cf5504fc3aa26a2c7f46f90e852fd7799d96a85b25e6066b7d24794648a81331d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee11909900000000000000000000000000000000192cc18dff89d9a94e6f0498419ceb9f21d70e42a1b9b64bea093d67075d499184d7b2106f74d31ccd1863beeb7be0a9000000000000000000000000000000000b80e940dce71be82106640d99c121dd21e99ba459f0dc8b1f11cdffaa0d8ab295b9711c23de1f4bc35120a89948b91a44c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f6000000000000000000000000000000000b53f55edb182dd08e2c9d0ee43aa3d734143b54686295410f80086d3aebf6fc681d1150e808d684f47b0eb23fcaf629000000000000000000000000000000000d73442636f4d5dd1374cfc7ab29b995420995bee9808aec29ef7d1aac08c0ee51a0390330a863295af6129b7e8171d82583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000002bd8316507e6eded2034cf268b2b4660211e6bea2e82b3e3a0902bcda0f9ae9980b401f36178f681691ee7c10dc4ecf000000000000000000000000000000000e9af98fdbd02ef62ae90f1e87c4e7a8eb2089204b1c58dc6e59fa32d001c97f22740d8a13ccab23b5a8842b693504a8506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000aef5a5d5b46d340fccdfad359b0499a5c62ff4e5d9b9d6f7a5fd6a97e96820b7fd226e7a2aabaea392869a40cd38e1d000000000000000000000000000000000865d32d825149d26b60969ca567ca85af5e280b835cf541b20b0a4db83309dd2b5700f802ed9106af73b912dcf9630b7f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca600000000000000000000000000000000153310de30b7a485753dd8443f8638c12b21083f6133a1c093648bcb566b33f73631c6fc558f32abeb0d6df8430e61a900000000000000000000000000000000005be397e9f77556ad952dba0540f46cbc7db910d5203cb976e168a7be3a3b8557c5f08d51cca9379552694a291d67fb4429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c000000000000000000000000000000000cc75210c78f2e7903b7c33379a6ab412e92f35de51a152cfd2f4a5d122f9e558b617d8a09670990b7f056e95eb058ab0000000000000000000000000000000000aee8eda7c1bedd39f97efc60af110e64662b9990257beff15ef5e7856e5ea388df058ed8aa6dd93cf5a81ba48cb88854a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a000000000000000000000000000000000f396976e55dc0c46fc4543a8dbf690b8da7b6010a03e04c9010f01abe1b3beab8870be0b6a2c6d6afdf85c6fd38d8b70000000000000000000000000000000006c60eeaa2d94b571df8a6291c2b12b2ce9f17f414264e4af2a006d6aef2d70436ef0978139751d4ccafce200f16f06113814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa04000000000000000000000000000000000543f8d9faa2b3cac2518f1462c297595ca10d8415143c8ff3feecfa58b648d0dd0c25156287b2f29f3b6f9a60f02701000000000000000000000000000000000be673141c496cdeab5ba8604e081ed3006828c7c877d8990efd29798c1ceae3093e052f1f928fac0c5cf84174283844aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000001342f79c96ba0a29de9a77cc2e10314bf2e15a7d192a90af9c025e2f23ff30fe49cf239b180cfb6f8c35f95c115777390000000000000000000000000000000011f0bfb11be253b3680817af2b929de9ccf06dc574d17cf6680643b87e5fadd06b54224f155c1393c870c2dd01d6bb07c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e00000000000000000000000000000000183eab3c2a127818862c6cb42bfbc9d59c51043dcc28c68d3fea08331323c9dd50cc34a4ef66a97f98684a5d9a982a1d000000000000000000000000000000000228f8f774bb68f966f3ffab5d0928a59707d6fb4f6ca84fed831a8212f71085cdc27b1d52909bdc005b3250f26cff3b9680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd,0000000000000000000000000000000017ddd94df17d52e842abacf3467f4461e345cbb680ae624f98c6885e40b17940bc240da17ed0a1a45f13f2ce4ab8dc100000000000000000000000000000000005ea5144aa5d5393b98db4d23477011638dba465a542dc28691ee2807ffc08413938ffb74e16c7afc507f85d76acbcd1 +00000000000000000000000000000000023977b65312306b1a746b94bebbe79ccef0342ce833684a273d8baf74e0ee71104d6c453acf02d0c4f3909144b1a3b700000000000000000000000000000000050494df74705eddbf97da56a21bd673e2b0d3a9cc157168b8b413a89359c9c48f09e756f8e6ecc67811d4bd8043bba91ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e0000000000000000000000000000000015862e2e3cb73ed2ba6b0b69dd9fc4c308c0a79e5cca2d2a42fe94e9b029b22b5b6aefe0503798d78d4599dd5c201cd0000000000000000000000000000000000c49723dfa37fb1592722b14e6c75110cf2252ad5170131bb72fa35bc359470bbda292fc2a459dab89900eb251e848e12943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b1000000000000000000000000000000000af2d03791884033b8293fb636b0c569d9b008b075c6c71ddd7b0c3f5e139a17e1fbb18144d1ecf491d2fc40b7369c0d000000000000000000000000000000000d680b707e32626219fba862cbb18e39e03a8b9ac78f7bde619049748f7f0e49cc0223f1111dfc1f5c851229e62a9cdc1551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b442117cecac25834a442ef457061634d863875c10e1809a3b9464eef6760f074e06c046a74bfb34f4d16255cd4f62a0000000000000000000000000000000000febea79eb8102b2632b6fe3151d9d972d5dded2893a117a6cd7e2bb662f042131cf06d04ca5c88c8535155910f9e008eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f000000000000000000000000000000000d355c97dcf055181b8c523bbdf7eabbf064159c15532bef1e1be56146d72c08eb5d6994a3be7d6f4a4ef204f0e6d8dd000000000000000000000000000000000cd6d4e6df1ef7cd5fcd360e8aac511a3aea1f3e29536c193f4c3a2ff0f3ca16ebec620cecddfa8f27732eacbea75500f72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000072ff95f5cd9416eac2cd83781acf856a0bfa567a079bd3cc909eeaf5a3fb31090e3e2ccc3acd44b6b04b47b5b8609a7000000000000000000000000000000000b7a39ab3ec7de26c86eee5d8737c7ae7e5969b03457b7b7b5720e3492ce254a63e031fc477361606a24821830d27271331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000886babc1acee93b5f96e4a0700805982657d15170c77468c77000f21978f0cc154a265de2f766d6f7f8600f378b219c0000000000000000000000000000000013cc47f0a1e5f7315e6ddb9003dbf901824e419854d234676e4a8593bc5ad4c15e8c59ee6985d0b729e7d095e9b7642416d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000567f08c96b8431a133cb284144f6ec8f7c68722f18ec257b4def0a18a754507eb477f405b8c256adb797f45ed2755050000000000000000000000000000000004945b59bc84df7b793dc759bc2a3352b3eecc5cd59bea7a9560c06ef25828ad2e9ccdc6b3beab7a71a702b829208b8556be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef4000000000000000000000000000000000584ae62e22e0c2fd733cf2093f7a1f3c763453cc34a7a7a4548d8fd43c95f13be06da4e41f257f6d38e6e6921ad0f6e000000000000000000000000000000000dc803ba6a45298075a8cf45939a61760de44d22407da6ac0d63939918daa6f78e8d0b7cd794256f992cc89b8622e737aea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef00000000000000000000000000000000090848e332eec39e026eac0e6416d1ecd5aee8b4d82712b6c113da1e7d38901470743af43bae951d4141592f6057caec00000000000000000000000000000000140f8aa557213d49097ef315a18ae7e62924a97c71139555baf08c70674031934b629a457f75bd801af579f9fe9395579ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea000000000000000000000000000000000e29d6fd73f56b4546358967d7f0080e6cad97531e3d672a91a6dd121f35cdf0f452dfee1ad98b7c832c2878b495f3c100000000000000000000000000000000050fe9818b36baa8ccef166247bc673baa8424e19a19b199ea5e9d0baf56fd68cb339fdf5d041b31545e28bb2b8fe32c773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000c13e5062ec580886d09c87c7cc72f7f19227eca99b0092a7e9759672ed1405d21fbdc8985847fa1b57129ac40bb036b0000000000000000000000000000000007d6407d32f846088759be5369c5ab66d2f512f00c93eefaca86a86bf7b1e3ef39ab85fb6c317c28c4e331a19b927650f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba6800000000000000000000000000000000112839aa4daa7b0d614dc6a555731cd4b595a0495f2a2f0f1a3b3fa1b603c36348e265145583e8bdfa8a2a26c1f822f1000000000000000000000000000000000383bcca42f2513ce42342f4bab5377ec276bf0f1910718c7203d450f15c5b6a3648a82e4cd1222109171030eaf05292d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a0000000000000000000000000000000010aa255df04dde054fc069473dbbcde9c68dbd71048b195df2b23e5471e5cd39eab5658ce689ca09db80c72e099907120000000000000000000000000000000013cfb46746c9bd13aa88a24ef3097b35ee2302e76b19ed001baee8cbe5b19c2620043efeaf81697ce48af0717a1066eec629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b000000000000000000000000000000001888735aecb7125b08f2a840957887fb5be0517788a8931fdb8d280579776c5ad70e6454303ba23908bc6fb864a4ea290000000000000000000000000000000019479631b9c711f700ff2353aac97cd0ddbf14669cc046e686ef19ff0bea0aa74b4bf771882f7226de0d4fe356301912c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d50000000000000000000000000000000003233c1edded239fd465f7f7833251b98ffed6180b56676bcbe2ed361438d26db671c03a6454a4fda34111e358eb2cb10000000000000000000000000000000003cc9768ad0576a34550b913a895e2687481c6adb3371bad5cc8f9792c61aec555a52bcb267c337649fa00293c9b4af3c685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7,00000000000000000000000000000000117879988edc3cc57fe19ab04eee6f9e94a30811b809afe922d63bc3d785a3a581b69512180beb89402410a4d8abf6620000000000000000000000000000000000beda376a5f1499882c560961f8b0cfc44c712d6691677ea24db18b138af8a21a5a4fcb9cf5814473b0ef7c00719700 +000000000000000000000000000000000bc83829ec8e98081abac2fa8e0572e819b570b2499d4cd1e6748f48c350c392f5d52c672dd0bbcdf1469414d7ce929c00000000000000000000000000000000007d1574eb65b391475b49857766c808fa95ac2a78755d8d740d2df90bfa9aab3dd5c850d536c9794f6cfa2f004b4550c067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000022e4ed74f98d69a9bb1037a307eed57210d3ca92648ca9c54546c7b57102558ab12f5d2bb46502ba3c07529f64b72b30000000000000000000000000000000005ea660c44a9d36696a899ed1bbef1d951656f2eae553f4f124ac9cee3d7de13556a7884ffc07e20d5afb7bdb9c6f1638b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d000000000000000000000000000000000f6f9411caaf7bbed9b05368ed8bbc35a0439a5c1ae417215d10adaab203aa0a607642aa8b94f4846add8f5f8db755530000000000000000000000000000000012eba1de04ecff3405596452a4f5830bc6c8af2ab0e84115a8a04a2cf60400eb741e8eda78ef733338494fd4e7b16f812db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b0000000000000000000000000000000009898acf9cacee1f5750d54798a4c31796fc471a17c9d2ddbd00262f5a82e3ca968c3e02334c29aeae9b16d8916def1600000000000000000000000000000000017f5a3907bc14b6cf182af2778c88704fc6b02d2b47bbbd6e40a448a89ad1455f868dba330452112973ab69489534ece2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000e782486684a6c3fd7f5977fa40038e8a9ac0a8611e79c18ea5328248be9ad4d95c63ba9ce41d3b4d85701283369063f000000000000000000000000000000000a98e9f649d2431991dbad1cc7f4ea0c89a58bd7e75e4a5bf7d9a728943363777c1cf84bdb1853a976e4e66a6d3fa8cbaa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a000000000000000000000000000000001171bd468b4d40e77b8264e082cf7a168d88ec3c21adb6c33f215e82f5ff3d0d2314e0fb12d7ec93aca92532debde74500000000000000000000000000000000099bc823a44c54fd379798eed2559d95275b324481c248d452a02755e1b5a48a7b0694b637dce4c21ad7d73a63cef2a3e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa00000000000000000000000000000000190f9d82f079757ad752b17b419c63ca09e3c8a23d0f56b1e738dc8ff4d588a4a2360687679e51bd75615c18c49103c400000000000000000000000000000000191b91de53dc0807b537540e81d9219daee48ad27de9e5ab2980dcc09062b80dad2a0a9024c5b0465e04e6ea2b225d0249510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e4000000000000000000000000000000000c8f48d3dacefba0e1719f74867b539a65d640d2372ad38bcfc43548f7ad3d8a04337878529119b9175068b511efb04c0000000000000000000000000000000003c7b5c11985fd7ff7c75e2cdd8670f75de655aa81f6b99206ed8a344f86ae85d2fb14bce434a25a5ee25c903c238341713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000055e115a8a7edec3a443354b381f584ba13a5802520c54b51ade1bfc7c93c96c7cd66254738929aea2e88edf2895d82f0000000000000000000000000000000001bdf3f4b489cc22c6f57a1eba23d3348c5567d0dd1cc82924873813b92a0d0b2b90727589028b9844d351e13c6e3868c040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae400000000000000000000000000000000041fd1625afa48a446454d6613c17cc6a65b3ec8b8f2125c0eb7b8e5d07968397d43969a6579226f496d9b24dbb71b820000000000000000000000000000000006131c506f243b5ac40354f826ac1838839eee9f61301aabd88e499d40e57df3122edc8b36f0a8b16b72f9ac783efd3e17b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a000000000000000000000000000000000113a08cd0728cb3bab3886681d8cd4e5f14b3a4a7979f9929ed4d8dc77de6a65f7bbbf8a282818ea3f21e6ea59ab1f5100000000000000000000000000000000032e95b26193c9768cc9967c9710c7695f57fce8a4e089f290526842963504cc8c99981bed3cc7d827eedcf686c813c3bd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf25752000000000000000000000000000000000ac19ea5cb7169ffa2741bbef922e0ba307e2bff5eb67fbd2c1545bcfebb79948489605f3c6c072444093e996594c95700000000000000000000000000000000111c277e16440fc3f0cfe16bb81b927cf76553fad040c1825210fa145240abb0bfc8a40a016db15844b8830d4d725da3f221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000906df246466ac720b1db9445902aeba8ff5c747133b037f29b33880b3f511621a0241fcc46adb0532682feb4e8819bb00000000000000000000000000000000145b356e384183788358353a69c49332ca137e9faf30bbcd7a67434a980c27630c3f21781a36fe73e82459318b59331bba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e0000000000000000000000000000000003f2f02b7ab2d2165836349ef8f53e42d223f4f6a892e7b72db93362de3929fcbda5edc4606766fe26ddfda9d09b283b000000000000000000000000000000000feb10a6ba91dddb0829cd6b95a78958fd55cdb120a7237a2842df1a2007530775848c3976804824698a4370fb022bdc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001433eeb265f1d57027a80189806d071edb1f5ccb97da0b5e00dc75eb88304ef2eed287f5d74264245684a1677a23b3f5000000000000000000000000000000000be2d2b5fd307192ef8a0b2b4dc9970c112a236a71ee899a0a5147012a206a0274d34901594f54bdaae26f2552da481b87eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000011b50012e0d92c0f74e3b6e83d60bf77e710dc03baeedc949c1af218bcb87ca1528a745aa819a5b615ac355dec360eed0000000000000000000000000000000013cd46e3cbe008dcec36e64285173b7d545359c23fea32d3a1fa2918c5c5d671a87d90791b70a740564c0f731fbb32013bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded,000000000000000000000000000000000cd7a2b89d286a421808305db523aca962c3f253f6defcfee9b74bd7f00b3ca8521b520376659d584b03fc5dd736d3f800000000000000000000000000000000117b8b8a8e299cb0fe3eee45651069a21988a2421b1d349529cbaf7362425349148fa043581e5fd275cc94a4fce44732 +0000000000000000000000000000000016b98dda34f703f90438f5c2624c1ccc870b18cf8eb964800ec97179f67f82c521b1cccb1b81ebd3484da1349e4c0cc3000000000000000000000000000000000c743850f15041ed9023ce296570036f96db4a510903a0e7971592348651b44afc0091c8f0d6e86bbed8bd3f6b28072af44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000007df1648d65d140c775f729e7739a807a7f430de0711671807a7154a8e5723a2b9137175d47bc319ca659faca10af23d00000000000000000000000000000000199ebb99b555fa438587b9badcf5d7858029e905b97229f1de4ecc1940ccac59503e0e1a99c9571d50ba39ac3619699bde25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea0000000000000000000000000000000000027b64caa979063b420cff77cd259e54bf86498f87e7297651f9bbad6087a8b4b704b27746db53f8869d080a22363c90000000000000000000000000000000003239455ad4ab885727a25b0cae5115d87ac9ccfd93120ffded5130ac683b3b2485fb358e3aca3b6cac4bb3da5b4210d2e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b210000000000000000000000000000000002f14454852a72159581b8a931d863c65170fa9280cb811c697fd067a505910d17fcb71b27963c2a6a02264aa0e1fa04000000000000000000000000000000000303f0857d990e90e19a076d2d331f5eb7fbcf102dbf8d4cb29f159fa2277eb413c0c10c3b501cefd9ca581ca62876c5e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a91300000000000000000000000000000000192b3fcf7dd2534f226ad51f40e7256064eb788e7c91b1155908fb752ed4e854fda44af13f0c681fcb818eb4202eb64100000000000000000000000000000000125b51b4cf8e9427db9baeee0417b02c2d296ec4adfd437667238ffe5137b85b40fca4fa705f81d0b4b6d788a8456f1fe6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c400000000000000000000000000000000130798c851758638c03f90f9181814eba97c5f93de85a71bbcc360bc53e4491e8fea38ff8c94061cd5008b0333ff26af0000000000000000000000000000000014758dbfcbbf0e1c78fb3ad4945bd300a74f2555338a009d807e2cf0e5fe582729556bd3ecb79db131ed9a72c3362c37e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f243500000000000000000000000000000000093318a1c189c8957c9736a56a4b3e8da13bc8a303303bbc106148a0a7f319e30f5dcd11787dcd3424255c7a02cd3e760000000000000000000000000000000015f0767a3a1e3c448ecbd4ac8c4c70db6daec95a1e4b3a69cb5dc10fb43f8ad030e360832f7726cb166e0fe5fad0c860c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000c7143093aea0143c58e2c459472f44b6b759a3f036aefced481eef6fb3a1b2af72ae4cc4de06af2a8a99e27cf9cae140000000000000000000000000000000019f44d1120d82e50f7da3c1e87a47d3433152b7141e9085eb54e04f30f5931d067f9ad559cf5d092dbaece723e6a724138cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d14740000000000000000000000000000000012a171d46d2bbfab83d02e023f5edb18e353ea82174d1a1653952bbba234c7de4fd5ed212c81f795e8c7a0b81e37087a0000000000000000000000000000000015dd85eecde306a845917187c404cee066038a764beaca9a58b859873b06652800291506b4c995581866a3c2bd7f19618a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f800000000000000000000000000000000176e512a4122ef10ca1fe6626cd2c839d4c573bede92092e5ca55b0bb936de9b62297b2a598a033e9a7e49ba9aabb9190000000000000000000000000000000013bf0f4c0dee3c9298192748497803a906e4192333b1ca61deff010a63eb8e4cbd63c7bd5b5546540e71bcac6000eb5380030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f600000000000000000000000000000000066bb65bbc3f8ed9cdd5cfcdb121274427ab7dff904551a60be48f8197c84400d54ec27ed25c2a09687f1067c10edae5000000000000000000000000000000000afe1e97e1dcee30959a6411328f0d69134bb4c3a0d5ac53b87f254593f7cecf3070eaa9e19de76ebc6e1052a41ccca00d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000001439b3031d7272f92c7072c6b44dd3a1c328251d34e1fcafc5f864b7072086168fa6f398d6334fe7fc56d6fc0e776eb600000000000000000000000000000000090885199f56df470628357ad224e19c29dc435ac54b8c17a7df5cdd24c3fdfb136952063dcb446ffe271ab5775bbc51969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000011a0c8f7d76a36e605f193efdb5f7899d7db5b89ab0603dd6184e69a7e51f0d7e12f466fbc917cc5b6dd6d4a0bac16c30000000000000000000000000000000015dfa17cdd22984bec570d2ca24a5ac373f6f174b66aed70a15ec892caaf92c73ad3d7ef11b2f4a0104df8ec5397f5e9957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000004bfe701f6645589925b34c1117cf62752b4e242e38bf056ef36515338a5c3698f561d65b237123677d926c1616618ec0000000000000000000000000000000011892535443daffffce0867dee36b7bc711006bc0963e6a061066b889adcde877a8dd3661250b6bc48064ed9dea304168e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d50000000000000000000000000000000017666cd38f1e7139fd032a79776301e4eef7fc22c144900c711f1568634d9712b2e3566bcfdd152faeef20b47cf6cf7100000000000000000000000000000000150c30df0eb5945ab96603b0f36120a4f697b6958a9929f6dd8d1b8a34a1d1d3f1a34bddf9ff7f1e105ca23ac34b6f7671a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000107f9378f695524614ba000d6fd1b72c5eafc4ee60c5ba36ddb72814936403fded547f8d15083186f7f5f5d94c1ce18300000000000000000000000000000000140bc17d86038d4fed0580582f55d90259b460ddaeb37a70063d09d83f5fb6c803f8b467927758cb7cc52a2a6f8a84ba2d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641,00000000000000000000000000000000179ba87e1596396fb5cf88f91f56e3fd6f01dda6f95363be3365bd42960ed620b7648863524c22663bad20d128d22f4c0000000000000000000000000000000001ad84c4e62b0494bab4f2ec2931d2331d88502674a1bf58b765eeb022f3d6fbe8f8546818d965a8be79a94be86292c8 +0000000000000000000000000000000007eefedc0360b258ca2bc9add8e23b9d535f35332e7a35952fd832d7fe3d448aac08a01073876a21914a501dbca513850000000000000000000000000000000016188049abc44154b244c6af4e115caa14a977efcdd524ad78e5dce010f2f48259708d14454630eabf2318bb271315007740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000010a19a7cae27e432b77c77d26653c6f17507413a5037621bdb096fa4f33e68dd86d5aa3b52fa54655730fd88415c3eea00000000000000000000000000000000031925aae4540280dd6d08fc53478fbf05b0ec784d04abd04c3a8dadb04ad9adebe87101c6401ebb4a808104b3d7e88fd226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000000447e249cb49d64494fb1f1b18c94a44791fd8d4957bac13df1f992480f72a14c3aec517184700d87200092e866d60ee0000000000000000000000000000000018a12284086bf2f64297a65f6c8b55b4ff3b791372b88aed9085152e24b1214655a74a182e131d7023f949c8cd9602dbc133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000780d3f5c10ab7932e3e3b45c942d1ee2a12f28070674d9c666016d084613f3ffbcccfb576fb7779feb2d0e614106c990000000000000000000000000000000000ea320730367c89cf162305c69ad594d8730d71a910f53143770f50024bdbc40b7d2486b1eec63b1ac7dbaeb51ef9640fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f00000000000000000000000000000000049ff517593107482da6805fe4ab49cfe9cf71c9a95eba00091511719eb76db98f71f089a701c6c136b398a40dccfee700000000000000000000000000000000038d1566f1057bb2da7813c39374b79149e598e1651dc3541a445264693495dea35a6515dd2173f7de43964dd5e8257d70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d00000000000000000000000000000000016baae36e71ce87a6dd7136f7572788c256ef88cb73e550641f14a557828e06ad64f001fe78d69465fed92b67e8dec3000000000000000000000000000000000613a6b87249bfdfd01016ce920aaf902de85c066c2d64c866ca0a93950a1a971cc561560a4122d9a766e38f9dca9239ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d721000000000000000000000000000000001921d310700ff4e2868a28dd29ae6e0216bc27ee9463cc8dd2823a1b4670abe973859e86719142525ae5c76e2df0bae0000000000000000000000000000000000b4b4952e96be92ba6c78037e529c197c9404cfb67af04f39d24045c742b34a700057b2cedb3193dad70e64944642c01fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000001dae69033cf21e6e1618efba143426df1501250c82f214ecc9ccbf957e685d9831533cf7f747fc22309227aca1d1a2200000000000000000000000000000000114abe65155656679b89a11c7961435ea9f77fe2f957833dfb61b8538695e2569e509f0ee2c0bfff75f83d9399a3d49b5af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d700000000000000000000000000000000128e019ff92e7171d3c791bd4cf75b0f47c2a9d8722b4a8279f1178db6dddf8a4c00083a935168518a1c26a56b23624f0000000000000000000000000000000008d0c5f3300e73682f4756e6ff1d6722dde576beb587301ded34427d6935e59e76cc8a8cb0ea5f659db9ad5435851e53c7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f1000000000000000000000000000000001110168c2dc1c2f0df0dc645970c0feb03bd644fdbe1576d5e5a8090282bcb81ac9be738d18e72a31ceeb5ba826b40290000000000000000000000000000000013fccd2429da394be698812af6c3288e89a26f0244327cd38bc85d5c3bb934004bfe24449534b7d271add7a279bdc8512d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a000000000000000000000000000000000199b9c9772a8c1bb0c015c467098bd38b5f73e5d0b3f627c8279b8dc853fa2952faad01e7be353a2762b8144cc1614c000000000000000000000000000000000f781597005df947eaccca59939253b936d1ae84805ec27dde0dc707a4583af408672addb2eea607a14faec9dabe61ae3549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab710000000000000000000000000000000014bd5d22e4bd2f7b8df4add90446650fd83d72d531395fb35dfcff72eca0886ded935e7a0e3fc99a7dd07efa1ed60c3f00000000000000000000000000000000122cfac9ae5c98dd162576c92e9acb4582b9eb67117bfbf4074654fc8bc473793a7139995666447a7663f3af1446dc35c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000f67ef1eff6875abb96378e5a7b1602b5dc553554987589b9953c4401fefdcc5cd7b196a1a65cb3daaa13f9fdd703835000000000000000000000000000000000f58ef60be74af52c23662e6b405f1d5c359b2ce9d15b5e139460e10da0e31161fb52f529c7b406e52c6f600d5670f3c9ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000000b7dc680fbfff55bf0cf276a864f448d5a9feef303d2416e7d87d6d669456b951a8769026bbba545685e1f92277b182000000000000000000000000000000000c36a14d5693b0d9d91d831c0581d1f4ee801f86e5c32f10cc400f66b58f247594c30f0059b4ea79995d6f9d90b0009ebd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b0000000000000000000000000000000014dd947a01add8294f97a84850e6dd11ed4a513e7656daac5b725cff501446e95e3b966492e028ec23fe1238b53d99ea0000000000000000000000000000000003d9726342018f802df12fc867998b6016743739a2a4f47e1f6f50992e4fe23a6bacfea0e7ed5be570eb8242ec4101ec10bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000014182228dbd223cb5b601521608bd7f87659f86a7a01233d4158484024730925e3d841e05e07f2a330b9495fb028db6d0000000000000000000000000000000002e0ad163d40a56215a774751434d19ea17341f41701d41e521983ff753ed76c435c6e2b543510e47060edaaa06d29f665c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d,000000000000000000000000000000001364f0b671bbcf69b3a101dd758ce54434d59fd053591cb744841ba2061bbe2be91cc9f2cbe1ec058119ec8a5e909024000000000000000000000000000000000cf5be1c16fd10ff97a154826067ab7cfd7b78ca5ad0a2e2117155f1ee126286019f8a1049b86095887e79ba67216a33 +0000000000000000000000000000000010040f531866c4e6fdc255e2a7ebcea89ffc36d44e265d5129f8be44b07f00646a7810662723546ed158b2cef146c7120000000000000000000000000000000016d6a5e46b2067c29e11d00b6b6ae9f0987afb4e9357c1d223fb2962589c3527f94d4e01f2ce6a7c57f971756163e48108e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b790000000000000000000000000000000002b6e3a234119f0f06a2b049d952230da40590a84d241ff76483169428e787093ae88c4040c64f2f1e3aa5be2c37db3b000000000000000000000000000000000732aea9a2ac5612ac350b474d9d267dd1ffa822cead992d3eb411efcb6992d196d66868a0e1f89dd47da584d075d4f55e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d0000000000000000000000000000000014c3187e04024d719560e36b5a63228a685f085aa080c82244a3a704aa2ed68b219d1c699e49dc1fd648e904ae638e3d000000000000000000000000000000001911df5a9f709b8434856c14fe4111935156a984a5e8cc27081059840167c3daf468a290461bf6cbd2ea4fa21255d7c11362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000216ba7fa8afa06136b054c11bbd978209017dc4d8c8a2b05fa717a97f4d88abd9efc1e9879de709b87d7de65c859b65000000000000000000000000000000001797c34bdde358ba5533d5bb531915545e3ba359ea1fd66d9dc2ce06f7cdb64684bf11e5bc02097f3b957957c986de1074d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de0000000000000000000000000000000013343f0b79485528b8a5ca5e0780e8925ea7277970843ce3699046673a41c977dd0cbbc97273ed47a1a105a0017853340000000000000000000000000000000010f3232b511b8d529f91f1ab613af1e2443947fb2e29c4f98d1dbab1aeb965079f64281d0b10e58e26a4bc0577943873389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000005aa017b9381423c9d00982fffb93a7cf9bceceaaf31895a17ce3a9bc42bc5b6f5c69679ebc91c9e5cdaf7651cf78621000000000000000000000000000000000c77e86d84377ceab757a0da9bcea401b3db29e8e577da793da0d5338eb471315315171ec4bab4e9dab36f4ec6d907a85f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000001971e5758027516443fb373a8ba8cb98b78fd5d16b42a83becd2a9b06e8ca7d255fd687cdf10de7dfc6bee5cfd199b1f0000000000000000000000000000000013465b45ed2469c2dc6ef4b4b8ac90b9b30c793425093898203d3b13d76cf4b8e0836c6fe57e637a6eb08bffa3bb55250dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b6000000000000000000000000000000000c14833dd82daba173eeb40c29912c0edacff741bc3ab03ae4911c334cf91d5832a8847d7e175934f61089f523b77fdc0000000000000000000000000000000013820819e27a27009ee44a5cf02e995bb317ee49b6068d2e9f4c5f072d233a6808d0feb61958e047f70b2bb1a5426319c579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b53600000000000000000000000000000000105a1323577a38bc9495090b4d023a9dfed8b510a9a6d755f7ad6af72eedf1c92e6a5172cf68608d8dac34242d1e0eb200000000000000000000000000000000147d889d919a58de8aad3b4735359201c47d8961a1dbd321061a81c67b1a05c6732782975445d9c1f2aed12b0b7306f469f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f1000000000000000000000000000000001335049a2ed3629ca83f041e4ccedede286445e4b79f3afe225bbee6273e0cc84b32b91c54991dd072c54ecf0d6c538e00000000000000000000000000000000098220fab5661a40cf34782efcd62ede159c82dba8c6e9f032f7216b888ad85fca1031c4622547a03f14185b3eb6d0d576618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000018799254b6fe847f53e2892343dc77efa3717bccb3589b776584fcc9e934deb3b8fa4c1ac0709ce505ca4d1504ed822c0000000000000000000000000000000017b98c35564c9d67b77bfec8ce23310c93167a5f75a4680420e8d71d8851f4061d897fd86b52d4a8cdde391c5b21a63afbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a0787617788000000000000000000000000000000000499468c8da336124bb89285a81eb76fb05e4ac2bde68d2f78f1de8926109631ee3e33eeebf686c7f6b7b4d68d13d2fc0000000000000000000000000000000001ac43e7c6d46e88d88a195180df6a3a91b3aabbe54f88c8b39168ead4b9847a031561828b0076b9b94c8fc7cc0c4636a0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000006690b59efd7c3e7f9477cc35fc5e13a5dc7f485100ecde7771e7bbd9f79f72719cd45cc9e0e791b7b5dee6f0252c53d0000000000000000000000000000000008b6f82c8514f7804a1d75f347f08334064b81ff95765355550c53098e19a4a5fe59c6a9611f4795981047754a6304792c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b0000000000000000000000000000000000ca51cd2fbe8d015a2e80bb4a24f52abfe6b99b1fbf1b656d4398f76e8e73e7a441dcacb43a4bd0a1dd45df2ed03a4e0000000000000000000000000000000006269d0e0f77f3ac5af8f70905ddb323362ec5de91a1eb90bf3773457a2bc2d018942e58c04013b83a7764b6639ea87c141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c59200000000000000000000000000000000122f6c35f7b1456952b56a5f90ef9066a191a4164d4b2f81965bf7318d485c725141576e5a1164c3c17a8bc387c9262800000000000000000000000000000000086bcc20a2f0f0afd4ce845243061e1c12eb238f2d3fd711000f259c31d826c2bb56617479139cd611d35b6548a438101d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c00000000000000000000000000000000008ee124fb457671b65c0f9f550ce1ef196c3bf13a5403a3a21a801cb1a335012b43cbdab33a1ace7f84a998a4322ae20000000000000000000000000000000005b0067f853d9dec4dee3b2834679b9145bba170f22b7e1dbbb6ca3dd98abe4f41673b283f9c43f2cc7ee2305b874a0e1b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4,0000000000000000000000000000000016dbe06af92533e96177581a7a349811e0a3d3826241c4ca047411d5b24c76dcb6849df7f4ca050286b2d84afd71ec9f0000000000000000000000000000000012dc4fc05526d6dd6da27b117294da4e9473a34699871b7bc9f02937a8308c57b5387a6fde0dd09e8a01702a8b97c4cd +000000000000000000000000000000001394f8d94cccdaf982b1c6a8080be6bbb65c9352a961cd5daf2f817a17bd8d5e3e086c6f54f6068691f3edc4378215350000000000000000000000000000000013560d0482e6ef2fc19cf274f85bc3d14236273dd8af86107839882dc26dbe897a7de90ab5457ca440498265bb59e59358fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000bfc36885481f9ea9aa275c1b4a774fd01476c6f956fe75b5f6e73199928b1928108658e35dad50b298307598582443a00000000000000000000000000000000161f833b58de4db4de0af0fd17ddf81ba20e4b6ca21dd80852cb992afce9857e6cf99cc580664a970e9c6928d13dffba73b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad00000000000000000000000000000000042489a05aecc0fc139c0ef0c703860ed36f8bdf50e4c772487c0d27b46b395f6417ae34ee98290a40b3b765d5a41d430000000000000000000000000000000018fdc2c8ac7aa01ae6dbf84412de8a47c3c504f2abed060c63190265babf779384dd6e3330e91198f5bce5a103bdcd701ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000005a44b3af7b95c7869d74c7084d0e556a67b39090b7a62fe51fa833cee316044a26d4e383695ecd3bb1715d0693f2f1a00000000000000000000000000000000112fafd6d6f1da250d12817711bc999217d16d7a6a923b5e11cb91a333898fb27f7b89885567d33b39923d7a664960eca691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000197317f509ddb9d536845443d7966314eca15f20cfcbf3ff2f8701d94974e35cc0957855e0085b3f85c7da512ea882910000000000000000000000000000000018b1ddc196607122be575ebc923dee96823fb4f8ed05fd8639b1af06ddff25398e67709809b642d4d9c21dd8ab6e65470d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000000c7ed49a60aa90f074af9f7fb19f6e27ec4a83ce2ed77a44c70c8e0bec02318bbe44a212c505efed3550ab6a1ea2c6d50000000000000000000000000000000013c0a772ce2c97522607b1b05cd9a89e930b6371202b69eddd108237f1495eb1c6ca65549c5ab030cc4f7e3ff4492fe9c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000fd64797f2bdd429e6f5217858cb14d78b7054b178b74696b8bc8ec9f9ede70bd03c36c824a3f775ee2f8cd6be7e2ca2000000000000000000000000000000000f675a8a43da599a09ae2367240870636ed385eb280cc199fb7c4ee575f5e3c5fe0b302566cde70b956f3c2b20fdf09c6fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec500000000000000000000000000000000065856fe1dcbef934cef47b177ecb7df76cc8796624400d5c0518aa9438bcadf397234808d099bed89ab674560ffbb1800000000000000000000000000000000071b2ff64379ed3e20cda000602c3504616dd673aebbe7690e797d6428ecfbdb29f11138169f3462dffd319cad68b96ebffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf00000000000000000000000000000000094d6e0bae02b4e7541a27111092737e7b27fe742fd0400672953d8fd787482195a2cb59a91e8584be002976c3c3e9b8000000000000000000000000000000000c2146b68ef535ed9efbed7fd02ea5cf6ba8cc20ad8bce17c06e5d595282f6e7453e2cd267181e477f511cd4fd56e8b157f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f40000000000000000000000000000000003925e9f1e24531f9f26547108671a6a0fcf58aa6ef2bcf9f4f64b659782b93187bdf2988029de9f51e5d41cbbc4744d000000000000000000000000000000001975210e2c8bbd2431288a42f9cf5d6bd6c6afa2eb05caebe740c0a1f680b9cced0f32f8f84e368563183b97aeb6e7ef2d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba8400000000000000000000000000000000004a2ac3d53c193265889f6c3802d7c68b938ebb6298dbfa14d1a9f515647482c84ebbb3855686b544d4299554473f1d60000000000000000000000000000000003283688bec2b8ff2e34565f8e254d579f57f9c0fe0e8521129088099a5005dfa9d565d52a75a2b26148205dae83aa6a614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000001b82af64f984294882fef7e5ba880ed8b0a36a90a5e9680ddfc5d86e65aafc3899a7d63e2a420113ba29412a025a0970000000000000000000000000000000012b11a5bf0f7895e329c2c6bb3d1737aeb5fe9f32a96262d8268c74687a460c47a89e252e607032576e7b67f5ad655b87c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef72840000000000000000000000000000000003ad0dbf936f79659ccab765a61633ebb648503a774e92b24967aa8f8e45c5e26f03acbc7984a45e089ce68c5566664c0000000000000000000000000000000011686f58262dca9399d95cf2828b50b216e1df251b61c77f952c21374bcdacd99d26891fe5f335afb7ec76ce7d95b43f64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f000000000000000000000000000000000ddfea60c169079c0fb4b9c3ca539e43b7f184f31cfa2eeb942acd2a84b472597c83fb52544479f326bd1207b4e872f000000000000000000000000000000000102108e827cf4473ba1382a2fa8f3b904f20a40657784d54e3a91fcf2703dc6fbcfb7f4b0e04c3a53a24a6e14b5735f435bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf000000000000000000000000000000001286a578ce3829f289cb98aa41cb6bd7274aecbe15b5087d8c16d575fd991878b06c88f17fd4bd905c4576494ca9f8fe0000000000000000000000000000000018e3cffb0746cf70aa79053ac579c1adbb09ed5b6a8b5e7b84951460e551e9bb62f2c1968e37ba34f7633e60a5f1f2a97980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b28000000000000000000000000000000000ad648d5e0a45c8208fb9b6adcb3c47cf0e20ca906c4fdb31e5c2f0678fa3ddb6e27848a39e8035cfd9eb91aeea824200000000000000000000000000000000005ea40be38d82e2b256bd5e26e71dc642e06145d94c1ca4fcfd6e63e2bbbd7b7aa153b498793e94ed1d89691195b4aa3a256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca,0000000000000000000000000000000013a9e1e306a5cfd461a1df4f2097f0beb079a845a959ca3a6d3e229198c75d29daeb5e7b65603397353cf800e0a10d820000000000000000000000000000000016532afaf2b6d884a5b593cb8dbc38b4e2bbe522ac69b745fe9938834d66e2393653e31b052a803f3f901abdcb352eae +000000000000000000000000000000000a187eff5afd944ea8731afffb4aefde4046b812b47e7cd99687ce40a5af90d6a4a2c7e2c9ce515a229e6c99ce46933a000000000000000000000000000000000121183879453793d954c99cbb007ff428c721d0e0b9cef192dbb177696ab9d575d3ade2cd56964428adfbdfbafba7505805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c00000000000000000000000000000000196b029b6a808602b09dd4597db611f19bb911b3acb5dce08bad8676cae9910865355cca0a109bb8d7b60359da6d0544000000000000000000000000000000000cf045d01c1a6d6ae397b39833243ad3cc310be9220f71a248032e9325c7186ce3ea40fbcdae5258410e1a513b69713e502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000000c6578ed0ccdfea63fe924d0a30c4aa7d65d9f85ea832733013c0ac225f039bd6f94b4acf634a01ac67b7165a810db8000000000000000000000000000000000624981245bedf55b95217691d9dfbc16d0d83476f8c09a46f9541d77c29ff978ded7fb7fed7272701e385e016647463e7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a74000000000000000000000000000000000a406d8da1910d9ae8e52ac70f1fbb85954ff7590863ba9f6e00861160f83defd24e99be31ec63489a483fa77d84ffaf00000000000000000000000000000000170bac083f0f6f4ff5edbacc5cedbdfa314de364e86486cac0e656d27e6a4880ea3f76ebe0f69927299bbe4a734e0482e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba000000000000000000000000000000000b8a583c24eba7a27a05bcc606a10a306ec07401ddb8de8e9bf206250ab7cc720903bd81a2c17a9e075ecf0ef99ad65a0000000000000000000000000000000006d5c7e9faf830ebd0154dc1c366b84445a85f0ebfc13b5339294752f4d1dc352e0e4204d9d64abed83e8297325de2556e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc500000000000000000000000000000000122735cbd1927c40688662c740db5cb78425942985ea69c729061a6ba749c78d4fc3d894d07c285aea9ee104f59581690000000000000000000000000000000007c18425af769864f403c39ce3df4f07d4b7319342457d0dee30ce4bab013b754e2ab7492f2dbcd5bac2ec1ca2e0220f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed00000000000000000000000000000000039c8c0453627d13ca0e174f5a27525f8a0054ced2b9e7d92c0ba7bcf06c78c1e1632db35abe2a81f72b986934ade66300000000000000000000000000000000134876b42096d986e6004364e176e23f81637f8ffd3dd86097f480d25aca9ce3a96c9dc73b651106b4de307c002dad95586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000032e727809658a52f60a973d32bf47bff5fc45404e6652facc1801d52188dc7db79ac1bff415a6c3e49e417f205422c7000000000000000000000000000000000c83d3e5ed78c1304f42afcc0143f959ca24348510e241c3e79ed5eff8742a89b4ce131e63544b9497c2a1712999a18cefaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e4000000000000000000000000000000000c2bb8dd01510ffe473715d7714e47dc8fff0f24af49405e55a9537a617dbf425950ca3df681f1fb2a3721afdc5a4d730000000000000000000000000000000019fcf0bdc8cf0f14c4b8eff44ce2646feecb3ab0555f530f579cb2764badb6445166598824f7b0c46a857865ade1278239d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca80000000000000000000000000000000011a463b5221e4c3abd9652917379564ed2830907464235fb6f9038f1d3a4f0f8cf9f6ccbbf66c86e216975b2d372400d000000000000000000000000000000000f0e9d5050d539f9211ff7d3cf3f0e7108c5580b634b122810c78d8fe047ac709bbb06ab1501e08f0e58093ba8208e0d4c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a0000000000000000000000000000000010b293dd411de6a5cc464e919d290d6bdb51952a7a68cc27aee3ec80808bf05a50763fd4c17f25e94e655997bc948860000000000000000000000000000000000f18c7ab95bd74d9095ea9ea66b2b14987157162b8b8a313a781ce58b05d2307db4e853733a45344923488ae9dce1a459af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000013ca27fdf920f901634156567835601ac0b84efdc79d7d979c2156041bac04f3297c1799d3b0641df33da9647e604b87000000000000000000000000000000001527cf040f6c84496ceb57df9c9ebda89c394eef034e40f5e6b540e931775ab91a4aebbf6078922da479ff397cc5271ac72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b00000000000000000000000000000000197c0e4474e27fcaf203819563b86e725778409c7d6792fe41820c495e412382fefda97b7df560885082c70f9d522024000000000000000000000000000000000b14b9d40bf866d933a15e16f06ec16b502ea8e7084d68c74418414fd281a6da50bc443647fdba348b26b4a3490d0ac4b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000a254b07ca0f2c9219fc0dfb49bdd7901999cc85161f741500a3ae8be566e64f8a5fb3e59985444206a2cd02ed4ee99d000000000000000000000000000000001726739e92da7bf5a6d2dfbf96fee6028fc7022cb1be2f838ec1b9bd08ef141f4b24e093fcbd99080721063f8b7c98dc986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000001133389c12bf1d2e232cfef1a8303a733edb0dc4fa26acedbb288166fd232b79f67cbe76227ab2eb517f1c3126b929a30000000000000000000000000000000001ca6bf5c18255bb3c533ece833964320bee7c3da4af56d876058edd15f89b0ef147fba11e1d539d528e4bc452e17df8979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000000d0caaa05d3a01c89d6efad96f5957f1f9338136119e8530853a58c0698583d834fb0f045e212e6889d8baaa49815c790000000000000000000000000000000009e7fd124160f6ba3afa752b2557f1c4b5f4010a6d4a3c8a8bfe350c6b6e198b9e3d11f2ec7dc6a02dad4c07bcd4bb1d25ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990,00000000000000000000000000000000138cea47ce2ea638f06c26d24ce87073f98b023b800245b6fc74fc2851d79a402b49c54e5df4e1aa98e33801d3fbb965000000000000000000000000000000001558e37121ec3710ff5e6c2a4e418c803a5b83cdeec98c8216b8dac7890ce17bff08a95ca2aacb40eccc761c8a31e8c0 +000000000000000000000000000000001920ce210ffc78b2c053eb2106acf1e238ac5160b50187fe816010e8a95ec632a7fd29565aefa4bec90d87701c2610dd000000000000000000000000000000000322ce646a20e23a1a68361806cf072ae3d6310f4055f5289ace0036a90b5c7ada537e614780156f6a103ed726e15b4fbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee967000000000000000000000000000000000a1ee4319282f43ab9cecccf2c7f5e08f35a6c7e7bdc8dd2f4d642e8968aff377791a5d1e2b2152c59a8f36d9bbe04ed0000000000000000000000000000000012e60ad9f99f55859f2529ce02b8b41f8565705455cdfeef3cb315903ffbf29fabffc2546359007a36ba579b6dd06c2043cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000000c2aa9516360c840b7f88ce0cfaa0ebec502bc9cb9304c1a4d895089a2344bdb6623638e730cf30c66d977e077423a000000000000000000000000000000001163f60b32213940c9cfdeb2c86d5ccf61c0a714436b3d0923ec338ce7bd35542726a87a1311c8072fd589499c26521d733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b600000000000000000000000000000000116aa02028755dd5195ce0b2d3234d31b07b557a52330fdb50064a18015ae630f427a4512dff06f93ae67c4fd0c1e10f00000000000000000000000000000000117d4a68064b3f11d88ce976ed43ceeb742ba6f473645995a2773121b2b8edb8fa2715f51c8be109f8d91c44e8943e7c5112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea7100000000000000000000000000000000166cbdb131fadd6c4e7a94af82ce4fc4805dc34aacb0d6cd89e69cef0b9071b112ea4a7d9d03e3dd961b5d833b84195c0000000000000000000000000000000010736a73e2283849595569db9a5b0b9cabf2182c3d8c40a39fa32abe52dd6038edfb8176f64ec12671e3411dd69397585683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000ec2405173e541945011d09092cc3a71d9dd1ff54451127181bb2d5b50876a148e59f298ee30ec5473c520be0a53d61f000000000000000000000000000000001239198a5b1f6f57bce914583c3bac476a922e56d2bb30da4912acd31cbf307bc258f22fd9f6a0073ec48dfdaa4799bb5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000000232940188006769a382a4958383aa6702b2cbfb9c2907a989938ac618f23e241767b021e8ae11c23617ab393d4f85a0000000000000000000000000000000016a672061fe76ed943e36b2d6fa4aadf579db96eba5e4c60cda2884ddcbb0f37668638a3167d8858cd296917eaeff8e0f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da00000000000000000000000000000000066fe1f7cb3d67c20a1ba29a52c0c86d6a2aca176630ff20d45632398a39404619e55b8ade69e0cb0b7a6f363c3b2d4d000000000000000000000000000000000aa25dbff2a8c1f1d0982a709fbe88563813e918c8f25f0da9c5d0dcf3abc257f7e32def4efbf74035aee1ee350cd4fa57a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000001e54b0e8f34cbfbc20c9feffc555036857c31f453a1bbcffe67bb71d0d6b2b278b2ec5d6ab6648b397c9255a1139993000000000000000000000000000000000bb6d6c1a41675b3394f5b9cf14ddfe73c188592916f24240edcf0940fdab1d1fc04a11bea4af90d0d9f6734a743b38086ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb00000000000000000000000000000000099f521ecae704ed5a37ac90dd4beb4fa21ac197d467185c8329ad7b87c02943a228285b109178bbc2606e89699403ce000000000000000000000000000000000a95a85f84e76ebace78bbedbd13c6b79a6339dba246596e0695aac18d2b14b370c033e62a01caf8484dced0ebe8a76a03fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac377321360000000000000000000000000000000005b8ba4c7d3c83fbe9bcbcbf60b0b3ce42b52ca19a5a322fb18bc20f81c2fcac23e1f62b9fd6edde5ffa2e37f685e06a0000000000000000000000000000000008c03604012e4dff47923a2a43382edde86c76754a1073ba51fa3a2ec7011268ffcd1452d46786682ab2ee4848210cc635ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc62791000000000000000000000000000000000869a2ec19afbe70ad0a15532f776f56da5d7a7dd5b75194d0c65d0304c69a6d0363c0ff3b549e8d15171fae18ea13f8000000000000000000000000000000000389d0e6c9d73bd98202191b5b213fbe77bcf527faf98f4d25c9dd3ea2cec8f3b1e8f261d9fc8baf7b1c21dfd102f99104a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa0084527000000000000000000000000000000000138a4f53b8fcaea11869a6208e7498238dd80be79cde96885e6e5226315deedc98a17f8d75df733ab6f15dc24efb5c5b000000000000000000000000000000000d25d69d6d5a9c597fbec8aa7fbbe579dd86c5fd3747378e984c20b34e018b83f889bef3069c693a91ff552fff1fb8a403f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c98162100000000000000000000000000000000192e8e186cc9159d2207b0af2dca495e9d0c82fb376041360ea80562e470168b52a3326553902fd6f5a43ead32eb968e000000000000000000000000000000000fcac12d18fdfb661a12d112fc3414839bd34aa244ce0cb40be79718ec37a014b43856e5e4b003f4816e04ce612e63ca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c942342000000000000000000000000000000000b860984aed11a63656e3390f5e94695d8cd9367ad7961c65d714637c68ad88a3602699ed3f627f0fbc5782ff18775af000000000000000000000000000000000ed00636e74e8163645c43b8b31f05228da7c42aa332ca250270e5f14b3660fbadb8e8957f52592d942b1cc1bd2eb0a50033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000b162c0897755fa47053e45ee1b298404818ca282a7b5818364c292a6052703502656e536f2dfb470730e9bef0d7cbf6000000000000000000000000000000001924ea42eddcddda067126534e8b862f0e16dc0cc296ea892115a9ca9734fa03d019e90263be2c909528129a12a68d874c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87,00000000000000000000000000000000056604e75c1069b4a061ea72cae2cfcba90130f6db6d9c268649089ce4ae1cbd80120a086e3f066f2584f5b2233b468c0000000000000000000000000000000018c5e93590b89119ad7d44f80cce5ecd97d4a4d3010a19fd02a58342752b166a1b82dbbad30d6f3404b330dba6a57454 +0000000000000000000000000000000015f9de55b3b45c16d59adda55d9f5059e765ddc06d22d6e68c099358d8df0229c6fe368384a0486af1cc9e532f78817a0000000000000000000000000000000018b992d73dd4c602afd82ad0845ee2c6662c860c5b7be197c62a8a20e91764004b5293ea40602574e91c313e8103e7a1dbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc8000000000000000000000000000000000157a9795cf9a45d2ea5e0312783829cddce176c63eb16195e7994b0688f9f30a4f2b2113e955bc66dcf05b5441521889000000000000000000000000000000000dd9365359ce805327b8f627f02ef5458cdc806bba246dbb21065c89e7ac6093004d214145cf3dec605195f14f1a49d357df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec0000000000000000000000000000000000066d9a54dbb5fe64835523e8ae05bb70b1934e389db0ee7547da60e4af965c7eee14a148f2e3269f01e8a545480db610000000000000000000000000000000017d6a22dffc3eac4366d0d35bfdd053d73d7b3392e7f52fe04e7e481783db3232f85687d2341358d2148fb3af7e9315de2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e0000000000000000000000000000000008be91d2752203afba19d8f3660118f83dbf851a6d2c54af389ef979121c55426d0761812de72a79d46c66dfcd00d5cb000000000000000000000000000000000269b050e36718ef4ebbc89bd88106a4043b267d974439855b6027f7fc3441518c39af6d3fee46e87d399d3ef03c63c82897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a94000000000000000000000000000000000dc976bbec5c5791688499da28c1d120e8a68eb5511ddf54525c047378016f68e8590b95f05cfeffba56c3daeb0729dd000000000000000000000000000000000af6e02afcbb707fd4d8bdcb5e73e1db56d7a2eb02258b91ec4a5c46c4627525220c11e6e379077677e1b733e2df60e02f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000119b86eced2222d203b6428907269b950bcbc1519859c013349b1c7acf486d3da5c4b35319e6b1ba8ae815e4ea14a6900000000000000000000000000000000015c342be097ba679319b83a68164f6820e2ceece3a90d1ec296514f0ccab6e454a0fc444d599a812bb4d78e656e8897fce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a610000000000000000000000000000000002da8de95ee2ee1be2f3ba8afd8f52a4fd0e352c295e92aa8fe9a08a03b6170222f5d6cabc9b9d9bf2835128c6ece3e9000000000000000000000000000000000fddd2b5faaff49cec261eaa8d093b410e024e1620863b6b9bd882088b59afdd4445a4971f31738e2afeafb36900b2d47b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000012060c8cab190beadf40a2e3d927d7cff21c475dad04d64c718d02ead9e351a27be81a3c5a71c6c95aa7d7e287070356000000000000000000000000000000000233ee868716db87f46d546aa1a7e4d3e70b2592efa0104d9f4fab1680c627484a33346406f61499e3971157a6dfbf972b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000cb9c4b59eb8bbbfb8aa2e9ed72eab69735a0154645d68428f0bda762d3b061b0659b31a907f531a55c0906532c539e6000000000000000000000000000000001806c7e8a8d95a34403ec78b43dbfe0bb09014fbe0e019f8c3b6ffd91a75d5e361a6794996e975309fa716b6c6a933784f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000131a492451e5c0ff787a233f72766339d7dae09f2e17c6bec9faeb08e4e48d6407b12adf2dffa3911395d5f25980c9650000000000000000000000000000000001f14d5268c422f94657a20ca02be7d007ea88e1a352753b2fdcceca5275a7ac101c0ecfc075735eec82b8fa6bd61c980ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000d988d419d559b1b487297cec19386f28659fbc5f121750b6bbe941794954e82e67c15a9a00334527d85e9be706bc2960000000000000000000000000000000004c222c037fedce38f42da2b08f06614ec9b166cc6428e3c4cad8ffa440af3d8fca7b9e4aff727eb0890effbc2b88060002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000fc9786ef5291943cfd885238090be47632c10cc46df48f6bb5250a7a85690f1c90f5f5bae03a71d7c52634cd0deff340000000000000000000000000000000019b4ec13ad67e058906a3559cc683511715b25e52f39a591b22177e2dd235e042832f740269544de112d9100c1ae49d9aa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000570b5e7b74c04db066d0aa751c9f763f59c6121e4e2ca4eec222277049143fb2e5fa39ac0fb41cd85310e4504f662ef000000000000000000000000000000000b522af535ca2b9db0cff08bf8ba19862e8f964b6210ee19f0cfccae8972150ae41ae1b8ddce4b1d2733c7dd47bc4c87df9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d860000000000000000000000000000000000892cc60eeaa0ab6584ef2731538a84c6a1e8dcc2efa9591ef1321442684ca9fd953553268ac4ed44bf50004683793550000000000000000000000000000000010234542eb7231f4356c34e11e7b4f08b4cb405a31aa87f961d4eaddbdaf5ba6227b2764e7c7c9ba76bac7da3b19f6014005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000005f3ff7ed08cfc6bfc8f5b55e2b368cd7e9f4a508ab46c7a383b2123b0346b81c39ba1304d628448c65d8c86bec682760000000000000000000000000000000001cbd3457f6925d5b8db7a785587d0dc6e2ad2ff5a6683dd11c8946e953dee72bd52760cc977987cd06a2679c74f9b64893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd15157000000000000000000000000000000000aa17bed794d72f8ac77989ce1b78550da54b4920ef6ac4ee0e83bb3cac5431cc7fb5c300c021045d4d391c67963feab000000000000000000000000000000001300e87daa3c36d87138628ad9aac5ec7d62e979c83c5ee4ce9a375fdabc745fc5874578945395ae128022eb98c6d8e4f685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b,0000000000000000000000000000000010a773006edb1a84341f2971f9226841142b26bcc4af52bc8b000f165f6c59d094aa2eab1b83b3623c6c09145b5bf6120000000000000000000000000000000000130a0242c95fb2b858256de2fe27df7f5736765f880673300e3506a5e756b3b563e52b481c51a003bac76d04f56c5a +00000000000000000000000000000000090ef8b0781c66698848215b3aa84f7be47f86a9d95bf5a1ebe9c3dd6615d4fb4c6425f9e0029fa3d7b94052ef8bb252000000000000000000000000000000000cd1927ed1bfac35325d69fc924f4045c5af9fa5b0a18fbf6c658a3a6d405ac1159d1c82934aa116a98cceb382dde2ee94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000dfb10a6b4e5980400bc275ba5cd8211b8a6bb6cce026546b9459805ba48f46a429ba683ad3f96ace4a4ffd6cfdecafd0000000000000000000000000000000001f643a6d83f235edd9dea19f0f2ecb98a82ba295d8ad45f75be5c0d5b1a1522c5d9f5ed812d47da6e5fe8d7924648fc6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed7000000000000000000000000000000001824ddc80e263475b6ae3b73ef5613c7334b2f71c95d64cbb84dd489851580e767be29e7c7b47d53668a0ee3e6bcb03e00000000000000000000000000000000073f6ee13c3b05c466d35ac49c33e5ffebe5e8325f8f06b893042734bcaa4a1bc76da272602664c2aff48e731cea0304e5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000000abe490a12162aa01307e814684261566302501f589c655b3cb840876259112a1924b1ee723e0c81d6cc6b19535d52f20000000000000000000000000000000006a2205d02f58dff40715070cfd635aa5e68553eea8718090e5f6a96dfb0a2f5a23e11ba92d38a7cee16ce67aaf5de194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000180b5eb4201b4f10f605b4a7f5f5e25783bbd7c9e354238dacbd29563cdf119c832b4ca5c908329d5087d5c8c6786d68000000000000000000000000000000000ac5f56013acf364ce736c455a88a4b2615ca40fc67251039eb99df3cf6423fb85695cc035b6a9b47ef15db7406880bcde4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef1000000000000000000000000000000001963e29f92f6f72be2afa4635221b0d2f6afe9ada4582bd7ca4b77eb77fc4503578f38fb49aa1838751db8cf1ca0b0cd0000000000000000000000000000000009856a48f12966554afbcde1971499ee3ae40c9c5c3aef13bc415fddb97545ed84d5f50d2a26b9c16c4403a487dca614bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006cb805ab137fc56763f73867a7ee5635448a8a66bbeaa9ff07554db3d07aa38542884006744f6719f4cfab1392039820000000000000000000000000000000005e6f6f14f7aedc757cc458ba363fb5d97ee0dc092cf6866083722d4535e1b852c1d99d0c7c57e96a644de4b431c7f9bc3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced4329000000000000000000000000000000001045bd19d4fba8380467df25a777b1ed2850b7f5c5ff5501c048339c2f71278b2c97e4815973303e9eef283378cd8f470000000000000000000000000000000003278c7c8aa02c15275cbbdfc49f6286d6e7fb208a71a4da390c0c853684d7b4d8a6ab24953075a6a45f79fe0c9b910b70fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000007af4aafeee0372e88786c6025a710fad46252a8df870b56bc1d8a39497c2422bc01aebfb567b5b68273ac59b5cc8d6f000000000000000000000000000000000dfe4a8471e42dceabb609b983b59dfd9869f29fdde01a168c07247252a9be6555a823a61487778597e0ae305da4205fe538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000005f847129487acc07fffe21e2d0aa6275a586f051c06e2575f3bf8549ad9f6c2678c541d0dc7bdf909b7cff683ecc5bc00000000000000000000000000000000163451ea5122e16ee62d58d6ccaf8cd981a29aa820d77967e69478127a76092e9bd0dc9f24a27ddca5b40b1fe8ce18b130b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000faa1804b1f65a6ca75d032186b5dda63799a5fff3ffcf1f53eeb04bb5ce08be40fac13295937f34666e0f0be3bdfd9c0000000000000000000000000000000016a9086134daa2a1374fd8eb74ea65858ebe8b2990bb92972121ac68bd6bd77916203a1033ac4b163d863d9120bea0a33a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb4605000000000000000000000000000000000a2ad31568d9778b306525e275bc4f525d86c04dbb98f48e72adae813ce9d02dc6d826a813ffa5b9f9d014e92de42c520000000000000000000000000000000014e928d48c4ca7640a5f5c55c8ae756fb6f03bc1a8e4e907ba89865ce610fbd919a024e86969c52a4216d84b37673cb5c7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d5600000000000000000000000000000000041fc07f8759995530350fdb8712304083da882a5e4df8188cdad48a3df91a5f1bcc1b2a25fb3c9b59e2c935d579a9d1000000000000000000000000000000001925153fa12217d98007963237a665e56570cc666651c29729445adab3963d599a4eab996b192be1d49c7429d9f0cfe43563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe00000000000000000000000000000000006c45218eaa27435aff594c2601276950bb99fb3c1756dbec76e609d163b2593933b5ecd5fd8544d4bd2d145821831c000000000000000000000000000000000a43ab2ea73a8e1131e184fbe9004aaea198a3dab575d3516b422c275f20c7a6e5d41bca0aa3dfe7ec761dca0ba6687d833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b5000000000000000000000000000000000d17ec8ed30bbca5766def9fa375219503bf2f7322d2cc36a38fcc8471fd9d11d2a30ef004e39cac4d1ed2d33a66f7d200000000000000000000000000000000108e6c9ef3a5a41662fa16488243af3419e2d8e78c0311446186c96f20d9c15a60b5470eb95e0e58143a3c71a7565b05d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000017f498e192905962fdaf41120027d49267523bee9de8e412161cec69c62d2586752d1da3d15e89446b5941a2f321beb60000000000000000000000000000000015e9e4eb30296ca3355ba9c5eee343fe7edcbf5bd110ca5be12f55191d0f07b563881f52e65588a8f4b3e03dfce6566e3ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0,0000000000000000000000000000000008b5f4f55def15b4590e978384fa0aa87e088916de20ff0fbd688ab3a13138f32d1e89cddc72acdf69fd86aaed6cbc4200000000000000000000000000000000022a02016f38156fcff83fceed881f96fe14e5d3410b4fc50e607d8a23ca986351ce48d6af970590f68aa2ad7181c9e8 +000000000000000000000000000000001155a7d2cf81ee4f8d65c835ef422075a9453bb85b3566ec0545c1198b93749beffffbad14ededaa5bc6443736f77bb800000000000000000000000000000000073e4df0ea06345dba9fe772710ab71153e57152c74bd05d8cd4229c5ba1301f7e654f3fbb2a45526f1bc3b09c72366f16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f51000000000000000000000000000000001238e5a46f24e0f00d2b45bfad87f96140ce10d774f4a17c3df224b58693afa7cd0655e5ab202998f4f8b4b5e22cb82d0000000000000000000000000000000012628d85d982086640b09f046c5bf07b1cf718b5b4b20bd99d64382bbd8bd0112230609d78ecdc742cf1ebd24f1750ef8c02014d5392d30863a12102d1c9315839b5611dccfdb489207f918662513850000000000000000000000000000000001363b85a95432193800bdf353de1a5764cc2333b0369ca7dd539f230bffe81dce11288a289e0842f2db62a89e6f6af1a0000000000000000000000000000000003dc043b958167a900cbca116b097724e64d49897f8fb6a31df99e100be837e873328f5113a28c9fb510017d28d90d30d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea24900000000000000000000000000000000175aef023d9375ae90e9f562f88e0a4affdd399c3755c1b22494445d4e7d96899aa4d5f77ab9392051de4cb7e400ca830000000000000000000000000000000018e3eab56eae429c09f9eed67492181279704d947cff0f1c9a4919dff5e6fe07fedcaf5dae854dba6719194f9fccde1704753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000018d7001b1d4a67d22399c5f9b3262183a47b6fc81786f8f7b78e80fdafb3c0c175756e602c92855e8ff9d99d4116e3a40000000000000000000000000000000018451928599da4a14442910a5bf125d97f0b67af4194797b3f54ecc9ef0be840a1e0ede13e1415391f57044d71fae2efd1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000013ca19bea2e93c748cd2adf682a123416823a2473148e59d87da33cabba8e0ff2516e5b2bc9a8fcea9dc4240b20133ad000000000000000000000000000000000433fa5475709a7b70044f88a5949064e32014f1d64826abbf60789380db6d5ccfa750a868d9902e4646bae766e241acab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da000000000000000000000000000000001236e6ebf0b704a18f85281b09a9552e8a478c66e59c9f5d53eb6ff1f606fd667a6f0bfe239970892c9c295a378fe389000000000000000000000000000000000cc5c1039850f3333981b1cd6457a466dde93e2355c2052cc325e18604f59cb22588b6d892685fd7843938fc1b5b8d8a1d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a00000000000000000000000000000000003f86a5cabfe7792de25b9d8c58a283c5cef56e23dbf713851b42fc0d66481ce1946d1c632e38b9de1a55ffa0bd7f5a000000000000000000000000000000000f548b05782ebe160d487c622f8378786712cb5b68545ede95b34b08698f600e02e918fa2253a8be2c1b773cc74c41042cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d40000000000000000000000000000000015a4bfb53e57dcf53483fca1b4dad7f788e48fedf8bbd7ac40b1707c35a57011a0c7f77ce6626821221e59d8185b9ca40000000000000000000000000000000005618adc16eb9771bfe731dea180e7e2b3b0c9537806349e653a586dea4633aaff7fa7e7ff165fa16ae0013c9672a783214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e000000000000000000000000000000000039895bd3ef87c094c9cb1ec77229d615e76dbf0f3bbd399948a70714d6835b570e54f46f94197657dc94d36c4a49093000000000000000000000000000000000f1c6f8b06ea4378234e99d16fcc439a64cad45a7f8ec567755febdeeeaea4f4b133af18a4c00b3778090c5857739b66c1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e20000000000000000000000000000000003007275e93f828b96d060e775f2b92d191d6da44b1441bd0aaeccc5abcfc7d2b5e9cfaf7b8497016ec992b13455af2c0000000000000000000000000000000015c1320efcddd0709a12a75049633dd871747e51f099e40908542a3e426d7a29b6633f5e69a4c0b5c32ad0269a969bbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f7400000000000000000000000000000000068dbddbfea897bc2b20b6f967aeafb0ef759082f55a180b3eda87174d0e036761f1be1c682d1a4c33f5113a6ff4e2240000000000000000000000000000000004ad9da407bd80ef365df2eb763ee35ae06074dae0eec7e2a36e57df4b3e5ac333e373cc60c1986543c0c23f3124253561ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a0000000000000000000000000000000007f506a54adb1f763d55278419d4c18ca581b28ee369f33b848be495dbcce72c76533b809d70e26dda71316cfc3a1c73000000000000000000000000000000000a6c574799ba920ac58d6cea6d0f8ae249ef5310609904965bf86fbf88269530badbeededfcaa03892f1ad6b76818ec4681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da000000000000000000000000000000001424ab1e7a30035c4ee7d5bdcd8ef87a0aac284a36259742b68a5997e7dd3f2e5065e2238f2e29a23ac5ae9bce3bedc1000000000000000000000000000000001530257b63872851431a0bf5397dff45d6c201da58d7b779318beb70a5ee2a93142e4c5c43c3d65ddc65fe2df1af18906f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000bdc58489ffec3668363be0a3e45ca2115bd5cd1745f86f1842ab82ae31b08a1f285e88dd4e0c7b94778f42d495b1f9c0000000000000000000000000000000006f4d2a07ebc588a8f9993ec6048092b6dad82c25275c922b2842253a8fe24e191cad4fab51621198147c6d1bfabeb0ba8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe6000000000000000000000000000000000096e94b43a1dae483b49c1a616c010c25b660ec3566fb7d9c295d3b43c60ba4967b3f0abcc0634de5cf3fba14169fea00000000000000000000000000000000026146a58d55ba4cef1cfbc1db6efd46400b78f508ecc0b2eede8834eeb741b68ade43ef2300fdfae18c02b86e3386768823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b,00000000000000000000000000000000143fd63e2576a606ec59d017e6582711718a542dd0a4c84566fa4312b2d2bbb2d71c1e0ab8e4e214ef2376706a20e3130000000000000000000000000000000001e97699fd2e0badc3a97f6cc55bcf729142aaa13c51f9b155e5904382ed0d94fbe1d2553d602a71ac7ff39189848a52 +00000000000000000000000000000000023a2a3e6e1c1cc57b2295c664ac26abd0f5bbecc0ed8e9850f90b04484c0cf048a76477ddde84e90cc452429e28b78e00000000000000000000000000000000194aa1d8332fd8120ed518f27fd827e3c955c2cbb2cae8d5e677f55963565dfdd232c83a38826621e8e66565f8e200b39f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c540700000000000000000000000000000000124adb0352af8f18a631cb0078ec7daf00c2186e04d3ee47882d557b0e9e7fda0e0d258393ded20288789085583a97dd00000000000000000000000000000000053f94d0889a5122b6dfb1da2d7f13a836b9be039f127a011991c360c941e5dab8cb3c7ff3d7e128e52dfeb776aeedafc8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000009dee1a168c00632903b93fcf330b28ec7dcb8d6fba65f369237ef873ecaddd60a2d1af6e5b087f07a103f096aeb5e600000000000000000000000000000000006f90048b72dc28cf4cb40585925e62275d44df95fcbf1206e2bc762a455dea5fc6b830420d49b2415d259f8d5ed3ab7e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c000000000000000000000000000000001750fc7241cee9d71d95f0023dbc4b1f41ce794e9e7822a29a84c93b9374ccf0f11f931795fb824bb5c9fdb4f9e7bd9c000000000000000000000000000000000a0e6e6c76088200a345531f589ed883203e35c8ad8413575bf961b1e8d6716829f632e72fe90947dfa46745c9ffdefdf7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe2200000000000000000000000000000000141d878adfaa6a3982cd0de93b4d64ba840a07c026ca443d6d4c2b6c36cf882e109d80df63b1626c112f9a89809788080000000000000000000000000000000005a5888d22a2f654a58d9a03c68d59cde9ab5e5356b2288033ba58fe2dbacf533e59344bdf30eed07698261d6269fc70557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000afb5e198ea80997e7cace2d5b271e3907525b6383e9d45d8a7717317655a79bec3a48800149d6bbb11a838b1338079200000000000000000000000000000000060dee81112b7e0bde192c9d382b1eb695f3a1b0b9ef7ae33b1c5ef8ad9134c23b4f473103df15a97bd6de007b828fe63313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb75000000000000000000000000000000000a9327207fa94bdffaac0a8741955968ee2278dc0fd17e99c6f4717e8b0db2ce7915b1b028c81d48380cdef05ecd5a7e0000000000000000000000000000000006c24bd6aa5f9c41bd4551afaa6baf5bab1729b7012951fd0ddaf2c6dd03ddc2030d49dc92073540503718a44260fb028faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000195d0a7f5a351dff02a805fa08b2a793d9e0c74ae95fbf2f42bfefae8aeb0deccadeb9a2dbad7285c015ce14724879ba000000000000000000000000000000000e177a86f6aebee8bad62d77703d1d34a1b708e84216437c02e0694fe722414f2ef2577c1d39a45b4cfe6c73f411b1b413994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000bc7fbda14f76ed98e78eb84033b65f286527ef76ba56dae43a094a23067e10798065674daa14f912ee13dece4f36b17000000000000000000000000000000000f69104995530de05660aa048993c4e08576488deaa177520676c9cd53034ef101fa3911e40933975aa958efbb1b931f81eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000001c3bfedaa15025440c6cd32115555fbbec439a9a2fbf706ef21e06a534af3f43baf46897158e211ea8821a5e32f932e000000000000000000000000000000000fe08cc9ff0fc601e5609ca139ae0ebe58faf8d2e2f4f3d0a1231382a15ebdc8f67271b556cc24fc5408daf3c7f74f875bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d00000000000000000000000000000000032c376b26551a064cace577ef53077cde48c284af5633152c89ee109e880b511c0b90db1b30d6d9700037489f6984af00000000000000000000000000000000059c013cde62f10f39175335b76adc5cf7330ffa75d770d908ac7e0fba6faa7b9453e8d0215f0589af872b2e648ec1d0a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d20000000000000000000000000000000009cb943167f21d9399b184f0bc0c2aca58dcf8e702614ffaf5407644ffa9eda85efa12dd23e756c5ccb5bbb25abe57e9000000000000000000000000000000000d4f59115321181962452c6f3c1e086cbfbc155f2c3019e51e73fd193e9b11ec891b2dfbd95198b318e4513c62cd51bc2c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b2000000000000000000000000000000000637e1dae04d31282c2278e087eac9ba8506d3c1349c6b98485cf32805bcad002e37d55667f1cc8e5e11f35b4d228cba000000000000000000000000000000000778c3a40e79d6288d3a93580c8f8bef7591acfac2c734018d61aea5dac020360ad4c69b4422f7320b87ff22e30d9a6a5e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf04227500000000000000000000000000000000069a54448ac1c9ee754fc28c9b671e84a67e884492f8e84e09e49cbcbcaf07fffed42820b1de61cdd0bf6314a2f4a1e20000000000000000000000000000000008f5512a1a70d3a61ee7fd6750813a29c47410b7ddd62db0426b3caf9cd7c31029638499c2e27e5922810cb9bb130723606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000078356cf80bc64c0e03da2198da5971b01341024a620ef4a455291b7a694ac3d91fe6f19299d725cdf7506e0485485da0000000000000000000000000000000015af5f875422c1e3ec6bfc5e57ed793f368799c2e068669656294be0de25eb772aebbc61358b410fa9ef79c72f309c84c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b8510000000000000000000000000000000019699fb3c6af71eae16b8ee123870888d646ac71dd31d0bb3ca365f728a6687540851c8539dee5c34f16871ca244ac6b000000000000000000000000000000000e68a278bee81ea53d4a52e84c8f534a0fb8c065bbcad9f3727917402746b4d1f611ba5064f0c3cea6f4d7fe84948dfd47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455,0000000000000000000000000000000009057b093eae9c7ab2455b447a681857d588819c94b1cdffc0e315987b095edba1ca727043667749c56591429f9173b900000000000000000000000000000000157bac2835d2f972fd1269039a7b6159b7a81a1bf4327cfbd3be8b7c779631e8beea634ffefd9771c910c612d6925384 +0000000000000000000000000000000015b6687a34084292423eb600bacc585b4e686251892b16a52d0783b1490a82f68f4eba5eefd36d147c4ec442d2eddf8b00000000000000000000000000000000151f59108d7383351b426ba8bebcf2a04976550aa2d10d5f89d5ed7c3bbd3473ebfa29c1706560866c8596f7549085cc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a00000000000000000000000000000000064e3333f828b1e54d201c043bb0f327d8c9af2cb96fbf587dcfbd55547d76784de0981a0ac86b65f4b8e45b19abc66f00000000000000000000000000000000172b76a242fb2bd9070ad26497a5c190d08472d3fbffa83dafc53d2bf612bf805691bc8f850da8c230ca0b8bd4fab818a59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000158e81d92b789696efcdbd6e3e7c16386d6e5259a247991118dfbb3674643fb97a82fe404832cdbcbb58156c9548e59000000000000000000000000000000000fa0d18e57d64db246ee52980218c3eda5fb7b1029e1c76c9894548df52f69725fb7ff090417ae05957a652029d0a37019ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c500000000000000000000000000000000027a07cd6b7cf0219b57110edf07d758ea40b1cca42270b341b2bc33c78fb9cf52acc31676811032d3f618898a0d13330000000000000000000000000000000000e1212938244e425860646cd0258b65556360e832d4f2262984f4e307023896714731a2db10004e5509a1dc25f49ab7b8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc1000000000000000000000000000000000bd589682a8510471ab1be8c348ed0d242548f0a5b85ee9eaab5af164367be21684ce2329a64a6afdc6a30ecc5bbb51b0000000000000000000000000000000008c8af9dd0e06a08f2da0ab7cdfc20100b94c04c7e6773a0351bc0e0ea503a69e5f25f250f0bbc5c7685795b279ae151edf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d0000000000000000000000000000000015865d51ca8131cd5d2b0cb11c2f06e39b7e167ddf504d5772d478d48463668c4f7dabed00cbaca414b6ba96224c95cc00000000000000000000000000000000042fee2fb44ab45d310ab00896170a638940edb2df9a0f06c077bd00d203966d49694c82cd59c378445ae0577471221c0dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd0000000000000000000000000000000017257c7d5c733cb6e9ea1bc93bda4f36b98375147a119c376996beb6f0bd030c997ac52b1556d01152991738dc640788000000000000000000000000000000001155b29f473d9abd15514a0ae1cbd0b6a4ef394aa65f4fadfd3e9551c1d8420fac28acd5337fc5d114c092bd45e9e30d109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d370000000000000000000000000000000009802bef3feb5688df77c86c74214451e4613d0260fdc5ed6e763226d3eea8a583c7dcf29eaf4c0bf16c907ceda76db9000000000000000000000000000000001447b1f7ac05cf8dce7e81de516d7303b310316f49ed5ef3f40f03db17926ff5f6656d859367805c889e07919224a6436326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a0000000000000000000000000000000000057b59f849f0237ad511a75b66a77e79ae062025e5019eb71b7b7ad94a96c2905e25afe4357506b2472f99bc71a8ca000000000000000000000000000000000f10b6ad9fdb4f346c5b4a499722e377c7649a800bb95306dd7e2ab7542e59455ea5541f2d75e7cfb1da5dd03bf037a1e005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d000000000000000000000000000000000e0725ff4149698aa757e794590ce446a1589d9a574587575ef64d6a3c935fbd78fb60c7c840d7ef42eee8d72a5ce341000000000000000000000000000000000f0478a776be354e29bf8bd2710a8529cd01da31853d04ea722225bde560f2d9da302ce4f2634c9385ffeae379324b743917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000003103b0553facf8f3cd18967a758b73111a4a9987b0ceca3a20d6657a7e365be3925f63bd09990e33e1162bbffb63278000000000000000000000000000000000998a34ba445dbefe6023e737f3e35cc6416289185a26611301721db3a24f80dd784b001a2f2a745ffc3d0da5a9e6204f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000041e13fb55bc9ed069c6d625ee08122efb0212f525b319b88197450ed1a60fc7283f61083ff263e4df10499b689498670000000000000000000000000000000010d931f006adaf737afd1ed2d1a631f519e6d1e9e22166c24830e92e3571e9f138ba901f5ac2f03192c9701067e7906b3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb442000000000000000000000000000000000bc0d401197ce816b692c5ac3ea539cc9658de56e48b4c3ac78631f3c529d4fa2a656f66098a702b4307fc56e147f962000000000000000000000000000000000d89fa2bbf3ad409a9ee7b7097662113b94fab95c98bd47a70fc2707a6aff23bf39944aad5509aba34930d7343762f6e5ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e9918800000000000000000000000000000000103cc442deeb800c14c9b3071c13d354d8c36d187e580073d150f4936ff178817dce67ee276d1633e003e66985c038cd00000000000000000000000000000000188b34fb0a4fc2408d8c70eab6df4c6c42d92ac5e43827044db526d4208acad4561c1310115448bc00feb9ee7cfdc40a877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000145220a2f8fc61b2973d219042580a0edfcbd73a6bb6feea3655dd33bde8a25e0fb841a3b038049e554315100e6724c50000000000000000000000000000000018bf41cf4ce164819a8b00e630401f0332f5caa08b03bda27c205e8fcc5ea7a3374b591a4adc581f492cb07445c8995f145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f607000000000000000000000000000000001416a39ffccdb10f65e5f06c8d7af68fbe894a0778e7270ab167ae2a5e917fb0eef1ef1b9fd45c991a45dc92a223ceaa000000000000000000000000000000000755c58a0692f8ff860430c5f75fa35366391f7e5313936e04230a1fcf1142c81b01e68fb3c888effddc0a498f264da9de4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88,000000000000000000000000000000000d73a7edcbb7163795dbb5a5b4daca733e07f6498d336a5dea1a61c9edee346f74676afe0d6d39c39caa1fa7660ab311000000000000000000000000000000000f3d573970077a17967ecc0fc5e2e7dd4b6ce910f1891f444e36761e2ee3a72fce399993405761de29f9563f74d8b1c7 +0000000000000000000000000000000008c2f928feb8b65e521b7218b029a4f54022a28a18845614b3b2de93035228c282c73ce172997e6af93a402e35158ce3000000000000000000000000000000000ca2dd2c06221058a4a7a06438f035ddbd96f6b39fe80c0029f41246a2c8a4410961555e43d9b3d5d87dceb8d0be1ef42576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e00448390000000000000000000000000000000010d919a48f588429918f1b2f05ba6e897c45b12d905615e045c1969ee8a7d9ae262551f546b7de764266d3ab656c3137000000000000000000000000000000000a40d6f247315e0440b0b8195fe5f7a7dfdb2e1be9e593f7933691fd22789ae94bcb6bfebf3b84afaef7cae9fd539b5379f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000005eaa990ca9d57885e6ee3eee10b6e2dde6e1652a743c62ebce4871ebd2d3c8e4915418aea4f4285ba375ad1923b70a200000000000000000000000000000000159919c720eefd062ba8d72fd3befd953e1272695471315ff500830c9b5b60ce5f94bd6e966828d69f7f268bb423dfd7300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f65715000000000000000000000000000000000b7244995b7819857f716288dc59eee9ba5ac7bfe010937ea0b67ee71388a3792e5b7feb6890a436db4f1b26df18b38c0000000000000000000000000000000009a0b73360bc0ca3b632c0116f21ffdaecf37e4d6c904c98d6225a08d7caadf5024ad6b457cf31b924118ea147ff10fb0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000005c2bd45375084cbc4bfebf41709a87c2a8d52256a5e4bc162501bc119394186fd624c5d3d6749708be2811da2c84c15000000000000000000000000000000001626cfa6e87e41c2f0960d6d2b8e303ff8de00c78d1e788f32cdf548a5ca00db1f3a3c082f051b4bca93788243d9b0973685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae8206000000000000000000000000000000000f736c8cab0794e3751a9e13027a8e4ded1308c23be3d75b373780eb69f130654121435c53b62a929cad39c605637ce10000000000000000000000000000000015b1edb73501789811fc09fe0156344a7a4eab1f04d1fabc24f36e2ddef7c2ccf9643699cfc654b7169d8e371c14e8c660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc2540000000000000000000000000000000018dbc414f9e1c66af803b0c228a3fe77c94c29239e529cee652099d80795c460a507538eea6c94e99b78779fc0f3f33400000000000000000000000000000000151bf39a8e3e85b9361a9472e95cafc3ae11f7d0b952714d2836b903910a8c701e0c3832b8c88592bb8507694d9109b5addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001241319f49e1bcc2d3f3eaca51d2e4c395241e2c5d8f32749a168e4af17570793fe086610432db1f93fcbbb95ced8b49000000000000000000000000000000000d90602dfcefc3860a78a8f51432a7608a7c483fcd86c0ee6a70f8ac723537825c14736240cbcf903c94d04e24e8ecc928416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae800000000000000000000000000000000024f26ba0c3295002418f7839b774cd305cecc3c2cfe20974343dafbfa6677c2fa6be5c546a1fe81458678c3548d8d6a000000000000000000000000000000000fc8ac2bf4585e8ac8454e3e424e858e1d67cb6b9a7181e26af803d8895717796f20abdfce0dfb390bbc0c7b16c70ffb78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000005f24bd878cf5832ebcf008835f12f9dfbc78b2f6e46ee384b419928aae0e754d86809d360b0afc01bd8f2f8d79a685d0000000000000000000000000000000004aafc9a20f52d1c78a17e7824062a1e7165362ff265dddd4c3458c7810a8e59104d36035c93284988eb708ba196d6a2871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce000000000000000000000000000000000375313e7ab999d174735b5290bf9ea333a62387996bf4df3dc33d9a5212ac0645789ef4153223d488aa2fbbcfe808f00000000000000000000000000000000014b792fb5bc39dbfe409356bd75b195d7023bf6f715a4102cf36ef05b52fb2284cc0739fe5ad628a760049c3624a3f2876ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000006ae2c85b2b267c86320c4cdc56b1a09e25f0f68dd208e898ac5b1c0645aca3dd8000eb544eb666f4256806123480800000000000000000000000000000000006670390bd47829d3c31cf2da8fdbbb64b92b47c78d3ab638727ea834ea6203e45a9a023060056c69c1fb567c35b671795ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c40000000000000000000000000000000011c78f1b6d0ecc5523dc089852d95dee641222c743dfd09ff2e56d008ce523762bbd9c7bec6c18e9885b7022131ad30b00000000000000000000000000000000066a1aa8af751eac5dbaf2d3ae285e0cc7a975c1787178f550a42e8ba89fa74a1b18f27716eb7ccc4f21b7957cffd8e806d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db000000000000000000000000000000000cbc0fe6b4956c0f7b9fdd36ea14a4d8284468c280605a31536636114759ece1339f06e050260bbf936b560586e7d12c000000000000000000000000000000001213bfe642bf78554d91820c362b73b7059cf20a0aefa5855f9e61a0490d165f6f61416e135473e2de54bf97cc14b8f6257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f0980000000000000000000000000000000007cac206b2d123cbe9375f5c913939b25886a51c857271a59cc2fae2e9d669af0ada833c72366f78be265ff9db049d0e0000000000000000000000000000000002db3f65b6fe7c6688f8d3741e448ac6ff322b8769277572f0198dd6ee8a99397aaeb9addd0892286a9ec6028bf9678863d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc3000000000000000000000000000000000a60331f8e8b26e97366c0e4cfea158e78ac72d63f219e1abbb670675bea008609f7154752438d9c7758b2a2e076da7b000000000000000000000000000000000d40d90f498a2855ba35f1c4bb3c5409b87062d7857bd97dd37d6e5fa53c94c78319c6b16bdcbf2610ba379d50d131e47a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c,000000000000000000000000000000000ddd3c7964bf51207485b0575afb6430cf801bae388ff78a69b8173c27431e0593584f9e755b99a5b2ed3113b3fc0082000000000000000000000000000000001735fb40978d364be3521ada17c3ae74b2a738b412906fdf425bdf13ec09e5acdf29013b03fbabe889fa261302a7ca42 +000000000000000000000000000000000c52993730e412fec923e33f3da42adadb5d87290ac4448d7df9b401e28b3c7fe7f49c7b7e4bad5412c815931416303e000000000000000000000000000000000db71c91975e41b3f12e303bd8ad15f7c9836b146073946129ba3815bc3217b6116a2a03137608cdab8807d5834eb12026a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c0000000000000000000000000000000009657686875d82eaf4f93f3e710c467ced1348b60aa47658992771195660c4b96798cfec584ace3bc64040666de71f8f000000000000000000000000000000001375f7e985d987df508321c3d0aa7e7a06cdb78117248e19c3344dc443da319f49c00ff605c057d1ecd942e8b04a5e4ef1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b6000000000000000000000000000000000da69e098b5e2c8be2ba699f20fa38cd27b9c78025e071ecb2d9fba3bc84b1e673eed79f1887fcad9bfd5b0516236a1f0000000000000000000000000000000016c4ca4d9f15716b7efe6f9e61aaad880423243b2d5ffc96804fc70f29b633dc16474f7194b5e3ca12ab5a1627da580f97fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e0000000000000000000000000000000005370f5c60fb3bc36ee208e8c185613390748452cf6191bfad06c9bcb52501873bff63892066e0afcb01a0204cbc951b0000000000000000000000000000000003c7a2a97cf7be433864541082bd04467bbb42b2ab708866c8520a6582cce5225af13acb887b6b6a8d627c90e43f6e7b640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d67000000000000000000000000000000001820666eb1abd6144df2f21f2d46096410274e346ba862aca0e62d293fc64a6fd213dca4ddc1a4e414796f59db4d6104000000000000000000000000000000000a2521c021f2fb7beb76a2ff4c7ce96cf1d05823ad8edd9b2021eb39c08e0c7caff505ea76bcff8f6afb6e8c2e81d2f68bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af4000000000000000000000000000000000e756ad1ccf0404e110a778f66ade3d10464bf8902f646f7d7ff38d15ef890bbc6d61d48122ba6edb799630a62ae084a0000000000000000000000000000000005b322f44f07d3db292c43f9ddf9ac9e44e8d16c07537bf563c98e02c2705eefc1013e627567ac2a03698268707cd84e8da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000000420b819a63b7ff7ce541661c5fa8cb107cf00ae678981b3fc1b568174ae3864a8241f1e9b656cadeeba232156e66feb00000000000000000000000000000000136fe878b886bc14fed061cd8ff1fa2d85f05bab922bf18a1f09b55c331e7cc9bf0f9860e9112c2f6242b6d1124851dbd6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000b73d3549a6b2f76741aa39ee9bc2bda8cd55759bbedaa9ecc5802310b054b01670dc803938aaea547389d7b0ceda469000000000000000000000000000000000227fc49bdf53bc4f916714ea9789b526aa53efa1eb032c4030519608c62434443847cac82a13e2dd2eb48f73473d8e1e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b100000000000000000000000000000000167cdb86301937bff18287eb0b00f5224e674953d70258065e5e8370016cac8194ec8c2f44330adaea44426aaefac7d70000000000000000000000000000000007e9128bb015f01aa725796d7b7851f9c2819a8a578bc7d3af02f7328c922c26335ae9f87756f52409c446852bc710ada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000018bd46832b101d12f95b21332b7259719c1f94c056118d877324656d285f73a4fe2cf637cc62a45647db92ba9d6c7d18000000000000000000000000000000000fe58fe2c19ee903d82da6da8713863423f10edb954606b6c56326eb8eea6c66cab63b0c816479f8107612391072c634b929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf00000000000000000000000000000000012db7fda36505d19a2c6ba5072044154f444eaaf3e12cce81ea74f28e691e4b7a730095667a71308db5e8322e80fc66a000000000000000000000000000000000fd0f22b05bf82688ac72e9ede526bf806695ff430ff3c750c2946d58ef90c778e4c5693d152e39fb1837bb10cf5f3be36999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f00000000000000000000000000000000116ba7b6faedd465fd4d1e5f42ae80c133a1d158614894ba663f87137f6108ae03b8e80bf32852ccce78b776dc224c760000000000000000000000000000000004c3702ff7fd9c74169ea76c00efb7b475d45efb12e1b5b700d47a970ed9f95f46e4c0ac66cd12fe79d62898b24b54a0fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000016254d89b0e2a8315253434d5444000d9b56b8f43d3c20d17fd26da4c8e7432d6e463b71a5b2a1a7f559a908d73abf6a000000000000000000000000000000000170c490fe3962fbfaaea1707bd28ecdd46ba29b5d8a0a35baf7fea4eaa47694e680e47e8a9f07d25078274074e232dcc36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000007a7fd283d64efef7094fbd6162da2fd56399765b559674c18d1cf6df51036007ad6c9af62bee534388ea093d3cdc3c90000000000000000000000000000000012fcf920eeec2c1728f3e620fdab1f8a0b99c6219f44b0fd19d0f7f4a15d1636fce7b4701f9c3963cff9b030c3759fb20f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e40000000000000000000000000000000009bcf0b2d49ce38914ea877832eaa3f1034cec429cd9fe0d06ef36691ac8ac6b69a712792e31afc700872d08c2e0fa48000000000000000000000000000000000f5fd9d2d4710d1cc6c13c88ae602f584a7b671df91cd544697070eff3342d80d750e15e09358125d15fbf8a1ae8df93c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba00000000000000000000000000000000053fc59a0b84028cbb3a97dc3124927d6a0eab1c58d4c6d143462bf73c0c847712bf22557a1181750146fe63e9c9668b000000000000000000000000000000000c1fa8c1539ae702bc9441085a89790a5dcac9b18925cdb1e21b95c9f7286795e8f36e7a8b4c3f4dcfa12454624911675398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc,00000000000000000000000000000000085e4232f0daeddb9e1ec8731855cf855d7dbc05d4b82d10b77a53306ee7a38ebf45bdeef1981325a61ecd754944c84d00000000000000000000000000000000061e32056ac411c3917684356a6ab3c7068f55d30ebcf8cfe446c68267923e4fb98596aded9740dc7944847a2e617fea +00000000000000000000000000000000070bcf49d6d066afa9b008fa22fd52f63b68a648bfbb5cb3eefd6feae666f3fd0b9a8447f427d5a9db52ba49854db7cc000000000000000000000000000000000947d708a02cd0a18342bc04639e8d126fc4c97acb497aa507e1c4c3912b04bdca886b75b9b9e1c5ca745acd090433119f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000001023942a16150e6497289627dbb0205a7c34afc704232ef214a6609125e90260d68b7c60600cd6f4859ddcad46c015580000000000000000000000000000000002da96265b7460ea6a8d51122bbd2442c6784d4f5bcf6d8b0b6eee6ec82e4d03c9265887f88c106792795837c02ed76e4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000d5be6f99bb9a2379d1e542ece048164fa5d14e0c6c459180717b3da46e8446e9def576635ac1124e1390196fe97f39e000000000000000000000000000000001482d8339b402e3bffe61aaa298c8bae4286f1fbfc877a66e21cfe239bbee383d701d95a6c2b8193d67df5a551bb7aba7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000001969dbab76e6a158506b9dd38c647d4a670a21458a9552d903ac686855fe021a7dcabc91e712aa252de369c9234fdb59000000000000000000000000000000000b60179a6fa6146aa6e57b097f20944c123916c6722fd7e606aa34b8da579f6c126dcbb251da7917076a83e2e4b02d32a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000e5fa0feaca8dca2a6b4a42e4a291383ec867f12b85593360f8caec45d31109373dc16d985a4702e3b5684774699e6b5000000000000000000000000000000000ae96f4a4ac0d0a6fe6aabcf902eb0765aee9ac81ad09e7e097d649b0c0165de6ad7e5ffd4ae7d8a272034f28c85ad6f97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d00000000000000000000000000000000006391a93eb14641ff145f690c626ca412af266d50b903f7465d9a9b678025a35a68bf1962bb5ffe76ea07989a7d807920000000000000000000000000000000001a90846cba7c708bf8b4bcdb3415e17e80ffc9b48820d3307362327b29eca0d1bb7fcac9c09d09fa309829679080b36efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000000c9cc9547fd49cb22986f7a1dc1da89b05f5e7c0d3cf2179f22002df9fa2c586bb3f1496c0c60f8ba36b631fe74c8fcf0000000000000000000000000000000014f8e4e8c5a12b61caf4325d1e4a8505409d722e4eb16d51be5f01f863e5dc1ca68df1b83f546d22fc116f1654a3b30e9a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d45200000000000000000000000000000000123292cef01012c3723b4713a345ea7648bdd8b8edaf76f149f1afb993f196f57b3315d86a374fb78a34486ea10e0c26000000000000000000000000000000000ee2389f669431df6697d79ba16d3e4d9bb4264c9ac146a772de6a9a8ac94760cdde7f613a4ae6592509b04b1f8233cce9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000001284787a11e0164bb197f69702d0d746975bd96a3b9221841c7193676861e97e11077b74e69f744c521ddb40689f9685000000000000000000000000000000000eb6c4c25fa1322f7c829691d938f87ba6bcce850404bab57cc3be8c3d0abcf123be8922af9967b83789fe64e2cb35f40a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d400000000000000000000000000000000069f8970964efa22facc786291d6ffe860929121595fa713f4a12f9e99d8508d7d20f7d19c51514538d1ce89d2adb78500000000000000000000000000000000122bc9405ccae4e409c1aa22b36db314a19ef6e67a572f7ea67c247085205302ad12ef7f83d3616279892ccd3c456980cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb0000000000000000000000000000000017b92fbdb00429846fb30633a2c3f383d32d0bd433d5a46e27d3c7bd6880948f89bf70b3f1639a18d308ba80b7209df00000000000000000000000000000000012374e8e7c1fdaa4ad4a2d8607afb62ce939bed23ea42a51fbac995e2c3026c2daaa338be160dbec2602a0fdaa1e9897e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac576800000000000000000000000000000000004850419631e3de2617bb6b51ef19bf14dcc9f4c7b24ae817cb239342081947f1799080cafaf51ed687b9dabb2f3581000000000000000000000000000000001108a0463d38d617d0a778bf9478ab44050ec290e442ac41e23b526089ab5aabd5819a8f08f903343e93177ce4042c82f26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac57946000000000000000000000000000000000c9ef168fadf7a056e6cceef0430f65a57b0f2c3372a5d3c533871c91cf81c40d3459cfdb5f1f66f53b2d8d50124ed15000000000000000000000000000000000483ebcdc219c4c361735aa0ea96c00c4908b9db62ed8cb565d25a7fa664829bcacc37a5608a3c3ea3a42ecf74708ee9ccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f94000000000000000000000000000000000f7232dfd8367af413dd078f9d5f47b8c76c38b3ccc4110fd59764265e6a368fd4609b52c21f8e6db2c73908d4ac0b3d0000000000000000000000000000000018433b00ede4de21cd6a1c78c5d280af98b814f0a60c625f0a8f355be43d8d99346282b6d9911c4d4074fe827b55d726fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb3400000000000000000000000000000000015b0ef81908ae275b2d5c3cbc563b8424ee0be0e1f2fb77f67749a79b7730d33028a136d133825da14448b05bda1409d000000000000000000000000000000000461c575bf65c6c5754a214c2e72d6d24df2cc228ae1c9f99d75eebf9cf48f20945a6483185337aa7c0096543dc0a527d94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000006e7385d061bafef2c731ffedd01758f153e2635c7f2bc42ea2efe29931697a1c50e4a13ac420572afc523b7316190cf0000000000000000000000000000000018ad5dac1577c9cc1e9ed30ab277dd381a6babc17e86538570abac44573a8c2439d97cbc370cd2b5d2c6509a18dbc96f122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2,00000000000000000000000000000000020da97236c2405d3f1bf4e937d8285014a190bbc59a17b7163a292a2b825f086db5d371776988d1aa2d7529a64d2a4e0000000000000000000000000000000016cf6d7b831a81d0c487bfc3380a1dc8a1bdada61426a457993f7d6c9c8fee9ee4959324bf7a2425b070aeace3cdaff6 +0000000000000000000000000000000017df783852d1f1f9c6dcf1975ed2dfacf3dc0cf942cbd7243a0cea7907ddb289f378ae59b30661d06d0702792ea9e9e2000000000000000000000000000000001717bc4192402e587400b4e7243db7e79fead2f878079c3af998b3a683a0539aad5d6c1e5da6e0a00ffbd10a2d891ff2b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb0000000000000000000000000000000018c9cc123fd18d50a7c878b31622a3727864fa61d784285b990fd116567c69dbc7ed872866db2166c7af1812157af9040000000000000000000000000000000000f38e55466a6d1cc2512c1282f74f5c0c19777365819e48606c0a86d2c6aab8938475d15a74f24db868802fe935f6107e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d310000000000000000000000000000000000816580c761a2f54c386cf60b1417d51a310bb7569a50b475f8d45f13ed6c1f11640079b5d6119270d616e77a489069d000000000000000000000000000000000d9af7b25803b611351f00daa88464e49b277de8d8fe22284a9001a13ed63ff931937d27ee19ba4000ebc212fe03a0390a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000032fbde9d988ef200df573dc99b087a8ffbec95349256989774194dabea55d970ba303657837bdcdce3b59eb54669c86000000000000000000000000000000000d65e89d8df2a189761e04d35c9f4d3a5292d1dc0d083bc9a982a131b07df6250cc969a3534808959b583923bf02125cb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a000000000000000000000000000000000ed3f47ea234f8fdc16e97eec7f4521941c37acccdfc422fefc6df9c1127ed293998945fb1bdce89ea18b9ec2b6e5175000000000000000000000000000000000a066fb6f1d69b88495bcb0f0eeaad2a41d5c6764e2dcac2ddb4ac340cda72d7b51b7901c758df15ea16e4e46c7053298902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf20628000000000000000000000000000000000ca7faa768ce5ddb6d668436e2e1692893d07afdf7466c00bc8c963b80cf0d44f6eb9a2070a7bd889ef692a81f9d76d8000000000000000000000000000000000f86fb53e3f061cbe777c7aeb63402616c428216a0c65d5d5a13cce1dc31567a4051420d54b4fc93c6bf263601046712426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000011113946d8ed7e5e545ecd0ef30de293206f3ac50e6010fa7a1cb0371f47aab2d8775c51172c4dbacb05414e65fdae10000000000000000000000000000000000022a7b8af616e4076f625f8151d748f4f49e6dbe439ec695b854544f8a498c7e261c366a4c81be5b9cad85a4eb07c36de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad000000000000000000000000000000000d863702db9f9e43ea311fdd7e0d87495ed0bbbddaedd3333108704417521b3da4b8ff0bf904710b0200453ecb2948620000000000000000000000000000000016a520d1162c7070030fea7702420de2a6e0f255c28a89bbcaf663c0d6761d201f07d86adf5ea6589e27bf844abf85a57431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb6000000000000000000000000000000001357cc987a4ee7c7bc063ec8cbaecbea0ace4b80e3af01f74d23801d5d37326ab5732222f60ad864cdc8c5dfd3edb37f000000000000000000000000000000000094fbbc2936e1730a1abeb42e58818ffe6dd97bed27a1e4fc090388d943763b055301852222503a2d2a9dedf69b3da26745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e000000000000000000000000000000000672e9a4eb4e8be8efab0595bcb7a6fdf269db71dcb585c12f9d7c1a8414b6e11d91373959d47a4c64a8890766f68671000000000000000000000000000000000203f3804abe330bca60b7bf9925a626eeae79d58ce7c71658b2fceb8cc93da9d455b6d59bb58bdc23b58238d4f01948ed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000c4e95c27fd983c31fcacb578a688c2fe055516735b5f1ea1415c5cd29592e7720eb2f548071fa3ac642b70e339757dd00000000000000000000000000000000067ab19ad1c97a773164e812771aac69fd5d199e4f60eb28c7aa5f09dd9b3adea959ab4ad47683d27394714eab4a40d281633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000003da3fcadcafc5eff08a736e4cacb1d6617c3f0850ffe33ff1648f783a4467163d1ddda082ba0b54e678b171b1f79618000000000000000000000000000000000a273fbd5fe99df4f724fb20ae0fee994823d374979ec7ff23dbe148f6977145de9a1f20eda777cbfe0fa4cf8c2a8949c6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000015b74087be4a98f4c5cb442e4e893d4d92602b1ad36d0f038f232ce25b53e19816f44122e8f5c821b40a0cb36897fef0000000000000000000000000000000017e50b1e84c7e767171edbddc397653c35b34141bd69ca7123792d6f20532f6daa5ed18615bb364b72744f96d4a730be6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000131517851372c44894bf433d5162d0da394b87a9554e9d4f6174d5712dbf69f756c5da1534eed80f8596f906f36799a100000000000000000000000000000000130a4583c7529129831ad621cd1e04a8fcfeed67ea96db4932809ac140a089e6252bcd101f17d4653555b1bdd9ea3a9b5906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000002b8d72148ed7076656128040e7dec82ecfc2d5ed05050b27361a85d0fae6d90de6dc32dbeaebac039187e3883ab238d0000000000000000000000000000000004021fbb748bdffca854bfc5de8f69a9bec478181477d3c6e41a7da2fab3100f7e2737ce958c046d6447370d47e373ad94ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000015d1c5bda34c6fafa52dd3801d94a04c53a3acbe43cdd128de3a346739df5afc6dba58d63c7cc09d18589c41d9679cff0000000000000000000000000000000014367ab7f03febf90be2279a87890527935725880ae3d418ec055004f312fa0c42c8f6fbc9c319117f6ce600d86910f16feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438,00000000000000000000000000000000130db02ba2d24a3d70439503b089e6da4cde7b5c51b1d69774b38ae0f265aeb8996e50ef077ec12199ffa3d000adbf38000000000000000000000000000000000de25ad8eb2142051fb2c97175cb4cb2984ddcab65dcfacb76cfe60f6a47083a22dac4f6e06e357a194249b7363210be +0000000000000000000000000000000016785db77cadde48a4ed0d2f8aa9f91bed9387a4766c3566217afec80b180461c8e1017297888e9c5896e509a26137b000000000000000000000000000000000025b26ffb3fa42b1a9e974eb23ada4b9329d670e38970e7abc937463e522887d777934895be0cfbf13d213b3b737a5f6cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000d90bd38049f2a8de869d8a748c9ff3120542f38fca6e8d5fbbff86baaabf0f19dbf449cf23c043dfea322d99837f7110000000000000000000000000000000000ede89c8bb8299726ec685765f10167c5b844e427d3c15da6ec2c1d97de174819d52caa96d5cc938e93dd09bbd1e0d813a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b0000000000000000000000000000000019e1e2706e878e60bf6fada47a4d4028750cb27749bcf8fff531ec75d1ff9b3a1b5e0bf19e2758899c3d8bc96a18a0540000000000000000000000000000000004b5f00109eb4832ffc9108740f0728ac059c613654a771beaaa028fef06b6cadb9dd182cc573d7ada1dcaf307a8bca4ace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b77400000000000000000000000000000000013844937de287b98db2b9631d8e36bc36ded8bbb3ebb2005ea5ab39a4844fa354b62feb7433b8fd3e72aa89ac8e4ff50000000000000000000000000000000005603183a5fb09ffcf6faabcb5042328496f8b0f83e8fe9031f9dddfefef43ee4525d1afe859177d4b9f966599005bdb8d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f5360000000000000000000000000000000008ad6b2bb88897a2e53d4fb9910b6244faaa045ef32a2fd223adbe6e0b1a5c1683dca69c0e9515dccf7e4589f1e69bff0000000000000000000000000000000013564245d53366d8468b51f88becc288b695879a70c3c753933092904b9fa5e64e39be30edf1f5e9de7eb29c4b3cdfebabeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000003b587bba9173011da620ff930befccb7b43093052636d6632fb6e9b59b8d127ffa0b7829b59873ae347eccf0e6c86c5000000000000000000000000000000000363be6dee6dd9a1271b24ff84c6557adc62738805b31714c9f7208c320aff220c02b222b96c62af96f1eb42b5299a63adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000009b403c5fe094f6ec4e4b9b7d098c3ca6fcd838e46a885506ebe8cb3d8b29849a8f3d8f9550f6d33315e69f6c1a6654a000000000000000000000000000000000714a7aee8bd6d754b9bf0292be50836e13ae886f7952c61afb1b45a02a2c378d6d22eb3eb882206a3141e43658a068c06e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f00000000000000000000000000000000115f7928ee8b8e47af2739dd70bbccbbd8c4c4f9b92868b981e407887b448745514b67164df86126a7aa53af9ea7a0ab000000000000000000000000000000000772b21e2bdc688f0b883a2ec5accd48a13ff3917d1c5ca8896faffca7e4097021ae3c348bfc2e8174db93e079979967b3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c60000000000000000000000000000000011bbf90f59d646617a6d074f5938f64232550e189c6d8105bcb67a3607e13b4668701f64933de602e5daf7b0f4f50c8300000000000000000000000000000000153ff6cb6a6dc6b6ec086e2ea8122d23e2c6abb8d59c7535fcbdfa721ba505d7e9113cfac69e1d81611c72e872071bdd29b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000001897421ca9a740a1f03d67ed31b3922d7f6067287b4addef6689303571b49bae574c343e967dc0f270aa4f91381609520000000000000000000000000000000007ab14771a4e256ec4009aa03af8caedbec4b3ab21d6499041ec58afe17175a656a7600c4bdac42c92efc9d2d21b48bb6b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e0000000000000000000000000000000005e4061b14fa76d4c02d77adc7e07881dbcb023dca9dbfd1301cb3252410d54db87816a6403d18c2ea8c18027674133600000000000000000000000000000000079d3ca06d0878a569a3984858cac6daf967bacb3fd540187e47dc2c0790d6cfffd1ae1f377c75910f0b9a17d2cde2bb3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f7897000000000000000000000000000000000e2814ce8e1011c37f6f7c38ee9543c65d0d40282793dec81b195b2d4f4b55f2d2b68416eedc6aba6e31b2234c3f08b90000000000000000000000000000000006ddeccda49ae15e5574bce201589758d7ab8baaf1348c30111e997154b6ba413c03e939e288fd95d808017387f1882947944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd000000000000000000000000000000000b78dc15a4f413ea9c8b347cd82c278cec530a28d239694d051812c4af08b5be888064f54d2fa2278ca4734549cdd41b000000000000000000000000000000000a8c5ecc1541fd79771037e247357599146fc46b852536529b841bf4b21978a85dd09c01baf8878bc2b6bd8e36bb93c030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac00000000000000000000000000000000172140620e46db480b2a9f1b7f9d0b374c0fa19145e3349906aba351686e0b75305db408fca3465fd263d06157ea471d000000000000000000000000000000000c20ddfb4502ad34e0934812913e222fd9aa201b9e10b4af688031d2202663e9c044cf3374ede037ef0c7aaa82428ccc4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd97860000000000000000000000000000000009d75caf6ffb593ff15d5635502abd9ef88675210aaf98a73bfea25888c90b63de14501459a038f07ca502b2b0eb98ea00000000000000000000000000000000091c4826870da1d2d7da43fabda1311384f24bc6d7693ab92f59cb76a06ea129911abdc22addd72181c3ecaa15dffc884d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e0000000000000000000000000000000011e977de99564d61c5e0d1654ceca0d0d63dc09a6dadf6baac980bbb97f38513459b391e40c09329d22be015fcdafa6700000000000000000000000000000000119164ddb3240c59428f11ef8c7e0469d219a591b926296f394048dd59a62a21ee2dbcca55f79df5cac6b784a2e06bc5e140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b,00000000000000000000000000000000073edf80ee80c7d1675d05f8bed28da759098f44730bcde3ca1a9a8e286ff1791fbf22bc36de06d88b20f7f1422dbe38000000000000000000000000000000000d52fe400f41b902f8801063c0f3e793bf643c027676e0a1ad3860e5455bdde58d988b929582823e5d7ee0af8987c551 +0000000000000000000000000000000004663e332c105837eebfb9ecaf524a8f7f4d651f3eeae6909824eaaa6250c9f7fc212f98c6b3d4c08c5198477f240a8300000000000000000000000000000000057144a8578437c9a10a7801fb179e417e9bbe1b85e9dd8e2208943978cdd77a8345d682ba83950e174c6cd39c9eb936a57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b1530000000000000000000000000000000017c44ab586ecd185de616da02f99ee799487b32baf2470871865baa2b2e3ca20f61e6c82d741853b71c5578199d46afb000000000000000000000000000000000c77154ab5f0ba817b30672367bf1e19f9e53a95d7fcc4565f82f604a07d5eedba2182cf1bcca2371af4d1bd09146cb98fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000002df334ee40a5aa144d3727ec6c19d8dac476c01935e7ddbfc164112e35cca9180ffdae5e56f1fb31741c327b5733d6b0000000000000000000000000000000006c1721530a765ce427eacc4e5679c42591d5d1443f0a1bca8a87dd19d6a33b731db6561c50a35511735324c5f402858b061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d6100000000000000000000000000000000016682e225b46618ff794f2da02a82e40193289c9df4ed6985b4daca3e9ce9ac6e8ce84a3fd6776119ae1a2e84f62e73000000000000000000000000000000000e383f55e44fa8528e80fdf391f2804f7b7f3367e0db07b78647e9ceeba5fb151a5b867bafb2d9c07a6a572ee71c2714355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a00000000000000000000000000000000176de8a3ee21e803ec6fd42f7f297daeaf1541c08c5c359e286ba65b78d7c31a0a630a2c73d2e886cfcb289783f30cf20000000000000000000000000000000010645db8d7d42e004c4f76bb2fe8b99a3177624ce0c1f465e67f3767bb57ca80ebadb12fba65bd021106e17adcd8553430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a77200000000000000000000000000000000006c151767d1066f9567ed86f7759a6f425a9a130a4530a2dec0913e4efe2485dd4b0105f453e90bf27cbeee5d0482af40000000000000000000000000000000019a081fb1fe2893f1919628cb8a3b332ef072971fe6ea7fbaf79d327440274a589045db5d3f06d6dc32d6bc7038c528b89a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000d19d55d1fa04f886078bba50e09ece3a394f3413745785c16d17c5936941345e42e4ac50cba055d79f2d813c69e0b20000000000000000000000000000000000ba513864132f44be3056d3d3d1fe8d10b8be954e785e3d07f816875a3454fb6d44c1a6da8c9644648b46dc7d8a0b67120b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d5620000000000000000000000000000000019cb4ac7844effff88b242db9908bd8773d91cbd8e076127493c548350bb9f8230d57a3e9c4e4b212e5686bee925d80a00000000000000000000000000000000021e94fbe9881b2f5ce2e8d777a33336fa21c24818cc1b6b699f0bf5cf1f22d7b9fe85be05d09509b88391f78eadf14e3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe000000000000000000000000000000000c6429ad7548acf43bd9e7fd9ccbb09b5b9b4474937bcca985a2d00c62cc8b72e07e725a5d447e2a92a6bb9fff0c50c100000000000000000000000000000000135ae562ac2225bdfcbed36817c8deadf892da1f8982f4bf53271320bb4e702022128dfbf9e48fc6623648878020c1a67fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000004a813c60a1988f7983f6ac644a66369153319e3bceda90fcef6fdf3e53ceb04b2c5d240cc65aaeb2530e8931f1a962b00000000000000000000000000000000141411938210cef5576dacba6d521bc46b13ce9c1f2a9aa41a0e9b56639995b69b6198f2a406ca5e471cb0a48233985ff0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000041855bc5957b8649451b7d91ef58fe8e0770b113ea3009815e60cb36c9b7ab797b4448d3747fa9b64b7fb50af906b6d00000000000000000000000000000000048f78b763a88fb7122e117ea4946a631be83b5ae456f0c77a16f3f2b546802bea7117eb27e23a5db65d616966bf2630f8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000003ca70d52cbfe2c097c17bd300f4baba1d03951c6dae613bfbbd53f68598a71d80a285af1a16365b5b82991599ae8fd0000000000000000000000000000000000ff454d717d8518415f23ced167ad7ad1ec76c437e29fef81b5604e8bc628b320fa39c192f32aa6201c2b5b4035cfddc285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d3258200000000000000000000000000000000098363ac967c6800b28c28afe92c1379574ec11e0585a0319273aaa6b92322563ad56144437569f3b9cd70ba9e7f9e030000000000000000000000000000000006e4aa226ef031c07150bb231046f36b8ced6b795b3e3f25f707435abc214f14e0c420c699f9c880e8d647ba85d467ef35bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e440000000000000000000000000000000001ced5366374fd923b3196d8f6e35900b80d01eeaa6ac41bf7d05d1fb7d47810eb8cd2d1ab793126edbe863be4c1224200000000000000000000000000000000010b27a94ae8413494e0560a10ac71554ff502be7e86cd9760b0d4ea7d1df926cf7ff1661b7902fb93ebcfd1542619caa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c800000000000000000000000000000000120ffc413256888669dce253043ace9a8c924f2996d73ef3a64d76d88dab415c870071a22b97da222361dc02d91cb25e000000000000000000000000000000000940f2259f4fadc3bfbed20ed2b80bdd86f30a846d6167661339e15548f6e57030fcd0be99496fa406a2d025077a4a4e1155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a640000000000000000000000000000000003ea26434b5bc703c242cc5e84e17be5c7777758f0b232feccef6d200db9a03f10df46cf0eead48064f8dbbccccc3369000000000000000000000000000000000649df5d665a64565079201123e954e78f07177739d082c2bd0aabddcc13f9fec6ef082a1348a369e446b82181e52aadc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045,00000000000000000000000000000000110fac33d46271daf3924995a4798b3f62c79562d3b44f736b91add9f2af779a614d4b12a9c0d7c60bcb1f104b35474c000000000000000000000000000000001592121fbb147085613d1b647cb0e4a7b895bfd4e5391b45bcb287975bbf0e5218078d3e88f8383a506550ae07c9d167 +00000000000000000000000000000000033f3c31337bc48622d27a9a3224a2acdb5c538a59b497a4a85840c81cff667ed0a0e4e3f4bb23a9ae53c1e79ea54cbb000000000000000000000000000000000cf0dc22af4530260cde26aa0eedc83a0ec3ae87d024e6907f3d22070e1054b3d4f24d5ace7218ed44763af6ec3f25ee32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000935dce5baf85335575af5a6801b36647727c3e28f224cf25227bfaa52fd646d6fdf0f24466631a93506a58b5f2df9b70000000000000000000000000000000007e032c51e2d9aa53a3120e5777a14963af8a9fc65dadf5da779c5ade6aa043ff496cf4f33e2672dc5e10c4a06dad86a6583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e86300000000000000000000000000000000178e7b4d05c4b7762b474649b38a5ce999c67ea677fee77115ce7e55207d87a82b6d05516ab41c2bac294fc382c0e12400000000000000000000000000000000126e5aef1a9729c73278b805cf102934239d1f706bb3fc3a81f3726feb4b3d2fd8de69fff2f20d5e5217edabb645e8df5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000014760b82d3b4949c67d38c6d9172e12bacd52ed49f442d781aeccb7c0444407629e3b7d5d5e1be996940966785940e46000000000000000000000000000000000aa2d6391e40e50ab9ece25786a42e8dc657e9112683279b143be5665bca43746244c27352d3600dc62c2c1c7776924339c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b0000000000000000000000000000000003f7c65aeca3fe6e67c91e1f284be35149276a9d9c0c1907010d8ce26d5c88f2a68b632530a31e41388cfc97529485f40000000000000000000000000000000012b9322902ed50ae50e3bb3e07eddec3245df27f193fa88a7685795990a5fecfa4be4b5bf8b0702897cfa369d614eb942196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c5650000000000000000000000000000000013995f89bc17b99384e389c9a768fa4bc37526606966a74a370c9f964cd9d3a7dff9d6be2319d2c8c9d5ac1b6f5140b20000000000000000000000000000000017b32d8800e21a4553a1a15ddbee029788f58023164e65b25086e0dbe2ee0c16e519dcc4753c322b50c24edc305cc26d8df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000017601971d5328ca817108dc9899c9c3b88aeca2ac5c03f70662c9bf6bf3e06d25fa4b7150e0838c21c9b089c7102a17700000000000000000000000000000000198db85ed42c61e1137fa50c8b2a3ad2eca4e9dfde3553b8ff7ee3aa6389d73c80d500c883e52be5cb9fe8f828bba84f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef0000000000000000000000000000000000c830262d029435b1b857e7e3cd118e8a6825e3e413f5a5f67b37da686f442577c0beca3e86c13ef6924472305ab54b10000000000000000000000000000000003d35dcd36ea7352d453041e821dea655422ae01a50731698af020234e3ddd38140c24ba2af296a964f4f5896bc0af8c8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000009830bb211c58fdb25fb97a4ba226ab03516911e7b7d98f25b94c827774592b5d5c56edfe3c3040454def1429f81c4fb0000000000000000000000000000000003f34873ad16852f435cec18f977db00f786b7860c580ae0dcff8f03a8a1edbb417f01e0dbeaf035b6f60b733f38a564cd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e50667000000000000000000000000000000001718cef19fe02a179385ba031f23d28e20e7f57ee82db31e632cc3530d17291e54e8a01564963835c724056c53f9853b0000000000000000000000000000000016c44ed6c85628341789e80e1d95a10399b6ac126319bba3c66bdfe6a40f2b06b721a0867c30be1356656cd36e6370aa2a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000011267a6e9adc4b547ea0f42ff6cc9b35a40c3cdfd7ea3c4169fe1efdf533341969cc591f26fe9a48a44e544c515339310000000000000000000000000000000013d878f761efaacf28677577c93d825336698772044266d469b934332412bde9ad5deeee4c1f534a9fd89e799584d3394a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee280000000000000000000000000000000004ca71357762ac2e9bc1f53919ee2c19d071fbd3918f5948f32ecc78be1e65672d12afb4d4a8df41a038bd5448bb0a04000000000000000000000000000000000b80b54ce782afbdad1cfbd57a852f629c0452346d5b898062a8abf12c73bf79296564d3fdb867ddd81156697a00f03ba62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000009cc641fda19b0e33065a35e74a7ac28ca1bd3bb8a7fd350244ad0cd5dc89d91e7b2865e78ba24e112589e298e6c5cb40000000000000000000000000000000009c3ce4324dacb1e2ca82f4ce6a7ed1292f204f4f7b2c5e0086843546c5c00d16be4e7bd9c979ecd3af590b40b0d70a4a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a60000000000000000000000000000000016ed972bad2d24d80332c4aeb1dc012ae4fc30a11597df1ca73114945c20e337d1c424e636d403141c737103a4dc02470000000000000000000000000000000009ab2d22c0161247a3c4eee341027a97009ea95bfd45fd186e15feaaabcfc09fd39dfeddb2d3631b943958620555fed81f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b40000000000000000000000000000000001721e0fe2ebc0be63df10f4b9db3faa5c5fc3ada0bfea176c4fcd1cbb696779c03602cbcc1da3917dfc09af72fa3cee200000000000000000000000000000000192e3e3b5b9b087aba72b852319c200451a4976a4e7cd817eec04c007c8a2f800fe0bf7834d22a21c1989ad8c6ef73973bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000159c5a01e76ee666e8e22aafc77e27705a633bd3d1dbaca92117e4b80f917a3bfe80b36d3fc7721ed2fb8434558c780000000000000000000000000000000000c8c356e19c759e1eaacab45b4fd2e0b42dadf6aa2ee8c051b8ef4de0c4e583fadfd86ff6bbfca1eed42a29afa470c8c1b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d,0000000000000000000000000000000010f2b9ae629ef12f213e6408c94601482c4c3cd0ee33e3418c86f0b8092d1a1ab7d2600140f6e1231297c3bee4a48a9400000000000000000000000000000000018446e6fc72ffb3c6c25d6aee2d9a8bfafec7b4f63dd3f98fde09c088876c7f4d30cc0ee31985526712228766ad91d9 +000000000000000000000000000000000de77471af6d857548f26f2ccea9c33f50db361c59b097fa481887b5a5deb4fcbaa25ec1008b131fedd3711d4d3ba029000000000000000000000000000000001037ee7b2005032974767d672e14be86177621db0ad5d7df5faa966b0e7db6319ead334358142feb370f60cec698f3d1bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc0000000000000000000000000000000000ae57db1c0d1575c49f8b049667e1c8ba0ca863fa56ee58a34ac1ae780c92418ec50294b666a0f99e0efcb2686a4d27000000000000000000000000000000000aa08900fcc4f9b551229b7a8a59aa9b337100c68703ef60597f6acaaa7c1ce910e643549dd0c328a7fa17e44b68de1cf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda994380000000000000000000000000000000016cf186f3a0ee77c7e990ec0784d99510320114793fd7a672d5f739e9b0f1186faaa9d5914860d66173696c603173b3000000000000000000000000000000000124f5c20e988b460c261d274251841cadf5c99a12c9ae8b4b3baa7fea8b592192dac3506860b15289df704cdba1dfdbfe45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a67000000000000000000000000000000000166434c1551befa708de9201c02cfe18020d18ed881ac4d154f5e560995f302b57b1694740f76232307ec0ff729b2709000000000000000000000000000000000a961fa3c19068590b4c252c0429414ef393ee071b02a4ef15f6a5c722a73d145c8e058ebe1997058b38ce7961860da954868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001618c78e4962162f253729c4cbe326e7ea7dfd6d5cdac1b17353135485d434fe7c4d857df673793e9d12ee65dcad4bb50000000000000000000000000000000016921790d30423d878255c44966b316f9c29dde6695d66a97139fbc6fba9c4df9e291c308effc424e5e2134680846fc37068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb00000000000000000000000000000000128a8a8584726a4aa2cab71853f843f49efa79071a8ed0a6ed2c7913fbb85e254184d457163fe647d0ad719d04e6857100000000000000000000000000000000158d36271e87ac2879fdd3f1fe8ff306126adb340ed93406951e372a7f7f3deb1c347ccf598f2e007d92f502038bd4960042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e00000000000000000000000000000000047e63ded02c49b7126a1023f1ed4a0af20c2d5e95718f474e4171c0fa888d7fb53b6a2bfcd47893aef6657f31071167000000000000000000000000000000001404e16f51ea45098d5bfa00ece3df841a3a6630bff2b02a8063ff9af5c3f149e504f04e1fc9d9bf35324569e8b2e1730a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da000000000000000000000000000000001866eb045ddc4e29fa612a31a34355ecaaa8482cd0885bbfbc5cc0b3870a86a2b4c3f15da23638dc03619cae6b721f1800000000000000000000000000000000086aeb6a413db889a86bb3fe036486b4e26dd614aabf575f8d63614a300df8a528c9f6d47d59daad59d840f591063b22ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000001765c386f85f7282251b6054f03a3941d44f9a8ea2814a49f75519f9fc985133937e2c9e06b59441a6d9a95c806d6b10000000000000000000000000000000011db74b6bd144f9a0d48185a3e9f4adbc79131764b6e82f11823f1bec92245a55d82e6d949f3378ea6605ec84f0613285f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000000bc9a01aee9eb527491f7334959b0f4275492afa38044f0e6dd222a3704f440b5ae2120e8e2798179634c65f3d674413000000000000000000000000000000000ff19f94b6802a4788c4fd84f66b9be03fb1417544d56d0e473caae0f9b9124c622e6298624fa1d53886fb5ba8b470fdcc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed2165000000000000000000000000000000000df5ceaa6ca501d1869b51f035c19c0f3f9db39c739f882a380930cbde7737790b25a2c01e65ed477755c2beb16e97f300000000000000000000000000000000148458f4ff4fcf8559b9f8a2ee4e486febff21d91fe4bc3c77988007cf700186894f1c1fa18ee3c4595a462712750d3097b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba591672700000000000000000000000000000000029b16c9578701febf6662da833091deee23e647a15f16895fc057a37c153fa738efb1742c4bfcf27eda953a07aa01c3000000000000000000000000000000000196d74cfb1e6472b7ab67a664a7c46ad0377c2b465e12d94b035b4b79c7e358475339e09690557e4b280cc84391eb84752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000e005ebdde060ed0233d1b1d6344b8d21f8cc1ceb6d4fcca389303e1c44c5964a4521dac8ce225e2e4909c4b2a47f622000000000000000000000000000000000fb3185aca9683a81d41a17b3a6048e75549d589354d4652756a4663cb25b9fbca1bcb9158e2ed73765d03be4e2b570f2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee300000000000000000000000000000000004df18eeff223e3a255e6652c3d14a6dad17c76e0597b43a6679a85f78d4bbaac1e2fc0ccf6a89149dc18045169345860000000000000000000000000000000019d60ee8b23308fdcfbb26ed30fda1dda5c6841b46fcd902e6c34dd268fdb1426e215d21bf650a340b284d5c7516efd3f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db44580000000000000000000000000000000006b084e91066f299e44a0c37cf65c30009006ddda34d4151b0c18a5545d67f2bc76df0bf9a78fd2b771795c8d041655d000000000000000000000000000000000262ba1d9dbb009f779e2a584ed313d78e4ac69a811e071c10e21027138234a32deceab16a33767fdc4a78062cd23ec71c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc0000000000000000000000000000000009aafc73979c000236c08e089828880f54645b5ff4c1dcfea0ff41ffe8e3fce8ba0dbcebf0d4205bb6616a737b6d3542000000000000000000000000000000001399a2072604d50f92ee186924ce32c4e887803dc258b7495aa2f3d2187571045db7f360d2614b198f83bc8024b06559b0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4,000000000000000000000000000000000365ffdbc48aabd8f0e786634b9a853cb8312bf295543bd280c1a0a9f7d0f8ba95b3aebe31987ffab1f69a504edeac2400000000000000000000000000000000150af5ab7e9b1bc60cda3ceeada36abf9bb43f1182659d8d72281c1f1cdba73fe7d6e52abaa7506b89ef43f092f25bba +00000000000000000000000000000000012a651f467e9a1c1cc99c82e16cab2cef53b77268d968dcc73c5008e103d2e4d19aef4cdffa24b9474fcb393a48d6a70000000000000000000000000000000005d202cf9bc8c0124c0f817465eee7d4b1219071cfde50ce2cf8951efcc21fa19c762a1a8630eb7b8dd90cd03b8bbb0484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd00000000000000000000000000000000060650b71c97950ce5cd6b6bfdad46d66df454c5aae1ea313a70e7fc841e06f64a31edaaced17d8de56f1ee75f5263540000000000000000000000000000000018a211f44acc52e92ab5eb1ce304d80532fd4dacce60370dc62d9ffdebbf749689620798429b5ad1d8293c1967a43c12bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd10000000000000000000000000000000000f51ac340d512becf5d7a515111f63123e9bc940242ba42be9f464b89847a8cca9d93360851e3d047de4ee667a6baf0000000000000000000000000000000000dd7e71b516b3752c5be5ee5f3908c17e3e019b46422f24659596a42e569ba9e8711b1e8f8329cfbb990942f258cce103717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000015d542246cc0b46bbf5571c3173abfcf10ba447e5ec962b5f712ea7de3974c2873df1979c9d6432bc88d02588a3730f00000000000000000000000000000000005e1611597c12a4c7aaa25bd9ab1b6d30c58bd1fce3d87d66a03f25d6ed110c84c3e902ff5475795b5159126debf6cb522788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d9400000000000000000000000000000000018f565b38ce775e6b40581f757935efca255311b872fea3bfafa0662620ad5a02a7e8ce48c17daf45668c95ab0487c4e0000000000000000000000000000000010686971b402783c1e7d60126cf484fd01b871944179adc4b28de5d72e5b8823b48d382a8b69f6b4681c74961ca2a3843c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd3840000000000000000000000000000000008d42e31cb4c514e450f56488208444481db0beb5807c6f1c2d82ee09c9413cd6726dccd72e0b8ab6f6ce6492921b14f0000000000000000000000000000000012143ca6dcc3bc9edb5b10c3a47a5130e393986dc5e83d1eb61d9b193ca28193101eadf00916a3cdcf7b6c1369b17038ccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a00000000000000000000000000000000101eb8b48df43c3e01c1508aa9d3dbfe168e7458cef2ff61c15d5b4e8dd11be6b9a76966c01682fb07368f22362f355a0000000000000000000000000000000000babbb820a5a8e0bbbae1e2455d54b97f6771ff914fe33a007734d5072a993df31c6a2726c8b03a8c2dcf48a73959a8ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f0000000000000000000000000000000002fe8c461de25f5e6c5a082fbc4ecab5a37dbba9255ebaa0b5d245735edd27550968c2558ed24f7bee99092228e37c8a0000000000000000000000000000000012513b2fb62725aaf948403c13f11a6d7461c70cce3e4f912c8d2cc9f2a8676d9bb37face3770e7c0121bad6af6302d121cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c91000000000000000000000000000000000175c93838001f4c67a3e0e5dd7eded26a8818b2e492eab2e0e6f8b421e3d3611561c8b933010a3c5ff96128631f4e88700000000000000000000000000000000136292092a366a73a5609cb1e7fa403c59825e99c8c91a37b289ed779c4a3db71370a4bda2cf8509cc9d4b4731b4f52d2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a05000000000000000000000000000000000d03e1d6dc4bf59262fe3bc3e163565110b751c534e57c621b4be59bac28d6e8bb379cd4afa3740797dadf32194fde310000000000000000000000000000000014ee46a0cf13e795c8a46399ae63e1b812f237eea725539265e13d3ad1a663374dd566df450fc1191512ba978736e5b779cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000003cefffd8fa01842c36dd9fe1c57efef3278eebe5d1020582c3d13ced75d24177127da37eb59e9b46b4a0a19421a5aef0000000000000000000000000000000016c258ffb2edb299fcc04ad309ee5d8a8f186db5f3af8011d42b22b23687c2e814e2a8d366f3cc61d7c89bd9619523b31973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e400000000000000000000000000000000097b6535843436f879ce659b6ac9563d81ac0262b9a861bbb367bf8244a35a5de51f3060d05cb2174cb41c8c3dbd8dfb0000000000000000000000000000000012dc9607e0ebf73e3577ba1ab39437b03215e366cf1ecffeae4ad4c7919a63f62e45103db65de4c9e3281d7604b07f24a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000004479ec5d5ba2f1c661df8e4f85320d0e754372e0c463098b0ad7477f7373f309c674dfd31c7f08cccbbf4bbd17c23d7000000000000000000000000000000000470cabd9f5c4bb8b1a370888d8f0f486387a89efb92912072fb0907a1e64f3327e9beaddeaff44c502414632243d6fb99ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b00000000000000000000000000000000038101da3c35dff20a878300bcf69e393b77873a971838581daa9d096b00bd6fec3dceca882a02d397a90c816fb415a4000000000000000000000000000000001184246344c03be6103acd745b3ed37d8f67ebf0caecb00cb2528e0da9aa3f352a4677dd6b832c042d6e1235da7521fbfdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e90000000000000000000000000000000000cf99121ecf9b02cbd006348b16f9d80f64ae3c946c4802ec6bc056bf6e95e01b80cf3fd10ab1d30260a402b7c46f880000000000000000000000000000000015f35fe1ec8c258095394ab2b021d63ce54ed4bfe14cc5666f5ea4d5a0461d535b8bce3263913c1b4e6db6996cdc037d786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e83f4b1d3eb8d45ec0fd9a4ef001e5bfdcfb9c99a6d1dd4b4e8043b4d11f5c6fd65296a33c7fd26a4e30dbbe1869090000000000000000000000000000000001197b11d6747280b37769946549ad9d4a1ff1006ac726d7cd322cdb4e3cf86906c7ed371e770fd95ab4fbaa1b7b514d985d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6,0000000000000000000000000000000012f496f031f5c1b594256e272520ab98f3733fc9c481e7ec8de8ba70f493065eb25b681a3959994d37aec979c22c6c3b00000000000000000000000000000000015dbaf471eeef9307d8dccceaee179d8c9072b052af66fbf049ad1d346e08bb555238a763e903541fc72d9edc30ec30 +000000000000000000000000000000000b632afb8deb955e64fb4ff5aac396152e23b11a3f326df0d77b3ec078934cfd5e486244aebb44cbd1599f594991a26d000000000000000000000000000000000519f9de5a5b1623e4524be68b5ba0f997addb4da78adcc9c3d5910009a261fdf8b0efbb6e2a085e74112ac4e2106ef319582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d370000000000000000000000000000000005456d9312825dcfe5501b2c38aa610a767bd38f46cdc8acd92f0c8206a9c2f9b8f65c8baedffdec5e69f03fd3adc4c40000000000000000000000000000000009b2dab21ba4e4b4c284a623994b92ed5fff0fc198bd154fcfac9abe5f05b830066b44894ac6f92bb2f61bc88a7867a8ac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d10000000000000000000000000000000009f55a987011dcfc796df284c7bd758c3024d4f09edb3884dc087de26fc1df0f71067d44fde07fab9334971b4a0bace000000000000000000000000000000000003a4ee3e9ac2632cc81cbd4ba397d44f738ee390a4af6ecd65079f412bdd8c4a37d5413d0d9a7dbeda8a1267d6d843b069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000173c7db310b54a4a720074dee01dc0e5f84b606c9c3ea0962bd4610b569f478d7a5221feaa944054cf7395e578d730d8000000000000000000000000000000001697f0e16c49b223dec9e0fa429e68dbaf96b004a561aa3e37158064ceb9232c1cd21156c053fb89ddb230deaa7f8336be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c000000000000000000000000000000000ab7f085b711171f999d0c4a46cc7c8cd8a429f6bd90d1b860c01066bd0d193f1c1441ae5aa97d690569807749ed69e1000000000000000000000000000000000824841eab90d56a1810c129b8f27d0068fbb7e3536d6e56cdfdd9eb553e283c5d0ab1c418869e886fafce53697520859b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000bbc8c5b5e4373e76457fa45acfd3f1151735457b0fae06e1d3e6e5dfeb35815aed44bbe6395039481ce02d2aa2c502900000000000000000000000000000000089ac22ebc582bb71a60c88638747e2243096e8d193fa1863089698fbf6805128f9e32636d6f954ff03bfb6c5bcb0060d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f00000000000000000000000000000000001fea43c3029447965718c8e76100875acc8fb4da66f7a4f7fc5260de3844aa9e9a89ae4d9baa11c118b9f851fd63de000000000000000000000000000000000844aecf4a3ebfd8b711dfa9efaf1a57d635f46fb980903e362d4ad55d48c4289a3fb1f439e6b7d8f88cc51867d6b462cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c4000000000000000000000000000000001384e33086ebe795cde3c951de9b48f3f0fa2f627524cf0c4e3691599b62d4611c6a84897298c287d162825c3f153a75000000000000000000000000000000000a04af7cc41c2d3663444c8aaabeaf70dd146dec114458b3d1dbc95cee99ba89a4c5a38f2974622292e3236fe2aede6d17f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b400000000000000000000000000000000158820954aaf8e6387cc0e8e528723e0875f5f719a46ae5cd9d967674815a2d9679aea9b5736f882d37e2dd26b7db17f00000000000000000000000000000000058cb933f8dbac61a22477cdb3f52c9e3de6f060dd51aada35b6f8480a53e8eec8f82800e89ccaa2d2eb1dfb4352f16561ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb50000000000000000000000000000000000a7c9c1bf574503a884ecde5e921da80b299c4efe674a2d5c841e6036adaf7c1156393116c2c0b9827978d43f1e3e440000000000000000000000000000000005cf22e56bf4a46504ecedb072fe5e18096f9da550065612a1d00cf79c65384dea1bf59cb7c52de905a04f1886f36c8a0fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be0000000000000000000000000000000018e344838e2efd9363911898f27882f67454dc3b1bbc71f1d99e787bbd6a1ec9744876156ed8db2ccd826f2b4fa784050000000000000000000000000000000005528854a8568ec6491c79aae1df15d965cde683c9ea400b470105117f2bf3b41d2f958a8dea5f866a55e60fd06c1f07d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc400000000000000000000000000000000047dfb6a6125ff02e12c4a9d88ebcdf8a4375367e1473f5a0d99152bf0a4055138aa9a83d98d7f74d9fb8888f643cac00000000000000000000000000000000019d0bf5162ca55d8113a97cc3255d090c6924362e6e05083fc323dafc3b12e898cd600d2730acf8cf5cdfd4420962881c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e132de353cb09b69ab369c616718b9cf492cdb9d3002593319a6e7b61c7d90f94808b75d8c7e3b9d7a811d01baa47a1000000000000000000000000000000000d636abffa063379e2084cfc09da5ee04d40d8e74ba0247a01be414cce820024766195520f1d2eaa90fe254e12a4d86026cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f0000000000000000000000000000000013f2367ff71430cb541557f79c5ae8a0d9053d82341d83037c1f73a52585255b205706227de4e87d6ea2ca602483d2170000000000000000000000000000000011f3f4e882de30b40bc160e69fc2bf4f7c588cc83bb9dce3467accec7c47714e2b326be001a36c42ba39c7f56b72d6fc699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000014bcf3f26683234584d79b436cc608462f1e2c20b5ecc5019988d8e30137859a4b6d0e1135dd5bbea0781b8ed3f0653700000000000000000000000000000000090ef29bf63ca97ae8388588227e1d1a0653c43b16a35a63f2ab4f0b11fd8005d9a85d30a7406491d983f347e4dfb9f140e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf9523000000000000000000000000000000001817fac61301ea6a43d7968b22616b836ecd1f20e5883e9b475c18353b066f93bd68a8274d0b6ea4480d8e314766dff7000000000000000000000000000000000c52fc676604061338bf0712fc1606dd09783a1f9a5250e3417056e3c39e59a28c7707d5225808414279ab61e49b6081062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d,0000000000000000000000000000000018410462829b3a72024468ddcbc42d59a99a70296024654f99b591ce016304537c525513defb655417ba3c0f5e614aa8000000000000000000000000000000001416a19f73407c262f5e464021eeae1d1f10c3ae5e45f132a2f402a75cfbe409651d3795e482b15d29037e2f7105255b +0000000000000000000000000000000018e6f25220e4b4011a0291424b4062930f5df45eaf1581d9591560fd77e630411e0abd57f9973d4542741de5cf3132e7000000000000000000000000000000000b31f903e7fc36327e404973b90efe5a5d2249770170ea1e58839e19d8aee99743be012b6e8a3fa73efc6bdc08be372f764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff20000000000000000000000000000000005dc07fa620d476d8f64358c920a401f8b08abf739befe1c266fb307b959f37542140e398c33b082d09f9f53cedf6f810000000000000000000000000000000019d8e51a28c936b5037424a7ffa8cae75496131eeb2b2d5034e4e882c1c91f6bbabc9ce4fb2fe4be3da4eba46326a3603280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000006f5f80dcfe8be87d057e2162788f7599e55b69ee8c6bb6a47c505aa324ddb5ffddacfcff35cef3dee6264ef73d6a353000000000000000000000000000000001056081108195d4d27af7332215c0b444c9f63c7574eefa81046e1d064825492e2dfc5bf2ab5847a37e6b253d9dda9fdd4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb111770000000000000000000000000000000008db431907692896f9e6e254a6eac1a0ba5f9cb84563da69c3601aff1370b7a5a98edf5a5fbab06abfb4496c777bd83f0000000000000000000000000000000018a3bc407fc42236c4429f241fa760c6513614653e8b02835480dbe1152763bc6a1a7fe076e8bb44ddc04322cc906e1ac66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000cf32da94af97001664607c7840631a8df02a008fe262c6dc649a3eff34a42dcb98884212bf3e979629c98cbe5fc457f0000000000000000000000000000000019b3b4d82326ec1aaa3de3b2f8e329ac0243d3f6bf9356886be4033aadd0398a5c58c68510de29f92a7ca910d851da244b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b845000000000000000000000000000000001504d90c52af16b5f88357c87d4be7c329855ccad6f6633af0fcf4341fae54aa4b1ddc1aa22fe1ac12e9d850a05a9ffa0000000000000000000000000000000012ea642b96304316451dcece5a6bb324d197e31f56ef3f1a17c973742322d08f443b7cd156787f8291b52c0a6f78b4b1708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a000000000000000000000000000000000fc61e9589a2dd7f6dfd613225d80a70ceb977bdb518b5a16e415f887eb73fe9fa5c9130d5fc6deb4ad153c5de0907d6000000000000000000000000000000000a0fd7de87139581e9b1ab707e25c186640db92875a7822d61d8c476c40ea07bff000cbfe6975076434d0b703695740685ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000188901f19a776ebd2ddad60209f4545ca9b0a038b0b3c67b6f5e35d61f8cc2a297d51450663c4af182079d3ab6b01d2000000000000000000000000000000000151b9eaaa281acd803abd71ee4098b4ff6535e5081a33cc68ecca54eb9f1a8f94f3b1b21440f33b8648ec456dc1cf7f3fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee314780000000000000000000000000000000008ce8bd24052a8e1472bb64cc215974e20bb16d502b3a8113cd6e3e9a2bb7c3fccd45ff711518e8430221f40859374ba000000000000000000000000000000000aac2e8db9123be3e82905a0fe780daf4a841f6f961428b9b431c3ba2ac31e8c06118402bfc7fd15fbe3ada0ec8bbb2af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf2000000000000000000000000000000000017c580f501a1c4823483ae718371432a8a69e16e42dc0b15bb8e01729b6707ec20b898e3835bba40d7e8802d9438281000000000000000000000000000000000bcc167264fb9d6c27272c2280d8e89f9655ac7e6408694a3a4ca6fd0b46d1d7e3cf608bc2ac343806c5de42ae7a99e80ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000017ae89082d6f531bb7905068a9c00017ba8ac8867c6e467fcd3e88e9229ba5b21ff4d0a5ce937b75b3d5dfbbe35f2e7000000000000000000000000000000000005bda8d641b782ed51c416d0ebb1cc7c8f623d49b741a7cb93b3514e71d5b9102ba2e6c768661686c2af2acedf466e4dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d099539000000000000000000000000000000000150ed2c2b2d1b0b87badd0dda44325000a6fe98d335e03f0d4d147b20d4738e1e0f0ae0ddb2783bef283684e631ff45000000000000000000000000000000000ec1fa174f3f42cdb0fb67a520da161d9a9d1e53a5b0735738580fa3e80550c95cc3a1cf67fed67dc2eee1597e469fe0e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f900000000000000000000000000000000111f184636052719c6df1541c100d5a21d573370fa7afd18f5ddd1d86842169eeb02c494b33f2bb2f54278530729bfbe0000000000000000000000000000000016be03c9764aa34c898dcaacabd1493610f55efd36ca0b35eb48e89c7968e7a720d545b18fdb95954e01596856d42975cc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000114fa84ccbe9552a2ce2368f1778a1fd3c67303d8036fe4ba171ba9f2f6039aec1a59fea1b8efae88c01bb50e53950440000000000000000000000000000000017a51bf70c41571f36d003c0715238b6c8fd64185f616cd9076b730ad16caf364a75fe68de246249a42cfe013606874709a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce000000000000000000000000000000001450fe1500a6fa9d966a0c905167a414d59a3f8a064089f09db047241e9abc31d9e41ef73558eed741541414731f838a0000000000000000000000000000000017e61d4092537ec48683f86b72123637df25a5fd926e5703f993678a798dbe635ea29303f8b4d9ac76231a71cf515a70d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000000e72f0c855fce66335533c05ae30031cbde78ef07571eb1b645fa3ac5f3a7d76a4d60cf078145617c5a7ccb16266bbee0000000000000000000000000000000005b3981900432b193985f28a88a72ca9958b4628e5ff9d2cf8b0b23184e2bd433d495636de3d56711f207719fdd3fd2f9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12,00000000000000000000000000000000110feb31a1c40d570d7281ed9f0c0ac8418f4a7aeb6be3221b130945becc15bb353ea63623ec7dba2844d3f527c167e6000000000000000000000000000000000d76c7aed58945a7fe52f37eec3be7cbd4438645a649a04859a487e9e2d4c82bfc76f7ba990f825302861d82a748c8f2 +00000000000000000000000000000000030c6580a3dc73be106748d070b24d9231c382df143fb4bb8ad45e4723b40f90724b7e54510da1b2bee523a29aeb58100000000000000000000000000000000010cb3562fa1b0a3778393412994e46028367ed52dd62a1d446fa02b50acd48a784ab49141778bee5036b7d3a95c9ec217b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d000000000000000000000000000000001972db503f6d70a0b247eeac7fef277098604e54465309967b68d24ec1cece802d8c4b699eabb72e03736902d41fd5b60000000000000000000000000000000007f30233f9043927a629b11e7da48f895fce86b31911ff5c511c7b50642c296d37a3078e2e12f1adfe668731d0e6810ec6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b4800000000000000000000000000000000011ab9fd98e42539382c85bf76b563478fae8cca90ba1beb0be56b405da8326e6f1348b94eba61fa29c78645f8eb96f8b000000000000000000000000000000000f30617240632d129ceb69de1d69a23c9bdf950819608deac0600d1d1fd730a3a6d22dcfd635b25154b5ac7e22b20c70410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d0000000000000000000000000000000007556b86cbfa9f186f38fb1a8adce4c08f93f874bcb36ba61df5750c7927cec8896bf831c0150c249067ddada2e914bf0000000000000000000000000000000016ecf045f13c78de8aa18c2ddd1714bfc532ba8ff5b7851b58240cfede20f032067e943486df628995b8f3845289eb02d97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b30000000000000000000000000000000011379452e627dbed2ef1c74eb917b95b3933b8fad8295235cdbd6a4394d9b75cd3598c930d48c2d4abbf1558c65e97490000000000000000000000000000000005e7044829ae3f9b073e4a2237de96b0a1bbec3a30dc39c839573eff77321b1e0a49d555f0e31b8aa096f83f5945026bbdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb360000000000000000000000000000000015f8f0f22c1553ca663ce7e9ac00514eb53443f6c4869f985dceb118ee60a88a4826e9dc7fdbf61e77cbc93768fbfde0000000000000000000000000000000001646ecc89754ac57d7d6fe9b871692d65057f23d397a410bcb07ef3df0a3c3fad9eca515f0d0dcf0610edbdaf4cdb5d743da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000003a18dcef4939e154aa790b0ce8265f27cfff48d5fec149d91307759eaddf601c788da6ed8124764bad940f117751b0e000000000000000000000000000000001813f4650490f3839fdc9f96ef744ea93a9fd86f8a43d767259c2e0abafe308fec2bc6b9d62c1dd7b5ab1aebc19586e93c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b68058930000000000000000000000000000000006490f351e78a40c0cdb827aed3869db293c7d654b43d69ad1c9b3b536b1fbac67d50a835878171974669a30ae9ad1bd00000000000000000000000000000000041816bf846528e23eb129689a87c2325f1b8edf237c530eaf578a908fa0a2604baa19d6e0b4a5801280c27285896d5a51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000064be79c5d382c6dab72bbf28defddf14cc7cdbb23eced6bd93abed078175668d4dd66d0b3abc6384165d26bd86680f9000000000000000000000000000000000fa4c8be5d20d16bee7bd5bacc0b0086875a14a119b4888bc408850c0a099603fe3f79d334e45bdc9130132ea15a180f6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b000000000000000000000000000000000182f23242108b022ecc1d156a97f1a5fea2cc2e059dcc82273212f37c312ab77886c1adc370bdcc6ee05cfec957db970000000000000000000000000000000014ceefb3ca54bfde172e0455d34f1f462208df69328782b7961ade821ab91e7b3ed5426b4065fad10cc8fc88c90d8e87f4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb10000000000000000000000000000000009339b95b043903f2a3b5926a27e57cd0c45e7955946718e7dfebb01f18e9d7a2002c670769c4674773a835311f2e58e000000000000000000000000000000000ba94f6b625c507934f633d5420654056a939c68899c41e3f337f7b927fe82191d39905b349870ba0c41c8bfc97d64a9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000007604b5eb3218140b94732a601da577da3cfebe04dc7dcd94396c1a6704a0ef5a5bbd0c31c196f2876e1a4bb7490629700000000000000000000000000000000193098ff839d38c9bbda43944d7b0a3ec9d0d6732519d4cfbec506d29801780813b2faab46658c4383b2f26c477580af283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e500000000000000000000000000000000022ca1a16df42ba543a118212a75eca13707ee01eb3ce27d3659b1fedd99b9fae859f4eaa51e9be9107704276b578a0c00000000000000000000000000000000012d60cf33701caf11be6c9e3ebbddb9c7066dec3821a2e0f9e5b94e029dfea4063bebd4b2fe18c2442311c2bddc7c08d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b00000000000000000000000000000000011d4918642919c801fff0962062a387a4dffe693ec09cd3d0286a18e3a22c84fc09e8396ca82e6054d8535cd888179230000000000000000000000000000000016a1f0c7fec5647dcce688d3e4e526749bbf23c1fcd9e9168ace47399f9198c9b3a6b8aeca68febde1b7beeea0641aa2310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a990000000000000000000000000000000017203c37b21375a524bcc906843a0045229c5531ca23177dc88026e83723db21d9a8b5e52cc0be1d232818ed9abd496800000000000000000000000000000000097b4d7fdfa442dcdb64e405965439ebe70e4e71cc8e13e299fcc0b5dd88c67d6d0dfd254ab9b545e66295e2f3df14dd91b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab9850000000000000000000000000000000000fc4198a87e789015a1e44935321677e84356aa9e06592f9cdbd149d13ac312980f3048dcb9bd02779a3b10fd24ec98b0000000000000000000000000000000011425345ae1139647f93fc13eea0e920c491a49998430a339cd9d4260479a427515109753e70811be4cfb3b96db5c78b3e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8,0000000000000000000000000000000011cd4c4507778871fd7b28aaf79274178df83f3e53c256dbe7a52df884d28df6a0d87d20238a15f489546a459195ace0000000000000000000000000000000000439a672492225fc09e46bb178a5d38956ae237d9f7d187d4cee14597facf2c06d7e70be5ce20e1f1389e4da6321e455 +0000000000000000000000000000000003790fe37a3aa78cdeafa76bdbebfebb22ab5f1e09e4e488418568fa307a5db18f9d93126b0d3cdd6a28abe3a4648f6e00000000000000000000000000000000043244b9c78fa56c611bf72bd6a17148abe76fd0efbd25085d7b46c90318ed591c5975f79653b98440f5f7c04cae4d7ea2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000008e8fcaf6d2056c6e144295d437f7f1422f6af7a1b62e0b8073141b2992b6ba865822aa2d9fe439aa1d896b2a6d231c4000000000000000000000000000000000bc693fcd2021972914747e48c600c444bf69ca8e1386655bb5d987608d648965c754668ae0a72c2439ba0ed98e5e581fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af0000000000000000000000000000000004e877b9032e168650ec3502ba65118aa0a8013b995a647210c1c36a6e6c702a93caef674d03d82da1f7c5d7ddfa0d0200000000000000000000000000000000063dd22dcd667c8288ca5b172e34b4eb783403105523c0467139b814e048fa21245879a5e9188a1a87d26fba52a9f601da609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000018cb2fffa3181bb665dedf1d60de6096e8c5ce43287cbd86c2df5a5d42d0129c73cd281c085fc562b7afdf52f0a680c80000000000000000000000000000000007f9884780460ea018351b4ccb5a120d44312056b96c5ba77cc38789627d20500d6b7e69dbf6ab49d6bee998a6aded67bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000bccad9f23b4c1231eb07df139548b66714a064dbec4ac6ac43ce18671144f2bf7ed99f16442b9f6600e1122c58f52e50000000000000000000000000000000013646b3c310a4b3f279e17f45fc8104d2c9d00f698b869479a5a0e1c2131e3f3a9dce86115ccd539bbd4346261c5a75f3222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb4000000000000000000000000000000000d02250115596126e858a63a7082a8c8f8ebe055653f5a60c855ddbbe3ed05792d08e5cc348094b8dfa4584037be597c000000000000000000000000000000000f68ec7da947cd0a57177fb91d12a820ef8574f4c524fe54b9420f9ba4944759c92d5919d6dc8030fc663c34519b64c37431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da1000000000000000000000000000000001960580ae965c37c2ec219dd0753749bd70ac2f0c4a3837418023c5142caf7b4dbf592554a6dd95872e018e912e3a20b000000000000000000000000000000001210b4093a07616543ac2034faa9c4a93b5f4cc3daffef2d8450b1a1770948de56c5bdbfdc9f1dc9af5e20778c1e8e6cd474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c73000000000000000000000000000000001955d93fc0f3ce0563ca4f4ffae0257297002001a3eb941cb9d3bf82b8d7f97657ad7168bd386636aaf45398745d5158000000000000000000000000000000000cc7a0babdf499322e060f2c83897fa7b6c3e7b4f56de3a18c823e0ffd87545a3dd68947df8cd8d3de5795ef7cb05391976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd1000000000000000000000000000000000d451eb31b21eff2c18b52b882e1eac68a524e3db43f233a9d08139667cd0173e3c716f29085c599a09f19019fcf447f0000000000000000000000000000000015852c483c8545fbf0932c99b1944ac58b37228d15284c7be5f5259bb8002abd57b26c244846652a862d46016221eab19db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000044e70861dec38d2b5ac7fec042c6b931d4e0a072073333f03ec4382fe40919b29378cac920836b1641e5e2db053c5c2000000000000000000000000000000000c422a91c81a99caa32666511c0ae4decc67cd94e85260b49760ac9e97894b0eb434d39c3884aa4614360b79681403f94ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce1997000000000000000000000000000000000af002ec82c5ac0dc87e1ac27f4cd052eab67bda318557c70fcc2edbdc071ac4a3fcae90f73ee514cdf8a543ef59050d00000000000000000000000000000000109f720464ff2eb2978d66370041206abd9ef0c6ce79d51f7d233c49b72da520612e59c39f3a775e288ba2220fac1563ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000f62e7d0aa954742a2018d42dd9cd76f041d9ac46ce659f4e192053a1d0c9b23fad78a06f61d2c90eb7b4d1bfe6d951a000000000000000000000000000000000ad5a5ce7b66928d8e6e3806a25425bbf2bc63f8ec87002a913c28ab702b83b6ba590b41a0691daa5b921a12375ef47b275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000008b836a23836624b39e3b3388027093125749a5edd5df50ee0cadf1d485c9dac9c2569a82484269fe7af02334369a29b0000000000000000000000000000000015232caa0c064d8d1bb7fdcd23c0eba21685fc4671e9f04cd1dbaa0382aa4e9d87aea42a99cca22205367d7b2261defaec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000875311ab0cde9a925383dc84e4ee8e1610b2f5af0e1f530aed4155cb8ef0b5050d907277f55d8dd542a89e4e0990bc30000000000000000000000000000000002c7a0d315bedb602f8ec558648ffa69831b9fdb6c14fdd44e636ff00777f2f8ae4aa23aca1b261460e6dfd87e7e501131e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f7000000000000000000000000000000000f9531c4998aafabc26e1ab588a97a78c236a854c3fc92424320a37a236d5181d34f8e5533aaaab2a6ea3385acc85f6300000000000000000000000000000000130350be432fd7d68940fd5f54649820ff5b3d015448d48d1f4db3a05ab0405a73ccfc8eea1966abce35833b5d03bf79f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e0000000000000000000000000000000010fefde43b2cbdab52ba664e12c7a6ff29f647942e16ba5a0d41701754ec63bf199ac8e710ae8dc6a033abbcaed3e05c0000000000000000000000000000000002189172e607876a6e1664fddb990009dd5c7a8412d60f7dcb235ed1825c756598bc67f8d5d383c2570a880492d4ee1967b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f,000000000000000000000000000000000765877938c1a8170e2c2fda55241e3c64f7485bbca218f4a2026d00ef4708d014fe4194048da8e994cae1088694d1b4000000000000000000000000000000000b32833dc9a39e1e73578b21f75136be6c6aa2b4128b0e6ff4fe099f7b7a8ba8f2b769f68d32ab4d1f37793aca8ecfc9 +000000000000000000000000000000000ab94114b3ecf9535261a0726a9bc0e0907385d56206b61b7a42f643d46296c4022bedae90d761d3c002dceaa9167fed0000000000000000000000000000000008e67942ab2b9aaf2f6f865b7e957a25dd7ab8d8a0cba02fb1648e4c7f15ce00f4f5d09199a583f38425bc62d32ddde69f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e000000000000000000000000000000000e53abd9ff27231fbb09155f794e5d126c490314016e31c0b12bd1d2af97a705bc267f92e20b64c91d9af1bbf5e45b92000000000000000000000000000000000ce7d0cc6656108aa7005a56d15a497009c90871f01eb38f1bdc82edcbe4945a2f2b67c9b812aee42cc9a9bf9ee84bc08fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f30000000000000000000000000000000018c2688f573d4849b6d19e711ef4d14659c2c580eb938434a3b2afb8c20c522423db4c7fffa42eee9ee907a6492b77ad0000000000000000000000000000000016a7e69d5539263fd6b7eb893d476a00efb8cf09f21a54e9ff0d1c11e9f3651eac8a5db31b40598af6c943f864ff60ba128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000002380f3260c7289ab2005f7b1d7f572565ec938bd894bbb0047ced0b652fa2e74aef19c9fe6bc1fd469b2a4640245777000000000000000000000000000000000f32ca31e6bbb72a02f4b0da0e1627dab9cf1195fd7f48613c89b06c702e662478b24d8b3730321f803ae3a307fd498bd45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c2060000000000000000000000000000000001892b0d219ebabc3be00f45b00be55ae486eb79b1e41aa7dc8457aa0812e7276c21024c79646128fcb2b3c517aa41c3000000000000000000000000000000000793bed9530c814fa0d0ed1684614c1e6968dec931868a64372dc1b648b1f99ccce20fffec7d485a226033601b92a7f228f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c132200000000000000000000000000000000173f49cbbe6304aa41513d3742b89c6b07a91be50264350d71bc03fb9efe4faac4a19e2591795ff4a7e67fef7a85ed430000000000000000000000000000000019bb5dcc59ddf055f099a1c3949bb50972c4cfd035d4d829dde4ae94ff9669983e9b1a7edccfc2436648dc942862676fe6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000e3daf60e4929b4a237caeab203f86e6eed0ac630a8b955a03460a7e609398d076c660401f8d2bd9601e5bb5e315e1e400000000000000000000000000000000058b20160ca2232cb8b6cc63c5a8e11613afb9776e22d93f687e7ba005b099531f9693f65f153db01f20c8e9bdd7839ea83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000c89f1ebd19fb920b6748b15192829d58820ee4995cab9035ad6bfd8dedadbc6352058806a7d45fecefce40133261f360000000000000000000000000000000019151260431a35d124fe44116d86ea99e3f3aa14e2eb09be8193dbaa8f26fb0ae2451ca1c70610233d3f0af9d2e33fca4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd3700000000000000000000000000000000162b48d56f439ff56197fad444dc460cc6432722b9b86c7abbbfa383ae1546e160716d94e442183196816084da90bf77000000000000000000000000000000001278d0796c26110f66930ea9248078c222a0590a031df30c62fe6beeefa70deb0c8287b0d204a911c147cb6344632bf329d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e6000000000000000000000000000000000f4fdfa45aa3b5d1838b4dc8a2dc6250c069806ec3c551ac961da5b44eb58d962d843a1c17ebf89bd653e9e44d16300200000000000000000000000000000000052ad9ce994c837596339dcfb73ee25bf8326657633fb5861039f197249d425e35c238dcebb287b77f41bfe7f4db5c9b971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e400000000000000000000000000000000185c62a080df61ddc97ab56d2286ceec655172b6c863b509a1a92eeb0719060528ad3a3365ad5e7c0858167ac2c6d22100000000000000000000000000000000126b489e107dfdf4a4638069944d1b1297db734e5da1964086114f9f62081527d7d3f6032c2f29e75b4e1ccf5b3776d4659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e000000000000000000000000000000000f884244e098975b837a58ae0218e7e2606821c95f51d114a483ed5d31a59c9b9cb3b1db029a0286eb95686e0457afd8000000000000000000000000000000000caab7f67feea4752d3822979a770a28c879f5e8f916b72dc71a3b14820ce170fd229fdb61596d9e89b4be8f515c470e12ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c600000000000000000000000000000000155d3e886cce6f257513529e40c21b5657ef1ff1f4e71bc32b968db3e05652b1ac780da573fe1a1b94b7fef86e7c260f000000000000000000000000000000001184cf09544ec2826d0101d2b79095da6e5f77d453203c52ea17b6476360ccf166ef092eccf86dbe3a260f7fd25a2794666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000001523e919446b532593b8e70cff1206e8910444c01399c0dbad932b596cd0b9c2e40983ddb38eeff4fbd5e8d2b15bdc780000000000000000000000000000000004be8fdc3a3296e543701ce8c1184a983a2932f33913d6d733f5baa3a783382739b697fab4a3d6f9ac5b85ffbbc78a3540a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e401390000000000000000000000000000000018d20e7846239f472ef42c78454b6c335979ec563ecbbc3a93176a7be9dde603e6f21afbb68058035958ef7392dff3f20000000000000000000000000000000011ae4de8a7e1a958a1186bda4890d282773788f7d5fc5432393ac9deaba8bccb5db952547f6aae49b8a90c813c5a93a4662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000010641c99a359d16dc3e3f68547288c944d44c7c3e6177fe94428ddcf3c86937a3fe1f41a31eeab551e11cffac012e1fc000000000000000000000000000000000f407b01737dca388d0793521b667757d70e626ea0ba3b051f522639e752280b5657b1b97beae3105489161ae95a470059401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6,0000000000000000000000000000000010084535f50807f287eabff2fdb83d34ca30454e4cd747cc3818a9dfd80c30fb3bf2f9f771d435b79a2d36105266f0c1000000000000000000000000000000001663a611323246a963856a92d52947e72dc123dfbeaeb9a3ede6147246814630e5304b50a6585894843790f5d4c818c3 +0000000000000000000000000000000005315310b8412d62f5d63fd996e8c6b14aaad5a6c83eb3505a28fa6bbe469f7a7cfcf10b49382aad4d6764859ef4910e0000000000000000000000000000000012fbfd9ee8bc712354fa3b73e57fcbb07231aeac980e99d5843fdabc081a159bfc6507911212adafc162dfc21a5afb739c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c0000000000000000000000000000000014e14689a5ef5b9ee89369c5c0de07fbb7980f37294a0e7570191b73f4406ec4bd9bf4ca2521f8d90157e9c3c7d4211900000000000000000000000000000000040d06da8127e64a71532afc8846bd7eb6fd5e845ca0f1d96effe0b12a2f8afb121d7fbe89f632262ba0e382e8204701ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000c5ece364affb6af365a4c7506389694b9a10f3ad6798c326852fe85a892014b6901d097aa8910256f47ca1d4667b5a20000000000000000000000000000000003f300682da34e22416f1ca2bc3430e3b153c95773c8c76660603a0ecddc20ba570545d9307a6b0910eb406aa14d196bdfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000ad0508c3b4fcc1cc608d002b66bc703cc16182a6e83794e4f3739238c3e02fbb6387ceb445791d54321ea52f779a35d0000000000000000000000000000000009a442ba572cdd9e658080fdf1753670c27e88fa894c307eaeded6ead17799365d1cefd1fd13f0dc321c0e881a4965d3f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c30000000000000000000000000000000018825670284d3dcaa90a678ff37f23e8ba36307f3c1146a8f6c782f7b43ce16f281dd346962904684c22c1980a772ffc000000000000000000000000000000000d65166eaa6b4ed79b5ddcd7b44f06ca1bf8b960211bcb17d5a26a8595a1ae1aecee9945a674b92384ad05f2f0f64fb6c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000098da5d9289f26b61486e3ea52b0145a47847ff2b9f1d2756e363e5ea0bab27a98fd01d633a46ab48aa1d2f1d2886f9100000000000000000000000000000000191412a43858276e4d7e69542f9e6ba4fa9bc0a8784df590aeb1e0d65ffb56cce0031916af640dc3e57662f5e5203436458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013a279c27bf2234542f4ac0e4c2676b41b3cdfa1b55d5c0eca1c686589c37ac63139a7f532910fefe275a08ce2d37fe50000000000000000000000000000000002f56719390112560fda45943509729fef3eed60215190ca1f90143a4d2ae6b41aeaff7edf027f27857d56bae1900ecc804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000016489ce6e2b8298e2fe0836556875156502d36aaac621e45514ef03db87631cfcd308285fdcf8ca7ae8bf65bf53a37b3000000000000000000000000000000000b6c8fe0db4492a309148c54465ca06c59c7b71e4418d8fc1874cc338df40fc1355a523387187402b04f5d01b5e5b82b6f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000065358f885a974a1f64ffd526e5ced18ae5ebab2ed6c9719c9f879adc940292ad124fe5b6c8278c82a33d1ab2a1916130000000000000000000000000000000010d019536f727f8ae098dd9ccb6344417042855fc6722443218d83127cd2b07a6816698dc1a48776d2cbbc937f83163dfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000004248c5eb514980da698bc5146fd3743f5b1a458dbb17edd38f65c294e48bbd55e0d9afb3b39df2e82085fbc03e5655c000000000000000000000000000000001830c1d21ff8cd1ad8467ae0a8d2a34367e7c44829f7530263ef3d7d5bd9eef76b756f475448c308f4c03453f54b43cc1aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000000dbb72f9afde915110f2483c09291595c369f0b4ce2c91779da9266c9f74764da4976a221c4997cb940302ce0e59ac080000000000000000000000000000000012de4b2ca14004be2c64ada45e9a0ba7989ea0e22d0407088a092cad87b4e26b33d5d8f96fe6831e085c6fd27901af61c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa289000000000000000000000000000000001387a1edcc34afa05541e15e2355d3cdefbfe22ab7481e1f194e461521894b97b2e18c9fbab1eb5d8e508a0bdae08b5a0000000000000000000000000000000016c4ed675f20aaf2c825de5bc4c11ce1e85a0b91b08577080108ab7b52bb674f78943a5f619f557b96a72206cc1bd447e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf000000000000000000000000000000000498481301a55b2d1dc95f8115534b1baade13c2cc4d5bdce1fe8cb1734004600a2359e5dd1c61c7338275e2f4fdf455000000000000000000000000000000000a3d2ee413b7e6c0e32e51dcb7d124be92990b7e4307b9b459da1db20f85f4a35964b7987933634fb62a07f797b00b27fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf000000000000000000000000000000000a885ed8c3ab46b60a7d2e198b6e8d069ca8f7e0692f2b8ce99df2f44979b6045fc17991bfc27867be79e2055cc8aeac000000000000000000000000000000001728864f0fda8476fda4df08fb6aa9e40a01dbf19a4d22c4fa0c319d8496d405f0a5f9c79ffbdd5a4c1b617326f3d774dc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e0000000000000000000000000000000016edd94f91c43f15818752660e4737071d44edcec5d5de426141966a9880bb894f3566e98a05232b9717bf85d66a57c6000000000000000000000000000000000a789ee6ecb80e2ab9c6e7a945ae4839c620f9a7bf430ce09b57a64479d5a10a1ec0a721678b5bece737f0dce97a3a56e4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a0000000000000000000000000000000008affb2247059dd4bd1498c8e229dcba313b156e2f420fa55331e7eac93d44af55a6c02bf2101d90955b95ff6fcb411d0000000000000000000000000000000004759596f12f17d7bad24723ccd6f86c646a39beb2aad35ae5a219ef57e1ce6eb310b2098130489421709bc20b4a53d703f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c,000000000000000000000000000000000f841cf3d8897108b4a57a7802a3cf8a43ae31e711a6258991b6d5b3851e9e0d759fb90899e462828ff9cf996bbe9ec70000000000000000000000000000000016fa655a67f441e967d3137f6ea8f6cf636fc1a7bb662b1e22f87397e0c77f34e015e6bc124291647727102a12561dd8 +000000000000000000000000000000000c98e02c9f7784d0dbcb4a49c97a9365cd069817d57cea3042cb4198180b95d141c5ba4d383de188f06faf8f845f78110000000000000000000000000000000014be6f602cd67fc2d147925cd6c90457dd253db766c4b8f737cfca02ae15b47d5798c621091c4be71fec75e0b8b1c00feb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea00000000000000000000000000000000077bb03ccd915742dcf3c2640ec61f05bbd70987d2dbe9641e0e34ebed3600703e8f9c382e77f99b70c47f54496bb6840000000000000000000000000000000015ad452396c23e820d1e8a8a9cd7557062ca9c627cc7439d43c528e0170e2760e7761c9cd872141543834c89c75537d72b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a0000000000000000000000000000000008eeb6c2c00a9f95c5b238290b06a67c1cbe0e96da246537c29c0efa36b53230c3c5d91e3fa9d129743e5a9d87e81d0e000000000000000000000000000000000ede1011370a956f240419cdb8a0c8ae869c3d583d938ec32e29c5ece68ec8be0e69296ff0c97aacba59991d65a25563babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db00000000000000000000000000000000179776b08cf2da01a94bfe7be4b89b3308330cf797906f85889b63487115b386c68c8518158342747377fbda82a6d2240000000000000000000000000000000003e51d69bfdb73a2abb469b379e2b4825423d2a2cf2cec62e2313a76d260be1b0f2892bf82e5435e88205ecc9424275d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000000af478b121104742d0cd13473d1b7f647437d980999cbe7aa8d2246148d970136f6194df1785027ce944cf9ba00aa4f500000000000000000000000000000000170e9f798184188cc21b0950e0f3a570398a97405dc87a2e077af96799960a938f363d216474422d8f4762fe5893ece61a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000005817e3812f73d3d236e45664af8a4abd2d4a44f741c3c1866588c2bdd88b11741b1c272b68e20800abf3adad7125a400000000000000000000000000000000008dc859c2323f0d2dcab76bd8454209c86685a971d531a32b00985eb822d33691c2524fe25d14ca386047a4976b9e7159b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa453000000000000000000000000000000000ee06092a2ba4c33f5c9dc6062d50e3b133c7fde5c81056f74a2d869e8f92310f07629db9cc2b755f12016cb7894aac10000000000000000000000000000000011714a54e236d1e13f9b649a0aaa80cff9e093342c71a8dc9ff1e2d4e95b0f6b4219ed847ba6620d23feded7d95944183b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f6460000000000000000000000000000000017f6061908e62edbb8fc5498eec23a51c861815bc1b437b7383dabf303e6a45d52e73f8363addac61974043afacb02ef000000000000000000000000000000000f3fc04d17d801741f3583e072110b327a3488135659fab2e8b1d2aecf4694f6d168bdd60624713a7c2c3314f8309079f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b000000000000000000000000000000000db7d958b44ac5ff3bdb4991dbcdcbeab36bc6d21d9e0c8fbb1eb66601df227a6367ccc783a92c534a30b17be462b95d000000000000000000000000000000000424eb0d9da831c658ff048d3e9ee43a900bd1ac98bee97be073ea55be1dfd07d425e0906779f0e3459fc69d316599e56440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af000000000000000000000000000000000c9cf785be01b7f4bfb0140004873d0db4c8b1387dac0fec42c6ae1a72123ea5cdd2b8c98c69b78d617b16c48ebfff2b0000000000000000000000000000000015c4856f183d26d13196739d9b9c971af111b4905b669f3e46bbc8d8c4281cad1be05e9ac28de0a98031923fcd1f5aae43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f0000000000000000000000000000000001802675ef47f9660d5969dbfce973c8bb3e6b2a2717fac9a509fb3c7ddb272db86f283992eb3167145f2e496002fb1f0000000000000000000000000000000014a5b5d966ff72e036c51686dc6a9f39a487ab8adab6fa4a906f28acc67d64576fbb3a00cefb7720f42ffcd62fc8adefa40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e00000000000000000000000000000000076ed600ed860f16ec5dbae3f09471302bf85fde7702b3376b0d670f93560e77699bed969e7001570f44dc5e37aaa830000000000000000000000000000000000c993a8b08d2eb00bcee05e1c09e8a37834fac53643643402f60fbfe2cc7d795f5c68f3d6a32c8604c37211585830426e03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000eaeaec30bd8d8dd9ad4d38ff97e08706ffbe51388a93967cf16155b10d218e5b1213c29c8054cb778a0d3ad22d32eb200000000000000000000000000000000079e5f2bf405cf2dc79984ddb3f813a07225729d4cae8ddf7536e9240fbd0480f6b66321749a6a9286cb07758482e7f865cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c10000000000000000000000000000000009a0933829c2a3f2c3e93f58551e7572ecf6eaa7857aa899a7ff0eeb15ccd601559b9ff844a177568632bc0ddd6e80a5000000000000000000000000000000000b69f23cc1556385897bb7457a706cdd8539a3ed3e7fa504ffbd95abba1e824dc77911efd1ad0a9c37e1a41a76ad38d13ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000d3c4cfdc03ef5fa066be3c26744032e5a2045746cd303b6df542a6133c671f4d25dfbd889840fd624125b63839a1aaf000000000000000000000000000000000102fd619ac946e99c765010a4ac392ab907c37b31f628d6d58c0ade093ef394a7547de36ca0630820f4b5d857dce449e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a3000000000000000000000000000000001589cebd579c2cd31226245f1dd3e428a76c7d0012f8dfac4dd3428a716d05a0a79763f0061d3b5846dc29a8a006a37c000000000000000000000000000000000bdf3425e6cbe628f9223930cb74ace4358e12e5d367a3604edb05cf0f0cbde84346ef45597bd61592500583827524144c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d,000000000000000000000000000000000555fd5a7818bbaa7e786f11eaf6f8620b9686b76c6293fd91690a4d181c0b470295313294589daaac429607b0020c9d0000000000000000000000000000000009c3a53113a657a5f7e30ec28056455f700cc7c3d40cbe4219dac00980675023bfb7462e634c8a131493f12725a27d5a +0000000000000000000000000000000002d49464783e5ff91aa0dbf6827315dd308e778b3da5833cfca3b6431ae784193d915a566142ef347b6ca024b6f1695e00000000000000000000000000000000029051d39ea4369a837d4cc8cec1eb8f9e7f9c3a247dcf99dc75eeae43378b4b9c4175aaa5eb3f7abdb1afc15bc2076d5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000001363b8347ef6754f61520942fa8cdd07e6dc2b72cd40ae41a23622be239ee25834482533ea7edb9cfd5a4e21e4f33f020000000000000000000000000000000004495e8d41b145ca7f5268e66c03528c8d976cd650d815257906e46c1f9a0827e0e79f5a8c2906ec96718538e1da3b1d2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000007735147af3bbef7cf0c4a7c8f1dea302a5e4edf01d42c1e484f7fb1f4b8fa23b8a7a16fbece9270d8786016836bc979000000000000000000000000000000000053406bb3d2a4cf37924643a186a56844a4e77ea4c9e9e2c707b5f947ef956369f400e448930aef7135449f8cc51ae1530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000133b92eb9f9a3c6cba655d5f26f396dac467b6444657eb0a811dc6a58ba1898f24b336f4fe9b11c1e0795891b00b6c150000000000000000000000000000000018952f3a7f8aa78a8c5e5bd96ecd5d2b2f237916d8e2982c40cb7498423f12c6ddd3cf1afee75a3e2cd773bad7ac3bf6d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec04991300000000000000000000000000000000051453a8b81b0b0a1566540b3026e40676ea48e3c5aff89ec4fe3b36c61aea27ebe01fe8a811fd3ff73eae0a67027cfc00000000000000000000000000000000090b399b1e5af056b428a4c270eb204df4999e53807d34ca750f30b292cd38030491c3d1b0e08600f40a16f707b4903242a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d600000000000000000000000000000000142bcc3458437416506631c4dda54572b5d66093ff23f152957350a3aaa462000ab000cb8e9c9b23a17149b5d012adb0000000000000000000000000000000000734c0fe1df24449ef498fbb60558010093cbc8a14ae068aba2f70bd7718e30450411a81499a895e3d84079a9dbb19557a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad0000000000000000000000000000000010a7a3380a6d8b2bbf212da72eefb57d2fc2305ce222e8d908bb572600bef7ff55b1df6a9af717e1345967cc18e779ac000000000000000000000000000000000c5a3aa84b489c879eddd3c20df6d510edb5e9ac5c1a2e42b770571ceec315d560235b27468299e2e60af3ac1283be12bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b000000000000000000000000000000000035471ee35c187e24cf0d113c0ca1ab6322528153d0687b15953c39290ec295c0dd4197b72448f2a692537064ede8fb0000000000000000000000000000000002717020e3369b288314a42fd8ab6c6ddf7007480ebc4fa094ff7c4c4b750f477917caf071d2f1897a826fe870c2b7dabef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d000000000000000000000000000000000b501cef8ea57ae253de63d81998768e115d58b353ac1ed6e90d24f8c39a31bac1a5be1b535a1dfe05e72d80d1db8b0a000000000000000000000000000000000a3b62c001c4b725f7cc861fa042c31fde4e77b3b0610df63dcbb7e89d3fd746919c2bd8ee4d623838a05d42b6932383392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000011cda0c937d8fb2b21174ff3a5b88aa5e1c9a8ce6eaf26cac9fb3ee7f3ad20e74ebbe2d1bd9f4faa3acc43b6e6d0d70b00000000000000000000000000000000195257a442c8e39ee6b72cedaefab0034f48bb988a3355ad07b3e3e314800b2ce30267dad6ef3fd9dccd7d2318dbce0a20a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000001630ea3c7f910ee8574f29d652e86fe3125c306218a894df0b4688ba582ea7d597d7e62cc2e7c78dc2db289f587f10ce000000000000000000000000000000000d2ecfe74480518ad4f5ded701afa68040246a08df1b8dcfe6fdffe77e33c6bbd37192c6c41c6ab5af506ba58d8b3fe4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c4725880000000000000000000000000000000017a910c111d7a0f7e7a3d48b1cd358e2a1213edc077034b06d1e96beedef80473ec17d1c10bc2d33d4fd2a8c052d926900000000000000000000000000000000040167897293a68c980bc34b3f79802b95186200b40b4763fee9cdce8afc681ee916042d619cd51361e6e02688b4915ac8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab000000000000000000000000000000000dbca3203ee6c7fe8d6504ad2041aad2681b889996bbe28ff1282cd20da563dcd5c9fea5fd03072134019f579e4ef7af0000000000000000000000000000000001317a861403866494eef2bf59519f2d324586e93a0037d07312dd8df4ab844525afdf4b70f9e21a6e0230bcde35db4d8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef200000000000000000000000000000000069ea1da08dce1c1239d49411861d3e8ee7e6082d9bf8ff0aad1cbebdea6dbf82fb0d6332ae436327440b71ce6535ed500000000000000000000000000000000079904ab7b16de5812ea3eae39d790aad32db02c9cbf7b8a3a8d4222d3baf710ba1cc5bcdcf4fc9e2c4567992fa911edd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d00000000000000000000000000000000173ca2615b65e574bd77c8cf55bb116462a7ab9ad4a3879f0eefe03f1a6c0d30feed076e0fb21fc60ee9f270af180cda00000000000000000000000000000000179351092d68e7e0d428811cd4503a57bab9a4072f1bd27b5e8445ec0058eb46af58c4752601b53714b816a4bd386048b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000016c9d1fc1790a15985028a38e57c87cf010c87bdeb2a288a055b4b08497abd1d616fa8b28d6da8cc23047e9f8bbe6bec00000000000000000000000000000000089601933b759bb565d849c3837570feb39d442461d764a22f993a695fe1c55283b8c7db02694aa66032512d44dc88867b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f,0000000000000000000000000000000018bda18912ce64106fd3d54ec2024a1d3e4a807d7bb8aaff7b515d75c9934d4729c14a4a72ca7654ca811a69f09d170b0000000000000000000000000000000011478fbc5c03470d9cfbf3decf9416e1dbea8a696636b94244c5c637e43f81eaed0210b1cbcdd291094e8581dba3548e +000000000000000000000000000000000446af4281a01e0a20b7428d06b63b89573912955971be4a5cddca514708419640f8a7f95b50ef8714a04e1fd81bec64000000000000000000000000000000000087b94d8493239047a5cef74dc20d7708d7e3365018df80624cc5511483c3a5d9b14ac3d4aa391da60980397e4fb1e96f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d74100000000000000000000000000000000033e5eb4bae80d55f512a48b44054d0efb8af1f9870fddd99df00f31dd437025381df3f4023ca217ba924a961864223e000000000000000000000000000000000f6d7a7371eddf7283890d59bea3c61fc3bff19eb7fa333ae713fb9a73c4971354474986ef5a9a81ca8c5b38bb67f58d5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000013d4e9518d398fc0add8233fe58c198d65966844fe286fe657891245fba8f37665e2bc40e4e70886667c9e2c0a1c245300000000000000000000000000000000089562c10b287d4d66b2b694d29fbac936f700de78525e9be59a83543593b42c5c577910e7ba1b67d840d88e7a3e53fdbbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce50000000000000000000000000000000005762727639503eb63854e5fd3de33bcdd80227e16de19cd7cfaa10b7863915e490087dbb980b6dae5114df7d56716d300000000000000000000000000000000104306b38970a94b5c8839ff282883b7c88c7ef45a7ed49a02b322a16521faf2b881e2dfe22da3f4472e2bea9fc40d7e607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000056e61b51113719c1829d4ae4361f79c543961de801b1a62ebbc3cff04b0722be241236d4e1b2dcf7c309ab9735334a700000000000000000000000000000000031ddb45e491ba2d719b1f72f54640c63e281dbf6ff84eba2eaa2b781d87e243e7bf84d7151f27556156970dc8a2407f41c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000029eff96206ff45ea9bd0be2b83cdb660d6bb2d236971517b962faa54535f01097327a00154bf35dbe47841eb36417020000000000000000000000000000000013734f1218c3c34d2780920806c5ad211128352d8a41c2a1035594f470ae347e372914827775094164a5db9d0b2a1ef7c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c000000000000000000000000000000000f3aa9f069b07cc935a974ad4eeb47e8b0083397928e8102651ee54f53005625c359d82fc8b5dbe1c76f650cdccc2ee2000000000000000000000000000000000e2bf6a8c4234d118676a29f12daf244ad9aa562faa970d2d63feb074946ca70da039e2de104f1524b1a8f3897f053f4c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e959000000000000000000000000000000000795a2b6b27209b48c00cc8d37864f14c6be66d6a41038122a28186d7bbcc4b02f531aaabd000fc93c685ceeb67bc3c500000000000000000000000000000000143926b42a6654e439fd01883f1ceb524cd8b5b1f2e3eed3e905f6e948736790cc1325d1b04e30247e4971b75939a766e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd200000000000000000000000000000000074096150c9e04c082a1aea20c785b3a7396568e43707c42c512575a97db8127c8c1e0548d640dff8821d7d235f268340000000000000000000000000000000012dde2f1d15c04292bb5da4c467cd674ddb43e401799257524cf3097d0dda1f3c9f2f0637cfee914a4c66d737f9e3278651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f269000000000000000000000000000000000109196dc59d6ec06fc4c774f665612c11bc3e826ca4ba528a15c6290f733f3aa1fc441bd896021471e1e85943fc9ec2000000000000000000000000000000000aa0d17d44bf354e48275ee3e4f06291e242402469be6f4cd4a62ad3871d878c1d27a8d06974c5c1138281802368edb01a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca000000000000000000000000000000000067458ca402c19488e2515037abf9323ab8288e0e11f7cdee18b3da50cfa377435cfde1f63dcdc451ce65a05641cae370000000000000000000000000000000010ed9c895629bdafae66ea176388be4e4ce45cb13ecbe0869ce57f0f48852b6b8c47bcc4a14fc5327f1df372ad9f5d4a7db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf7000000000000000000000000000000000f393c5fc8e5f1cbc7b59742e5b6236c9d1d262d0b736c1bc188ebf58f954bf2835cc70617062a01459c139f328c912d0000000000000000000000000000000015501635aa7565045ef59067e0ae91a5ec4871485ba411425987d540bcd7b5782aa7164dd631e4c7896b3949cb115f9a14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e600000000000000000000000000000000174b0620cb49d8b1a5798c3746046c2888c8e96664dc7bda5b4e90336517448eef534469a40086703d9a835d2a94930500000000000000000000000000000000033db9968fd6322e7bbb9de572e8c92b5e3717a9496803e3f6ef8dd796dc6487909ff318ad6d4d91297ae6f2daf07bcbed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000000449ee22d2c23ec02fdf1751bb59feafef9291d6d56f7120612948875afdea56453e081c5c5086205ea83f0b8cd541ca0000000000000000000000000000000006114d6d8ef1e4c6d79b23a2b91e5577323107d90523001cf7d6d18a0ecf3b414d4fe1a3eb831a6d907fce9d22030bcc30fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000000c2082409ec14f6121de6ebdc06656a28dfc5e439a0278593dc6aa845e8091d8caaef45ea1ad05aa12e3c1533275a663000000000000000000000000000000000a2ad9980247640d44d3b37c7b7b2c1b57592ac12cfe9aabca4f88ba90c8b3221a2b9f5e4ce19ffcdbbaf99ffc584219980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000108ea3fbf78237f0e90d4addb69f25eadb0f21c89d92774b4fdcbc97632f1622ab4ab408fee95e735281ea5da5c2c8130000000000000000000000000000000012338527c7932a737daab3f8de98b9f2aab59aa1b12e84d3674a8ddbc1f86a8a9e7eb0ba854e9564407aedd489b6016c619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1,000000000000000000000000000000000aaf02063d6b5b395b75faae4039cf2eebb69053f1f161242b47854cf07786788930f3be2598520c178301ae0bd13ab80000000000000000000000000000000019574e1de9161a11e804d8077973c5ca70ff7925c847d870cd2bc985a8724d41331fec6c1cb341f7509a37371db9e4be +00000000000000000000000000000000048708595ff4f08cfa2b1c101ec7b3538a2e6044157bf39a63255b5540211105f680464be5b03256f9153a90a4e62d44000000000000000000000000000000000f2fad0353cd8fbcf0ba75a403209094d88d8c8d068eb0c7077b8263fd9f7bff8d6234d75ac4da232667b5c566604706119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc000000000000000000000000000000000866fb774b231d82a4508ff9b017ab836936299954b2b404affea65f315b62da34c76019192f5c9a447dba8cc1b9075d0000000000000000000000000000000004e050fb7a17bc738a55f1ceba48920c62648a27cf438b770a66166522fb0929069fa6f2b2b742ed689f554e9023ec14f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001710b342a52b0781d1ea18a9f07d54fb18e9c90e44815cc7509aca3a5c9ca3cca6bc32ff6ff726cfa353faca4f097e9f0000000000000000000000000000000017fd38b122a7ac39533af597b462224b86370f6e6814ca1ea71d961b9c7cf94b952fd75502031cde0851773b2c6b0108e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001fd243a3c69dd5e7ef19cfbd9b7cecd475e88d7be85dd3a8f48eb46d5dca39d05aa4b43c0c700b6632ebc0b4cb3baeb0000000000000000000000000000000008ebf24e9d2de0fd82c69e0ddd1625da0367c2e9f975118dd2ba5606d77de377be10515d9eb921be5136ed25fa6b27abf7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000021f919adb62791296db3a0b81f03b87c01d94ca312f55cd94364eaa654bc47684d7b0336a3afe813ef1aefc7dd0ced2000000000000000000000000000000000b40dd6bc2fbfa2ed277d88f77aded330c54c1c46a781ccd039b270ee9b799a70855ddb1201dae29a1b124dde1e6acaab1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef00000000000000000000000000000000054f284874c53bc914040e6751ddd444604d34a38314d8057fa0f77978150fce0add250a6bd8693ede79c9f6b2e025de00000000000000000000000000000000045f6579793d166198d73ccd03da2e907efdb31b54b0b0fe3e2f1e02edd7d9cc0c08af089330d53aedb60aa7cafb0e0ca41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000189aa0df86ba479009d4bfb8608c31d3d49f52f1bf758e5c05ee9e5a673bfa15e1c6c37a978c4c431ea035cb7948297500000000000000000000000000000000120c90261fe77d6f41a42a170b28df1c9e6e0cc4bae247303f399d3be7c6ce8319a43e7d551fe554783ec5ccaeba3bb363cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de0000000000000000000000000000000005e990869491ce375477b586b63641ec71adf226c631a14ebfae3514718ce546987c17c9ef41f9005c10eb04909a74ee00000000000000000000000000000000141b8edf812a2918dc9a2242301a7e7f6433a83298be9312cb48f0d3f0c819a4368ca961a0b6f09f9e077cca6111657e6a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f0000000000000000000000000000000010f3f93de5573e42ced8278a7a12b58086c04f8b862e11f256f26731560e606ab81d61a1090857eada5f8eb3afc363c400000000000000000000000000000000111915ab2711479677489dad7695cb02626a0525ae9ca51b5271d5fb6ff438d99730369654240b05b5d47fe00847c6327d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec0000000000000000000000000000000000ca6c2f2191cf86c596b439de0e0df79b441de41c7661d4b80723f14337a379bed9b97958d225700f06f8be5401399e10000000000000000000000000000000015904391fc3cb879147c2b5192641c4ddde11ca8129c3a03b82f5f824b2ae60b3a33c925112d2de94ba3eee10761da528bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000fefa6dadbcd8edf2861c6ff4f5eb501a76507b1fdc1b8cc992226a7e5ee17ea343cff89426c409bc36c2aa3a8f5793600000000000000000000000000000000166706cd1ae090a41ea211d1333d360a1e34dde717979295a0d6a870932f31158e43ca041d1978815aadbf761275953163b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000008f02061fbfe82eacd770520b46ab49bc29bf358468adcf904854e39b30ec4e363e80f18eeec8064947bd8612c37493a00000000000000000000000000000000138888a1fd168e9c94959cf026605691b4100a828c3a75ce95f3dbeba2a21d8a44dfaaad834dbafe28c12154f41f652e70de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f531500000000000000000000000000000000002a07974c00de6936c31202e2b0c76c30ad15b6c42393d5c5d2b1e0d5eaba8b5680d3837a8029283f572d43d2944e4b10000000000000000000000000000000013fa3f905a5618b7aa3ee5ed37055f0472fa361fbe07733f9c500657338c62bd4cc3b0b89e8223894f365a58100ee35416732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000000775861019fd75c201b3a23141c8e962948ce38fb0f15cf9d08d56ce0dc574300e0a6ed90a7c50b8c71a1a9c466d16200000000000000000000000000000000066ea30b3a1bd410e3c70b1173b91d3eb9fd0be55b2d583c4be627c3aa9cab1b2a5fe13ccb37d781965b1b121079916c4a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc24000000000000000000000000000000000ec3ae37e6e5b0c623534f5c02d998bad139394daa28aced4b9f781a5ca671a02f1638cddd3bfb5124f9c5c830cdd9e20000000000000000000000000000000002688ab0be331d6f8246a54749c54fc111d2f7414ddcb1f3b42724e5bf14cb8ff3546a3b9be6115d91f62af8c3eed35efa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b890000000000000000000000000000000005d5e2230d538b05b690e878c03d793fc70c391e853b0ae3609f81a7f24aa6d5a67f3138308328783888645d1d84a15c000000000000000000000000000000000d625eed47e245ee74aeb91fbd72981c4f2afd53deff7ab478f32e2a8635431d9ab9848f7912dfa4bdf8ee7201ff418bc4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515,0000000000000000000000000000000007cff832bedad3caa9c91ac59292828e79811f91c51c4799e2752ac0c12e2c47a2a07ad0521097af7e4a939f3fd230b70000000000000000000000000000000015037ed0ec4981737fa94f276aa9a4325a2267b90f34844f14a92d677060a23e23e3ff0b3df77e7e67e85c23b28cd44e +0000000000000000000000000000000006984b92b5b868004f39ebf04f41084d03704732363e65c823e5ad4a0a3fb4c983ff9375249bdcc2f46650921031bc1d0000000000000000000000000000000019b9d69589cd29a9909af5a303586aed5e33650331b9866a6d959b8580ca8312ad0e96c7214ad50db7502f50ecdcdafb0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e1500000000000000000000000000000000173433f7025400852ffdfec020a44b545b365158ba8f919f434fcd995c0d84509c77d8a05405c79953b8cb667047690e0000000000000000000000000000000017d73ee336ea56efa64038b31d5abb6650c4c6f7efe67add40d09faf93fdd9fae44732bb69dbfb0dc8267c4d01d8aaae6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a2700000000000000000000000000000000184ef5b6e309fee5030e2cd8c6c3ec49b1cfb09cc9cfb349ed47e17409d9c478e8e54f285a3b3a4025464162b172d33f0000000000000000000000000000000009b78ea5d2fd2113a4bbcbbe6d0108bcf27b60ff435b5b587e91155eb0ac6ea35c27f276b7e11fe5fb59508454fd8bd24e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000d0b8aca446806ab51b4a49049cede15587aae742ce7d80c2a05d255429c945d1337b4fa7ecb8f2c3b7c0b0299a41ad8000000000000000000000000000000000bce866df7061aa4319336ba1f876254a8e0faf3faf2f9ffdafd0ebd7d7d0c854c61b476583207818f484ccf7faf90fddc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed70000000000000000000000000000000007018908a64fe5795ad178b8bb1c8540ccc5c78ddccf4e6cbae72bfb84e794d23172998d29e568b186cacfd025962a010000000000000000000000000000000004751f7d225407a8d68b4a196e32cb4c0bc6a9ed9f2093e4242b268d6c5df978b8595d8940f59be860b66310bf8a5460f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba74000000000000000000000000000000000f61d66b0539c7ce56da9308d0ccfa9245158541b2d1b14c381ba53471ae9944ef3ec9f4eaf52c95d5d0bda92d6b9a47000000000000000000000000000000000612e57aaddc6eedd9b8a08b991bebe6f5cdf7805c2cd4de5853856f11eaee94c4c2e0799254f98348cef63236cbae3980eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd5107000000000000000000000000000000000b8a8cf0fa6ea9f3154eb35994cfe2f7af4252adb8f26d718163f2bbee3cf1bfca400f4d3582fd5fd407083e0bb48ccc000000000000000000000000000000000c3251d0d9e8520b3e7b43acdef58c75348786654103fc770c7ffef8593b169bba3eaa2686791f919fc70f40a171bda8067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b10000000000000000000000000000000008138d173e3e8f5e63f6aef89cf2437690dd0c848435f6032f943ef6cbca87bd2a622f9aca825b7caefb497450dee4c200000000000000000000000000000000183379ed3c9a6a6904e169c68d627bb828a05a93e38ea3b7886db2fe6d1015319d3887136180ab7dbddaa26b1fb3335f34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a00000000000000000000000000000000083f8fe152f7edcde2c81107eacee9c58ce22b5aeb10eac15e7df1657a813c98b182433655380c9e8ac18efff2188b5900000000000000000000000000000000100b06f6129bd9063d2841f4c244adf2dfead83e23f3b1586126623ec35674ecd6422efa0e86ed0502a83549551afebd8d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e1900000000000000000000000000000000072c644635936a91dcaee40e3b4794e634c315a39a9cb5cb99ef6784b332fdcfaafdc80e228cd19d0104d5796f584c350000000000000000000000000000000002318bea9077484e9c1937dfa63774b5ecf6fc63ff06e5cb653553d5111a981c09c907069ffe11b5704ea60a9987328329ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c698693000000000000000000000000000000000aa7249112c7897c9b1f95a7d8299790a25d155dc9ef7b1ad6dd7b186bcddfacd4c77ee95e634b5f283c8caebc00b9c30000000000000000000000000000000012e31211b2bc88c568e08157da9c3e3220dcd563cebe44653ff4d62f8c306ee9136832704272317342f634e66e8e66a240bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000003a5576b3663114b410276a8c537a93f790276754913df727ec6c0a684ab3c705ec04b8bac882bb9c5223702860885520000000000000000000000000000000002221eb21003c6512428cccf8a9c775df9b72ed8810dada5c92463e6cfa3d619f22a22e314b9b8882c9e2f609b73353a1574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d700000000000000000000000000000000111829f79d4ec1a80533f76f32503cae2842981e29ddf9a376d16ecd7037d3e0dd1f8cc84d512fbb39d58564c019a559000000000000000000000000000000001808e65ee7f31a1fc15d187eebd76c63a3158469099bd6acddb0cc96354072f636651137d060efd850fb599a6965044e6dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001274417dae37cd33b2a3e086f327df292b6f997e5c93e71add346d6e5f6ded135c8d6047978c10c5c38752006b7f76910000000000000000000000000000000014f867c58d3be7b09891f087f47c1bcdf82c16f899ba960d8a0db4a5eb66efde12dbee75e77816cf9afd4877d9d08f32d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f423082000000000000000000000000000000000d98fa6b2371f65f6f0b62133d1a294a7faa9949c7df16818657a9757fbd8381222cbea98a72a951e4b2b69b216f705b0000000000000000000000000000000016331e8f0661228b1e5f4df59a09de5133d16e06e1628afaf8b2a1160961ed9738400078bd79cb5bada5f99748ba220b19d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb0000000000000000000000000000000018f129d1799d9b46dcea6d239679eb64f144adbe1a9561044355cf66b4b1158513406ef4423468b5ae446c4128dc03d8000000000000000000000000000000001669ead3f97913fe5448bda1bb0be354fff223e51bda5eba9743526e964247211e9cccf75e6f99c6abb5b8912af94f5d3f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6,0000000000000000000000000000000013426d2d18267fa615975295d2029d2e5730fc845556d501c8c6ff8442cf0f3c7facfc329f6703043bb2d45acc1639130000000000000000000000000000000012fea8316f8eb7cd655aaf9cff8e395592360eb6d62bd42f6e1d1e27b9b54bfb7be5b56791d5ba55a798f073f9b5634d +0000000000000000000000000000000018eef275d602fb152cee956b04313bdbc8f0079da7bd8d6841fbff1f63a9626f17ea3f7a8332023fd9793ed2eff3990f000000000000000000000000000000000c41214c40c5c65e79f561b59d7ae103cf8c60f78c2b0a16d96a2e714eb30eeb0cf748044bdca077c4be5f4ed155ec50cdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c90000000000000000000000000000000001e5db25f5964e3a5030005152fbe9c00252e37dba6febdb7441046f734d4b86d60334d91960b09bd32d258b7ca2662b000000000000000000000000000000000949bfe49b0256a01da76f5c2270cd0b6ae70fdbeb55f932895d0e72d94eb6db236a8ea40d419ec6d9354c611b8010a918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd00000000000000000000000000000000158addae39a79638dbd1a7cc280e0a24d18a4914ce4e290f8f79c147c4e817d95a01bf6b074ef8e819a124cf4e3bf088000000000000000000000000000000000bd2f13538d08742b3bc7b1cca9cb9e059b4bcff76b49a189f506b4bde56d8a58fe0bec2f8425ba5d574dcbc5efe0e93650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000f8615d47e4327d862fa64ff4b9be14df2cad729b816ac7bdcddcb32500b6523af3303fe36c0e93b638992c671958d5c0000000000000000000000000000000011aa78c5d0073fb9b34235555bb2e3f27e55a1d576ad4e3f63974cfcb2646c6ebfd6e595d46613987c0c8e86846845dc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000000fe66db078653da2fcd1490a36db9371039f3630bfa4d574cb700b19697c7194e8e44453e16ae71db6c9001e18392a76000000000000000000000000000000000cc69605c26212c6a088b9a5c2cf6024e46f035e4c64da67383f294d6186bedc18922ac891f742165e3f09fb1720d476283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000a280b29948ccda96a2a05ceb9fca703dd63c65ebe18a0002cf1c63b8f64282cf9d3d4d73ba3a13426f253d09f83ebbe00000000000000000000000000000000146f604d1e90c4a14aa6599ff5c6389e426232a2dff39334f3390006f021f83500300b7b0f1585ad591acb1e0baadcd7145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f4720000000000000000000000000000000000be58255d1f227af95dc9a818204d687064d62166c16f1de052aca69a37ae98c2a73a9a9cc6cf187128e5b86969e2810000000000000000000000000000000003f1155d7e91220bf0b80943a16a9f41e4def1d5f8ce44d95dc2f9099019a1d5e770158338ec248eeda7c5af412890cdd9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af00000000000000000000000000000000077c3ebd0169da81bf07ab1bfb8770732e4182a30504cbdc8fb1abc49f31d698c17f68de1a6d8bada62e98e09bcb22130000000000000000000000000000000000d677a33c1590cb55c9c78afa455fe2b349c465e90537a73906343aef577afbfacc8e157ea6f834ff959f3dea5941bcf262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e000000000000000000000000000000000aa0a3898520c5bc19d7f3a8e0710585dd08419b39d9bdcfe12f7baa6b4cecb50bc0d6e877ccc2518e4d0254934669ec000000000000000000000000000000001376af22bb714adbd16d8d41ab503066fbe78f799aa8c1d8958eda9e4c8c6fbe119e592f655e0c3f93455e8acd8a2bc14f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a80000000000000000000000000000000011b1300312d0ad0352ea153746f051816693008f2d0b980974bc354996ebb664e910350e28770192f59c053f3f2bf00500000000000000000000000000000000125d87c441a1dd88f489514b1d550387aaba857d5a6bef20acfdc0afdbba3e98c2e0aee0528cb78970395a9da853ffba25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d0000000000000000000000000000000006a05ac512adc0dccb74c7b4c2187763a6ba8db9e290cb0efd1325b7a463e0e14a3e7463b5cedd732527dbd131246c6a0000000000000000000000000000000001c1b41b6d5c823c05a5d6db55d7068409f5fec25986db6e2689dc6ec3e0d85749db6deb737445c5feacd69925c5dfc44ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a616300000000000000000000000000000000143d22823412da99f7b87a794662bded7b7ebad9742e4d6fffd471b1bdc748c6f1b5bb395cd0a79c7291b9e8081825ba000000000000000000000000000000000f2b98d54e293befed0a97667791ae35494084229b2a25494fbd7295a04f03173a52efb8ff9033c4615ad1185d4e9032eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a00000000000000000000000000000000172919c33fd97de83b30740356c2bb2a9c97c3616d9f80a8d8266e07a1de21ad974ea796d3cf56660fc4e0df263a27c80000000000000000000000000000000019afdfd10bb736e8a6596db59f4f9a8244e585fa81ae315a768c8d91716de32d42fb75a57da238dc597885f083049a769d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b046000000000000000000000000000000000a944d2667a10dc5892760cd3e13289785f0a5a461068d70960e6546a0543474f92d68ecfa96efd19619d976af2ee491000000000000000000000000000000000a88a16dba3fa6cb5ef21015b18a14956ec9ec29650929fbd0308fa59ac4aa389aa2e306a3a68fc04e062367a72b3f861e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f96220000000000000000000000000000000014118a990f2649838954ab911e795c453ecd0d700077a5fffd1a4f303087074d595caf1b20399578ff1e23a2cada7e5200000000000000000000000000000000145bf8164b82ca5f8f93d89ca65a894c6d15e38da2cda296a94aa1a1efddc4d2663b8f09efc3b2d78510c4dceef8558fe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000012db33b91d99f44cdc785470e67a781b4a72ae2dcfe4555efe5b7527b9a76de8e492c4dc7793ad948cb46070eb1cc5be000000000000000000000000000000000ecf06e454ea574dbb9ba6209577425a58267d01f29f8706d06018a9caac994d2dbc9c6ca9fe3ec01aed9aa2ab886c60dcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c,00000000000000000000000000000000003747597e0f9bc39306319018fd08bc936c4d37cc6f84ef296df5a36cebf0aa46ed35ed215ba89a247174fd72fc7c1c00000000000000000000000000000000150f973b61f26aca79a3f7d1705999308a96136b08673322b4729f16b471e38f27e66996e2921cfad0cf889878c2ce27 +00000000000000000000000000000000046e955a4631d1a490f92cd40ee0a31c096210ead2b307a7aac60e84efc04898da5d4d9767f1303ad5652a0e377f0738000000000000000000000000000000000afd054be493fb26c7826c9c1f62365ebb28ed853bd3a45d266f4c690a24e179b2eea5261adb0bc50dd184c165231d2eaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b6000000000000000000000000000000000ec3c20a24a5f9fa7c5754007407d1aaddaeccf3f7956914ed3b06dbcff7f15c6d487a3b71fa9aeb61352698a93ed14f00000000000000000000000000000000086f3cdb1e21cf60a7a57e7ea7e00b4698a837916eb1f6ac1c6cf97ef2abd48292ecfa471ba7d9b8688b6f0dcfb6af62c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c000000000000000000000000000000000769b870411b65a1a86dfdbbd7dbb65feb708f9f90ee73153e42f7141cc660c50f41835ee44f58c7ffa136b944e84dcf0000000000000000000000000000000005f0480b4a35dacd304d8feca77f8580f66396a6434af09b98d57fd4f9f781012f3900407a49f4e0aca8d3ebddd2a7bea1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000000208f1b01599c969333ddf9accadb24f1c8239f82f5beca72d0d6d823b59a3b8c450e25a2da32b5a8cf8c0f47137e04000000000000000000000000000000000054051408658f025572a45c731e81f3fb88d741a632f1e2acadc48a1f257a69481c9c11e655c226d8e0623d34fc9fc158810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea0000000000000000000000000000000001738659b582e3667cee963fbea8cf695daa6b811dd808e724ae77db2060f248accf645db52f9838802c5322d993488e000000000000000000000000000000000a36fe571929153dd774fdcbaff2b924cd3f0ab4aced47d22a2662ac6f415b89372406c4ea5a0a466d4a4c5cfb02ad7d93e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a000000000000000000000000000000000c70dbc5f707fb949a2e0cd57e0ba6a5d28a2d85affcb55bdc9fd24a3fe395bd78b7431175a629475c0932b769b55d6e000000000000000000000000000000000a49fcd19bde4473bb98384bd63e96508b539fb80e1e0cd9fc9aedaacba0c36d705ad16a47f345c083401c6640675823eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000011236c10b9622f4e3d468d91ba9c6c072be74aea66f5bd77411193bf2358a03fd47d029dc7b50343ef72fe9bc08c7ea3000000000000000000000000000000000b923cf7f612e800c2c52b51203e12a72d6f106c0d047d1317711954cb33d44678f509da27f03dcfa1d4482a9cc2eceeabe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000883a868a58809bbe3ec9df32f8b963030d71a3ae97250ee9aa8446a8b1a4428324f22fddbe77b338ca58de26b1ad73f000000000000000000000000000000000a49fcca1f052e82fef8913b64268a33ef1d2ee213ce96e60a3a1842aa304c63cce711bba8f523302d9252e3def20e3fc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000d1ed017ef4702bcd3bfbbcff36000af6a1d26ab363e68ea5629027e0b90352bf1d8e03c13a7955da6c15507cc1c9f47000000000000000000000000000000000e09830e54fe9eddd416479a1740f6f1b7693f2d153d322f27779b16bb6451d7657df85a55da75a4aee0a2e33b3a46e637d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b0000000000000000000000000000000001f902e2947de38842c207b9029743da51ad0dffb61615b22c73d88739d80c926c07f97507ca3bb830c66661b397dc1f000000000000000000000000000000000d8a1d29f87b3335287142baf613fceebe9d4765d29e46bbc9e459af5450256295538b49081d849f3253f07357451b6e4904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000001846aeb64ead3a9b6da3b6f5de234fdc98442bbdc402af2d016c9dd25de8f9ca09269a3f01a812187ab7427b2bf31603000000000000000000000000000000001775e3fa3bd35f96faaaf9c3ce1d2391f89340f8d533e41a1d637fde7a2cd7ad997e50a6e9437468a1d5940e4004bc9068911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb490000000000000000000000000000000008aff7ad8d3e83ecbf5c3fa2cc9a5328531b1dd6e30b2aa618aa087281202de8f4d356586d64082fb039db4c9ce6c3e40000000000000000000000000000000014196e8ec67e5f0093da2b1233331bf1e90a8fe1db52b2629c0d25e3c181d595c03bbab3b399c87236d2353f1ea6bfe9481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef00000000000000000000000000000000018ad28e8d8c1d9dfd8f8cf4e60214446a988285005d92e38d46ba32f619e982cf96ab10b605b1e378d7b46b54282ff300000000000000000000000000000000029807f431a2101ac341241af021ee35c47e0ffa1975c982f75c10ebf3ab9081d294578288a5c308abb074b3e3c756c672780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b0000000000000000000000000000000008cf05c3d3bbcc63ee761f7cab1494299a3e2274ebaebedcbae5b35ff33bca129d79f73ea77152f19cc67fc66ff774040000000000000000000000000000000009ab576dbf0e8cead9450eea0a506c83f12d09fd2267715a76eb46602756859146e96920174dde3a361636986a3d38e084ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c6567000000000000000000000000000000000006f62bad30339a1a912280ba5d982bdf0d3c04ad9051555eabc32eef501e80d996f183a990ebd17301ede13db85f6b000000000000000000000000000000000b0c4bb1a10f8a281b83384ee05be2d65d6dfcec36253b9101cec7f1193f8fe3d29333034de96dc62d18a97153ce1d153256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd0000000000000000000000000000000010afb2bdbea9f6eb0c75ddb0a4404116498920557a5d416c6d855978e47aa90da70f29519ab244079762fbf965edcd070000000000000000000000000000000000b8b62a1e52eb3805056576810721cfcdb5b0d94759a11862cd7b0a88e3ddadc0efaeccfb89662860e187f8af2039f8575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b,000000000000000000000000000000001422eeff2bf06ecd67e0b310e7201773577a899fab8ee7c5b6ef5ce1021c9371e106c49a6b607cb3d58852f0e671000e0000000000000000000000000000000017ff4ceafb7c286f2036e6bf47a15e6695beacc7b988dc35f8521459203f69975d297160dc67fb88c4ed2fd7b61ccc0f +0000000000000000000000000000000008fae47827bf8786df7e9f8cb38a8e01354ed4417a05332e45a94f93a5ec61f11d517f5554d5444001ef2991f2e7eed60000000000000000000000000000000005cd17cc813442f45e7c2fc542a6359b16db4de7749677b1575f12ed694514b3569b722ab257f7678a230ca3ccb6e0ed1129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b070000000000000000000000000000000005aaeaf87735d9e9895e8703177faf8b11bea34aaa045852c57e9b86f6283332ab633f3e6947b84784733f6f73b289580000000000000000000000000000000004957220d5264c0ff61dbeeb0d0d51278386227a9386756a042df89fff5ff9a4d3e3e52293cc94ed729d00ed3e70b1a32dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c900000000000000000000000000000000049bec519df011ae5f19c85afb3301f41f71119ea6cd9eaffa9a00f9cb901681eec5f3f694ef9b4fe768a25a55afec560000000000000000000000000000000011414953ff3fec28aabaf3d62236d6a972da12c42102911a3ee8e88e188970a11487df719a739201b31fcda4e52d7c515a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b010000000000000000000000000000000003e5345484f59b269fa25b659e9a43573d4191c3c02f5f94534bfcd63d9abd57b2f3ab92f9fc746a852b185a6ae2c778000000000000000000000000000000000b7d7648096606b0c3fb93627e484eca017b95b27a8098e5dd332bb45171793570c69fdc16caf5b16e65f68c817de3bb579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000c6afd550c55cc41cea88e670443d97c6419a295918dfde1d5490718f18ccaf8fa0cb68c42fa2cf583284cc70bfb0a11000000000000000000000000000000000f88ec67e9ba0e169ebf93fffed1fb14dd1aa3e1a2fa614a140c1a2147fcf051457cba68043efdb1b851bace84078fd64be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d1600000000000000000000000000000000143a1884ecb4121e2c1c0cf2998b690d7f01aa3deec1a2ae5542647a3721f7be47c21ca071f92d74d9c3d9027b56d9c300000000000000000000000000000000113b01f060d10d95776b35c2b294216f768a323aececb308d3de24299dc12e55fac82c3134519456660a3465abeeb5950983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa000000000000000000000000000000000019a5620f3241d03d63ccaffdfabf7e99e784399929cfc3218d6b828d7ce137c9c6cf3ae830630fcef3cfdff705490e000000000000000000000000000000000114347768e5c8109c1bd47623eb51764d4b3f63f333677bfc28b143fcc1142f4d9094b2355408cd8c412a37a4579e0706615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c00000000000000000000000000000000043c0f4b09396d4b14deb7c5027ef6cd2d426fa4f93d4ba9c3647031d557a759e3426c113fa3949cadb8b98a64bd69880000000000000000000000000000000017efb6ab8b2eaa0768bb740cc8a4e5ecbad81087cec2a307e5f53b5f431d19e3467dee84df6c6453ad4566ffa2380c9ad77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba18122727363900000000000000000000000000000000140f0efabdc88a109da948494a9fca5ff790ccd6c629a088cac62e043e00e38c4281e49173ea0e423152c5b944d80ac10000000000000000000000000000000006d3d01cd44e56a4cd62d88a22c701b42c116082e92abb629e64040f57a240d71718927aedbd8ddef910198e1bb09c6841f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000000f20ace44f4b981adbb3035e450a656ce3d8464fbe4c45b9f7035c00aef11e389cccef660dddc025786d4f9216ef60c1000000000000000000000000000000000d5fb0a9e9ab03893a9ec61675af29e88bb30f3b61e05d7c5a3d823159bf8e641ad894ebedba4bd681df789e0c3d2547c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a16000000000000000000000000000000000dd38f99872751b4571253940ca588424190bae80434a3126a7ab5ad1383c55ad769e09179d148d151506e5cf5007b3f00000000000000000000000000000000032b2b9a8b13acb6589fea9e8b8d2535285bb32ab0e519cf8c63ea3e25d58cea7f9fb27481adcb9475abadd6f1384f4f8db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c88000000000000000000000000000000000c696064b7c9653cce986e119686b2e01216faf8098d494bdf6d302c4d176b24b05bfbd70b9ea3ecc16312f899f887180000000000000000000000000000000001b5b8d333dbf1d84feaab7737d3af13d3995d3ea976d9ea1cf1d005090a809fa6c210a6363495c2b22902442fc5080b70751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc000000000000000000000000000000000082bc6c7ff7924b88b4a6cda58295d050bbe8087670bc6036b5bad53247b803306ea596ee0689d805e7b4de65a634eeb0000000000000000000000000000000010a79825c716dce1572e6e8886f1c698d730327f195871db7a9b6690e9ba1dc38e8d92b34ee32b33705edc021f42349184bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000cbd4ac75eb0928f366d3b99e05799bf3d9dbf187e557f211af5ae514101961ba750e81ede07cb5a14c49884a9b55b980000000000000000000000000000000004fdb80f44f89e6cb44b950735703653152466f30a410109a24b555c4e6907b2c1d4f54c9c0d2b7954002a74f1b65e23d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000019a41f73feae98fd65e365912f5bc6c86142380b2633feaba440a6c635ce2bcf7f871f1f033f93f9f8668360da3898090000000000000000000000000000000005bd1afda6a52adb550fd9bb59826bcf492cdaae8e9600e517d77832a8f3ae8777756421fe7640aae0bf07518ff695a66940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000ce2a96c1ac3e2cd01ee4a20258436b62dfc2efb96a7148cf887c25d635aded48d18d38da7347abeaf72d73d613fafcd000000000000000000000000000000001773ef3bc5044059bdb5100430d4936f328cf876a48bd30784c8d3767a119bdbd5f1f97d78d52afadc42ebc85f912f0f7b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e,0000000000000000000000000000000013fcc5da42975bad80f3447a1ba05d9c6a51383921164ea3e93e983e24055f6398fe773b0e7a50d98568d49de36e295c00000000000000000000000000000000188455bd9ca4a0d3174cc8f0794d8c35356f697e62265d9e3d8e72bb2d1450caf5bf79dc5ba78a363a23d2ad58891165 +0000000000000000000000000000000000466047055d438bbdade1bbb00a7bca3ec0ce30b042e56afb9a25de1407d5937038e01e3c07595f46bd00cc8202d2200000000000000000000000000000000016bcc696716c21293b68d4f29d9cf675d447b726d579628417cc457043186d54f27c28b47d2e430041f9417ba323109dfacfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe2433513000000000000000000000000000000000e8eb8fa4c0c2c86d0e571cd4708361e606c9fe789b60e099278d35d169424721bc789a6048774d739a5ceff56adc668000000000000000000000000000000000ddb7d2e6094f1940dc0f41509bd430163b220aeed1b8c0a2b90e37f791410a35d682b75223b32febc95500c7006f6626546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000001080ce47aa1c38db9c71d1834c0b5d59676b0d938ba55a62daaf50911d23e286b3b813c7261bfc19e95f3bc8ea3b91fc000000000000000000000000000000000bebf539c3c03dd260d579aa853c28ae582b9c904ba2c56bb1239aebbfae10c05d9e33c8e1c2bf90553025d3279572fba9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc000000000000000000000000000000000ba078b44f92e90fca4981c66e89c5490b34f92e4026d826c2076a995269e4d4fcab419a508b530793c465531a631ead0000000000000000000000000000000007c19bb972c27c00b5b1a8731ed7dc9af8270187cd26b1b9d65cbc96688fe2f0ae86ffe753a50b4500a46c01a75a93032c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000009b70de2dbfe9af8ae771ad5bf0ff962c9f906a3637f992b08946c864b3d1dc996a2ff918ecb3c9648ef9188b15b624c00000000000000000000000000000000186a9f4c06ce9d5a969b959e4b17d4428393d02d0e7259fcbfab8898481bc97582ccd0e1d87d1735e28dde10a99b683e9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000005b7dcbe86bb6e6b328325141c1da77f8af531bf1463bf3c8c94812784314fb13e457fa461c1c51aef0721c5d6ceb5e9000000000000000000000000000000000d9d1ac39a5ecd61670c1b0d061d93a198eca1d294d2e64c3f9e0a872e7c93212ce7835ae0a7fc2a42ab5c02192d70715e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee2000000000000000000000000000000000741769993f2dcf5869b8153bbbff2e6e5d429fd2d862bdd590fc50a8f186bfb105f5d57f736b07d919bf0dff0cf4094000000000000000000000000000000001917c91f954f68c6406d6dc716dacf729a8c4a0de73e04cf0ce554eac40d750fd25b289127023af299c6f63372c01b7d4a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a2000000000000000000000000000000000bb29ce10d6e571e62611364143e08a60eee5ccb13dcb77f17fde5829ae5fc025b309c98f892aec1fdedb7d1920e658c000000000000000000000000000000000ab6fe2dd5eb1b90f15a3632749c351ec871038f0550dc54cf1bf2575f80ecb8a3c0d3c1a333bdd803e22fb6bd3e64bc5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000004fe17772d4205d7b1d0cce0db3404119707893e20f6b27138918d2cb0e4de49cd5df1258103c1fac903c1a443cb62530000000000000000000000000000000014d8246911dc40ecea823f02c0e17e690a5f66848223218dd1735cadca1a0ae89d7afbdc727158257d2cb248323c55316e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000784a1b282846404f71227064ff1a97766781900136d4b7ac73bab19cf8e03b449ddd35360fdb6dcdac80e335ac5cd1600000000000000000000000000000000074fc137d93decad1cbd4b753fe9ef3b8b3445c12e358450ff494a1fbd6e192ad7a4812358d85f6e3cefedea3aadaac6428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f000000000000000000000000000000001661e6d386aa6516f08decbbac9c1c3411ae9cae62b05037dd626a2e2273eece64615c54a4d73e09814d497067f9e6e30000000000000000000000000000000007543030f8995237f65cec9b69b0356a29133d8be27b5f79aea580955042242c2bc1c6a01539b6b55ec9af96db60b394ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d9700000000000000000000000000000000148597902b3ffe4ba8a5f9012e699a3cf189f58275557d98d132b72d3c34e5faa0953ec8cb10b0228a23803b70836e200000000000000000000000000000000008741bbe372a1e5a697e7059a9e80de8a012b0cc7b12c14bea098c16cfea154204d4e27753f1a8fae0e618223da14fdd88a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000000cfbbd4632e8998ba59721686310ec115b98ef470c3c4bbe427495d6d95d06ec6180e64b509c4c06e32862e17939a2cf00000000000000000000000000000000060042078794f4539a9b3e3127632c3c8b46322a669605d1774e995c5d82287d3d9be51690b4b5df6de8d55b20941dc630eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000007e67da2f320e1ef0d3afbd50634aff753a2e2104ddc03244a0c79eeb117ed1beb7316f7c5e116bbde47c53d47e725b3000000000000000000000000000000000b5399ef864331db729724870b431d8dcd8d3279cd00a59de2fdc15bbdff2035794025edafa21fce97836e93b41aae067e8dcbf708682225fe3f71b7a687da23de5ed188e40585be05533580121325770000000000000000000000000000000014bd7f0effe81cb626f92422ae7900bafa7f4c2d51d4ee6926eff68b60c7f41e667a57bb0506f7c36d3549cf154f6cf300000000000000000000000000000000050aecd688a63075feacfd29d1ab6430176dbc5ba6d406636a6650427a9e0b0d51df51d8dca27665b0b6c60e08d5b087532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000009252c5f7f7f3b36c5dd32991641c9f8244579960fd2d07a8641b82c5cb1768a36f4e5ad623319ef3f7d0c670fee58430000000000000000000000000000000018e432d33e506ce42bf3d873e36ed6ede0c9de44203cdd453cf91c42fc2ddaadaadd2e3870c5f5c171cfe76862ce44dc3ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5,0000000000000000000000000000000008b38b298fe2dfaed042b35ce73c76ece7537fe5181ce647de756349a8dc48d3296e243fc7613abb10e254e2b0197d7a0000000000000000000000000000000018d59a69b976b1bacdffbea68d523da3fd0d2910db0a509760bce56bcba36a55fbfe11cdc14cad50e6951ffdabf97a64 +0000000000000000000000000000000005d929298c9361736ef5f7c83b6a851c344d72b7bb92a8201d629bf9bc1e66e4db6dc9df64ffb41a11eeeba10be52ec40000000000000000000000000000000007962e1b1b823b770b44eab51b3b84fd7e0e57a2a3f7eb1ad9c3ab02677376cee08b0a2977552a0f9399584b576f17f148da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c0000000000000000000000000000000013283d9b3cb5ca4c3a39517adf466d2b7fc90f4895a24effca7ebaee4df8735c69993c7cf2483c3480cd2df4be04366f000000000000000000000000000000000fc94dee82225161feb78f2a7c951c41f43ff3c1109a824b56c01854688feb86e240c9fa48534809354e74ca8360cda4def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000000fb727cd02c5f69af676f9cfa68cc4363cbfe5343e304ff5180ed1f57e6928fb808539276feeb1e492ae2455f65de0b00000000000000000000000000000000082d09bb2e1f1585933e1b9076711803e71c2236ff78e83f5dae6ad492c1d723120ef64eb25c8e91486d102c2297c9e5b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000000882f34897651c59970934848ba13e815710b4952dc0ee1abd0e04ed82ab399ccfb16ec966d010eab51e5fe514af91ae0000000000000000000000000000000017a32754dbdae7a2541eddba29cb8ca85a0c6d189f9bbbfa24d477e9f1ec2ab8f7dd2a5aa7a596d3a2af916ecfbdb2c2a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000db9ec112ddb4a9c6e371440d0c79bf043c5a3c6c6bd613dc031ce9b81b49a32b006a165ef29a8e05f019b76b3cf520c0000000000000000000000000000000002485dbc3c3e2aeafcf18dfecd842ec48b2e79d3bf7936917df759a9ca2e25fc3f137eb88a701f5fee1ccbb06d5cb08c0b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000100d1fb78f53423c8cd60de5d39a004ee1c99b2fdf6847a62c73c33bb3d317ec06afd6424359481f8ff2d0730cfc9095000000000000000000000000000000000211cda7659f1e848c931ba1f65ca9c6021067ca01cdc8e87f5c742006f6dae39645553b69a4ae00ab6eca16afd0bda7571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf00000000000000000000000000000000004b07c2cb575e2499e333140e48446fdaa00368a74b87e607e285781b42eec39d1578d2e34701ed28488f160e9e50680000000000000000000000000000000001c2d66d28031aa91f6aacfdd80d222b4a0bc699a9b58b7f5d68bb9ed0a297ffbec3a6ba968f225732879f2f9907ca3954c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b000000000000000000000000000000000bfa7f8b7783780a2b0f5b9f1b10da77cb5904618b8c8a1d062fc94aedb0fce090d8c4e65515c0d05a471f2261d0063c000000000000000000000000000000000f45747e4b0bffddaa13c7e03b6930ec474735b6a0e779d3722330828ca26a07bb731a5d4884ed3eecc710356a00a897000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b000000000000000000000000000000001245cf167d097de0753d29ce6018b7777b1befe43b5709e8217b9f380d958e3e9298347673dce432e57338b313e84950000000000000000000000000000000000d697bf8ec405e252588e3ef6d979bfa60ba174da03266c3a2efdac176c1ec1341d737b16d53bda6ddf8be6e1f433ff6909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000000152d7f1e704619bbac7e594be6e105120b76d9bbc711ea40beb1063c2996fad70bc8f77a915411f3601e75af2f2059b000000000000000000000000000000001622a6467c13c534ff1fabaee8b29452d689e7f9e118e050cb91328b8078ef97fc82321b80d28d0c02f2b0a7b66f04a36d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000001c118fddc8df59e2d4ef9865d69cc044fcf870f296b009a2a471b1f74692f99e392b455b8b03d079b1f39b09e5fb720000000000000000000000000000000000032c05dc9eef5b55857956919f7a51b5f5225a45ca12d80208231304e66c77b24707a934cb9814108b44427e658d143dcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb280000000000000000000000000000000012da4a2c89951f85757c59a2630bde25c30af955448c972d256f1a6a259793c7b2bdc3f8734f4e312897cb6a3550800d00000000000000000000000000000000199939ffbde7b14b5f23eef23d4a660bf3f561aed38205e68d091ddef9679df9230a59e8cb03212df2e99788fa2595bc23d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000017a61df7581a341f21da2d1768fb41bb89847c88b2a0d7b61aa3275e376a46672dcb919eebf20b242ce83493c83335680000000000000000000000000000000013edc932b7755115f530d1d044c4afe71807a6b9810f555432910b54b0fef441b4618652fc4bc2ac5b789a75d2d276aa2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000000c73683f328a0aa252c10bc3fae9e786ccf183f1b606a4596094fbe10630d4418a527509c93d23e62dba263d86f88951000000000000000000000000000000000260c9dd70a1ddb422491a20293c18e4749427cbe9841aaa3370533b6e5d6fcf882f8bd68b7161434bcd5060716fdb97e3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e000000000000000000000000000000000bad0e395f46f714ac9d40865d588c06adb54b12439bb408a9d546b0a8ba5b3098c242cf5c17d1e40dcf7b384e81b444000000000000000000000000000000000e595304cd73c8c2a0bd1dff70e89edfab22be69bafa16877ecf669ab1e1160c9719952bb6103f31f2ff028cae0f0ea45d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000011f0037c9bc2bf953a3eb7d8a0a3c8d991e6eaa5f13dc1978a31f0eddb550432c70aad096cc0b904ee540e5d2d1ee4730000000000000000000000000000000004f8616cc7476fd0b95f7bbb7fbcda389aab60a88ffba3c819868f7ed6cf08e7c0c7da0958bcd957e0429b9a7fe120bafa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc,000000000000000000000000000000000e87aa419d686c55b1ed8ebf6e48d8f218d7198edcbc7db0dc3bb9581bb8dbf891dc383f27717536dc5fb7265ce1ffd8000000000000000000000000000000000a00646bc197307a7416aa9e35db9ce7eb81d762a065cf8d2e07f6470e424d7d59021be049b36eba2e44750a902f3124 +0000000000000000000000000000000005d5e69a8876b82b1de0b2d2a0d808c739b361d1cadf3ebc9c6096afbc19169f237774be6882caeffe47e86e3b8a33710000000000000000000000000000000017bf0fa8c247af0078d486e1961577d7977d0b4258ada3e158822d995188ee374d900c4d8b1ef4887fb03d8f6a4bf1776e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000e6ee0b0c7bb7c3f62284efda6bdbaf38bb5a72b4435b76928c5640fedbf9d4144358a20629403359fef5bcb99a795eb000000000000000000000000000000000e72324fb2decb0b0c7fa18061a41bddd6e2c55f901554de9be8ac7b2263631fee8bc77773318f6b13b2db7eb1ad0f3cd51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000102df0d54108666e7aa611fec5c09b72d269c72e6fcee7787ece5f33153a3999ba5f22adfafa461aeda64e113b795dbc000000000000000000000000000000000b77ab3de0a2d91b8c24a47a27fbc5b2281cea40d87872010b94e895d9589880385f82ff53fad55af4f4e462df1c9ef6fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000018e8fa3c5bd83b51b1af197f0dee78e5c912c742df0cae1b59ac44fb2b903ad5ee1fe9750a034d18141f09a2b8298f850000000000000000000000000000000001526a80337eb938420cf2e825e5bcf3152e90e448ae3b40ee61929117d35f694eb5ce9133b2cc664c520fa9da8ed65a7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca0581299490000000000000000000000000000000007f11b03e06ca74a35cf702f19fe29facac855d7f5adac59bbb8c058b1eff7d4748c886eb08600e0484aa976269e5d0c0000000000000000000000000000000010a5b0f723371690f6ccc5fb346874e58071167947d45e54f9d5edd035f2d68b4ef9e301f26ef671634121ae6145e44e41b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d65000000000000000000000000000000000db2e2ef96ea61075e063629eb031235543e8f39f012fd006e143eb137524976c5a81eb26996a4ec3619a7fda051df6d0000000000000000000000000000000015d39e93da2b392dec64c58e73740376552e69caf87ce9162801466e75dd1e25b7d5762099112b21411e8d8bc18806fe5615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc000000000000000000000000000000000e048ef3ee3bf3c41cc10b89b7d0f8b3f27c89fa0ab25542653155dfb7f8a7e8488a737bf2f6dab558910c9ae98aea33000000000000000000000000000000001357eb0945e2c4933b358970184a21b3369dd7a43a88841e94c3a388681f338770fdc3a32862c3a52eb251721b2979e9bcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000c6321bbc74b6b3a9f0c9470461c80b1713a5092871dc54dd022d3ade73845852315b3e85b53b74ce2b31d1780939d13000000000000000000000000000000000cdef7351c2923faedb211e79a44e0e02ebceb8103cec2ed7541a54bfafe3967791edbeb6d4b0da1ee37b9a5d77ac8f194c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c4000000000000000000000000000000000b925015af89d42f155eb1f5104db1128faa23101fb9bc1a9757266a2717d50e908c64c502a8d19bb1e8c01dad554e41000000000000000000000000000000000fc8c5cbacca685c24188e8f936637c7c8010f6126e9b9b49e7d38191af1246c2a3cf7ca45bce6f1e11c404919da61c3dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f20000000000000000000000000000000013fa1374d37396bc60386d07a441a7d21fb808e3b2ea0c39ca78a6dd70c473a8feb972e2981e50cab6288dd80c40c06a000000000000000000000000000000000f35c2a2897b35cd7417aac29ade18f86d56ba24848aed78a31513d5115bd964ac6711c5f71736490195bd97d2d5b507d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c1000000000000000000000000000000000799d71ab5145a8a4726cc5567d99b344971eb8bd6248e41aae02bacc358f967475f64169e1828a66905e4373cf5c9670000000000000000000000000000000017c680c55af98789584e073c3caf32373f58bea6ef7f839f1d5c39e512058360efe80a884ef5822bd5fee34869d028d5aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000000b4be422e3d3e96f6a6821c55bd2a37ba57de1bb59c8f4855b1f4b6906259de6be1c1be40523d5370ccc426b89478a350000000000000000000000000000000019212f598150b576c17c32a8f374db52c19431d7a60b99379f570189b3fa15edc75b807adabbed712268087cd9b89a8a106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a0000000000000000000000000000000009df46395e64ce38bc79acee751484ce1bac53c5e5233d3545df2ec776440e3f5b04239d6de10bdb086aa3c462fc6e820000000000000000000000000000000009a5c816b2abdcca7a916b1eb015b3d1c01f766e01264b5139e5a34a82a874c1efa8ef097d23b9e9441916a2f5bb17b4c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000023537e0238470f4d513d56d4ef8e244e3d853b3b10a893928547675c6b2d409ef6bbfaa299a726eb472067c48f056c000000000000000000000000000000000b48f21e01e72bb6ec384a1e8ab35db6ca032e4476f37a3282214efe483b672c34989e6d5c99f69473eb19e472d984bea5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed490000000000000000000000000000000013f1ad33a2016874de5265565049722929528a1c66b84c1876f4e4396f22fb2583d025c481d4d9aa2877e0062e842d7c0000000000000000000000000000000008a11522b3e6982a4b46ab6f1f6b07d33443780c914d4bcd50ef7ebcbec6ad944ab88b82640971e890a363dd92c71531083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a460000000000000000000000000000000019dfc9ca394e105c6ad51b130aab8a043ee58f26a0d8efa5beee59eb1543c2c3d33abb5cf2b23b0882a409d32f845b1400000000000000000000000000000000143e219edb6fad7dbd64e6aa82fafd05ed92bb46e526468cc3bc0d60c89319d3fa2032b5a617691ca2f136c9f7904225d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d,00000000000000000000000000000000027cfeeac9c1606a0942a95068faed1405a5cc9b5e631c25a0010ea14cae5b7a51b406fd0b8a9d9ea7be9a8c0f76c86c00000000000000000000000000000000106c07dd0d4e6f44fb1c3451942bf10060d16980f50b63005936e35b5add71e1352c48d1048e5d77cda79a09f86ff213 +00000000000000000000000000000000065d5c6ad252823540ff4a4639cd42443a3cccd808d40d8bd71379ef939b47c3027ba5593167b4dae93b62b2bd498f910000000000000000000000000000000012623162c0f025b16dfc1c7e5fa02f8af7b7fb0f2d42d6fa0fb01af45621f00faa4ed6da6f33c609448bc027cd6a4fd367c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000008b3c274f83f49cada0a1bbf0f56f6fe0f8a0873cf13efa42ff65dd6fda913102c2034a31a1a92cd154210d27b0120450000000000000000000000000000000001521dda1b2c9b42d7dc9822c64bec62e71c629d61e796165d9a18f8ab44056914fe5c8809f21663bfc70e310ddf5d952d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba9400000000000000000000000000000000109f72caac5abd41a228bd82b6649fab639e4d22cb3a9a060ff7577de61f33d32217a73014f5cc2c2a76582a6b751ae200000000000000000000000000000000059d0e9e64b10cefe03daa146c00c5040381ce6ac63886b5fcf19a0555a22a395a0cbe8b49c510c9bb7a308813fb482958c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e70000000000000000000000000000000001c5e132707520c525045a08626e014a84d8da23dc27b6320d5915e328c3bb0df3618cbd7ace26834920d4a8757368050000000000000000000000000000000008f5127405631bed295596639ec6091e97f16ce5a3062831102be951aec98c9ad34721489f65e731026029ae3eb13aaa50a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c3200000000000000000000000000000000146124bfbb9a3d253670be419f80998382895ad6237138044c55764f0d6fc07da5b70cbe17af3ad0c4b0dbe33f869e490000000000000000000000000000000011cadf640e78298347115e6110d3ed63dcbd251c48d3e21cfba4bd6859b0310041e67d212b54e63be6d68d2e7fccd83b3b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000012ab7e51b87512007e1baf2f3c3473cebb553bc2ea3d3146358688ea3167817a449ab9a7e0b090e00f47846da7f46340000000000000000000000000000000000702c1e0df68bee2666abb90bd593a17a6f9dad02a7d66102add9f3a525a1b4f1fefa3abe262852fd5ca357d2e1f02fd1d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e39210000000000000000000000000000000014635c8b9cacbe976733bcb1245eea410008082f240cc8d8246200abc0eeb6b7444f38da3ad93b1e029b06cbb12d42f7000000000000000000000000000000000d9aa00397e1799a82d73040122515b98be82052b784a4b385417f6e260e555c7c0c48a32ca1fb28224f75f887fa4bf86fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000009a0efefb9daaaba4b2beabf6c381c27df7c32d4021a4d722118886405414837cde5c55933de23ff6769a0a42933bdd700000000000000000000000000000000101c9941d98dc8a146a75f2fa48a8650b25ae8f6d943323b1c10360cfdcbebe220494660f4d6f7921fea006942e122ac06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e118862000000000000000000000000000000000e5b403702a229f36c9b83bab9335cbb4e39fe8f5e9a5aa4bace70361dd05c87ae356a40720c4a8214765d028cd161ec0000000000000000000000000000000006e447c61bce31b4843530e504fa1324657eba731a272ddae680c202a7d017ffdf0ad0656dc0984a1fa297f5e32c2740fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab00000000000000000000000000000000135fb22eca115779ad1295f8c7f149a6eb4fe046df664ddaee976a15e11a7a59db5e2c44b4a82c8ca1d17c0043f41ee0000000000000000000000000000000000fd9c1dceb20e85ef80bc9ee44e483cd0e2714882734a561ebbd0982d6d08e9c41484ee99790c20e83d051dad0a1b1e04a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf00000000000000000000000000000000040f65cac81c01f04db3e331659d6bbaac8fa01581b1bbfa62891c1bc95a67182d254650019dfa3171e16ce37deef29a000000000000000000000000000000000afd5e22abd5d5cf78764262a91aadcb8b807b2aafecb2aa3d3ba5a187304208e212e5df46a4dc48d6150a733075bbaacd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf00000000000000000000000000000000051046cbe6862c5e37cd2f3c14dfc2825d5c32de69b40f29140fd31405615edf6c116d384bdf1552a33fb00c6c65cd97000000000000000000000000000000000a61a19fdfc994105f03aa3e1b907f5177409664b2e50243cf7e0e6e7e74c7bfce582929e5670a351b3d7b4034f101ffaba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa0000000000000000000000000000000013b76183fa2e01d10a3ecea5be65ffbcb04724ed30e4655e26a7ac94d5861f0f308b7d4577789d2f4892eb89202d84100000000000000000000000000000000012c3fbed77d9c37c47c838899aaea0fb6585eec54801c3ff2b486086e33040aca6baf6192c33af59f7db1d489ddf7d086ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c00000000000000000000000000000000195c1bf8dc0114a472cb4daa31be44f22a162d22f2968b7909374fbc4d0883614d2911475cc3ba242844ef1c046885e70000000000000000000000000000000000d03e5bc3acdd01d174e1d2308e3f1ff3f103db8e2804210da44c47229bd983ac127295558dc5560c0fb2ea34def196f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000001261cc24d5e69fe8a7747fce45086499ad54f7c138fe76fa665517c58e475683c5a219df303810745dc554fa3c096f300000000000000000000000000000000122fc4c068c079827635d29e944366516c1d7cdb1ff62968d847f4882da8a4919b59e57690f6e0f6aaf083af0a04b2ca32b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a00000000000000000000000000000000050b5493fdadda15e15b2ad6104274da831753b1cd247f1dacffb6f896b9db7190bfae2ca202907d36b979b668540ea400000000000000000000000000000000141245d4556c7f1032d0ccd606e3a2d3338ad753fd7d0a3c1b8ab38e94d8618e85c22a269428537abe003f8de89f2c1171a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6,0000000000000000000000000000000008bfa9c347d937e2ff2d77ce7127b1af4e7adad5f1aa6d71c6226b64c62c955fb0dd0e36a5e87472d6979b656c8a740e00000000000000000000000000000000032de435000391e441ecb7e777741fc72f4676f64cfaca6fadf030e12ea6374a7fa988c443a38595def0b434b185c75a +000000000000000000000000000000000d0296528c7b2516ea73cf14c5625a4296c311fc2e09722f3b381279da52ba9482e43d3fdc1694b96c3f62b7d98d6951000000000000000000000000000000000da2aaba37d0955c5fcf31152926f2fb345deba744241bf66511da5f4ad9fab8a1cffa270c4e838c39b34bc28fbc08b02e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b200000000000000000000000000000000175c976baf0205ee7326c84c49cbd2d7c3db91d1ec92d87cca896ea09a7cfe4ef8ca45873f86e28afc4b525356a68cba000000000000000000000000000000000c442d3edb8b614407e0d138417f8a6c028b29dc1beb5825c928dff3a08820c5a8ed5de643068bf4d239bbcc2dcd0b7612a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea7778355947000000000000000000000000000000000f45bf893109177d3c336915c5e28c338ea28468cbe215ee6fc6f6e3c9aa9e0b7120586e42c5c087b55fb5789a4a9eb2000000000000000000000000000000000b6ad0cffaf555f081ec7a6fb354d6b20950fb6fee059f2f571430f86a7cb9996b5f655bc7cddd14f3f8ed37c7fd278889a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000001944f2fa08357307df2271f4bb57cd07a998df56425f7b8563902aaa0330070ce260b6d86fc38a5c6a284788d9cc0ed700000000000000000000000000000000165d8134931f7a4cbeb5114a10e44172aa6a0c250989dbd88282f92fc238a8e1e21221b04b239cfc597e2b74700c626d27dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c7a02cbcc758fa7b1ea5fd30b3b88cdda7c8661b3712ba5bf924b441e056fb9bea804bbfa1850c21cad891ee253ff7100000000000000000000000000000000012202a151fcb86875b4dd2dbeafe5ca484b63408ba01440007164fa2a2b7ebbe9d7f738f382a010508408d26a57c566ca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f840000000000000000000000000000000017d02a3ec6d45e9b49ddc8d1bdee168f71c32ba26d4de8c1bdb328cb4c46286328387aac8785eb5a7c71d0ed59810f4e000000000000000000000000000000000d23ee9c9fc914404ff46d0f6ee86984862e97a777ab516c2b84f5b5a7c1807d64e93fe57db53c7b95257fe46a7a15495aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000040ca6ea6cae1be17996106cacbc5d9f1962203fd25917dec2c053816f3200b9853b218a07db690d8261ae3cc85679bc00000000000000000000000000000000097e8f4b5a24b010382888ebd7ab7cb71f471bca00c1499486cfbf1bc5ba6af169ac27e1ed8cf31b5d9600361ad13663fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000016f95e3e24941c2745c009437c1b2f5ebf690c9c76e269f877bbf73ddc6b15c6132d424c26a3c7bdd9c5302dcbab171f000000000000000000000000000000000cfca2fd001c0da52f231a60288b22a134c7e16aac8745129c351dd96fa37b72a9ef3d93d5e8e45cb5fab9e73ff188e128d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b6224000000000000000000000000000000000e2bdbc906c10b04c5fc1e867af43bea7ca43cdbc43cc3574a47b2b0670716a92fd863d4f423f3392ec8849e74850eb9000000000000000000000000000000000ae76847a2524be3a04bf85e096a1ca4cd3674459698fe326db2d71799c8906022e15bcadfbc9ddcd43dbee3443842a81208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a2900000000000000000000000000000000035d66b8b8b64bb0d3d1ba6bc1bd34c326ce6abac3a97188f82be38d1756f14a63bfedd531d5e19813b668012f77763300000000000000000000000000000000060851234e4cfa8c168db199bea8cbc337e685b565a6faf67e07c463632a6a163a2d22acf9fc6bc6a1f7ead5d288fcccd3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000010e398f6c9ded2fef3cd95cbef681c5335a1e9d08c05dc05b6391f65941cb3a79df9e1cc4ebd3fce82d36cc628b7f65c0000000000000000000000000000000016dede30728c57650952e9425b6da1ec8ee5702e783c69936eaf6857f199bd9ffae569db3cbd61483d48188633fef7ed7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000005eb3a914a78b4bb3041a32397bdba3edf6943ed474ac8efbf9c84a6cdae5d65a8f55ce4ad141b846f1bcb5df1206417000000000000000000000000000000000c20828a5d8abc2c8f72809348e770649bdf4bc0991f45979501f31d9f31e028731a8ccf07f0cc51bf8b59632897c540c5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f1300000000000000000000000000000000122cf863d9ddfdc627a0993dc7ca5810e84ab254ff8147a220d436043c0a695b0cceaa374842c335c14b6ebb273472d800000000000000000000000000000000150fc0b14e30ee797e3b9202533c681ca9e6b1b43347cfa11da59ceab439c9e5cbc038a50917cd9167a0fd591d8175e484ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000002ec5aa74588f6a7fd8076b9a846ff3542543dc7a3c798c423326eb06ef92edb8c35583785cfff21f903f08f692d6293000000000000000000000000000000000df140c1539cd3d94b5f9d0aafc38294d1738c5b3c1880d8864e83909b152de0a469742cd31e5e8f5838ad793ea32649a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b5000000000000000000000000000000000a440f227be209dd1bb816a4dd8c1abbdbd03d97c243ac6e48c4efcabef4d7a4b5bf65ea7bea6f4a1da985bbb9fac626000000000000000000000000000000001431a99e1243e57054d2b43217286b35bbf37afff72b163ad40dd4ca92439f4b513284551b0fb137f968f9f59a540cac650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000b99ae325f1fcf4f3c83f251183871d1b6048a43d15da80650e0b5c1b671031cc9af63a478b5939210356c4c2dcc7aa1000000000000000000000000000000001382d6f0550aad61dccb47a66d004ab3801445d55dd320a6ccf03577b1c1c915022a955e7f3fccbbdd20e4175bd0ae38282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57,000000000000000000000000000000000c62c70aac1893222d967bde4fdffc565cc65fe29387825224b8571367ae8fa857b63c32033faa564f6e12d409b5cc060000000000000000000000000000000015cb57fcbc876f5aeca01d90d55ea777aa1421547e8aff0de69fe5527c9a575f9cecd1235a37648c7509d9bebb4e6800 +00000000000000000000000000000000123fa54665de1ad1eb74d400f93b70f8502bd9386a164ef9ac7549b3693525e3fd077b2b2d8b15ab0c6cd5da30f8317800000000000000000000000000000000185921a0fb38ec1eb6804002b3bcbd4d4bc759885e9c1fafe275d51840434382df783518ce768ae40e736ad2ca8fc8803d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb9000000000000000000000000000000000eb30c8da4c7eb16d797f24b5d8e210dfaa68684939cad598518298c84214ad769f6a2634fc290c2c267c8f3a2872f020000000000000000000000000000000006452f211931b8d7ccd8777b2407e5cb073097ae9b309f1e95633f39d1a5a7f5843a6e87473b4b9c1bbfc17971108e3de71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000162c0325ae75a81c92a8885f14e2f7b9b8bfb249fb9a352d0007cd8bdfce2d8024f1e4674614cd0afbded99472d547000000000000000000000000000000000010d8497a5f31cda80af22bfa6695b4e2c8fb5557ee74581a33fbd0cf8cb2e0b4ca3ecc42487cf957ea81a5388d9871fcbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a0000000000000000000000000000000013270ce7a1b4abe3026d245df9b93061a435ed00d0464d8de14675247c7f2f1cbb6e21c8282e71d2fa28eca1e3f5863c000000000000000000000000000000000b87656d14cfe98c2d3f34b03de0b9f08207b00aaf6c5a4a6b9b4989744581772a2d6d1923c3d07b784853f7b2d789b9ef0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab700000000000000000000000000000000067296630285ec7da7cfdeedd387d52d905ec39e183b87479c8f0fba967e840f8394cb518dba4f4b7d4e2cdc00ca62c3000000000000000000000000000000000ed41fe0f04e0c63f3fd7ec7560d24974fd06a1566e8f129f580251227cb9b7e10ed6e60c2e7449721d5332709f465973ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc00000000000000000000000000000000006f7bbdc3c8fe2f7da9533a3f8a3c48c630d6cf567c75dcf89e13852f7a8691e2625ca24517ad3b59ed3513f7d3b4fb20000000000000000000000000000000000a2e63715ec49b06a78e014b98effbb03f99ce61b464c66108cf18ea49def3e1f035a8b88f37b453b31357d2a2a48f4e5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a9e51eaf24d2d0fcb7f1dc7ad985ecd4da3ecd19fb75591467edb0f7fc7bcef67c1c272f39c31ef36bbc73d7ea6034d000000000000000000000000000000000332dedca239f4d1272db77dc388e07005d90f44311aa889b42e931d08c2669c3f4aeecd9052d3f2585b2a4e41c8abbf97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb5926000000000000000000000000000000000c6b189ddc86e2d6722ebabc445190cf94bb4c54135aae2601c957e062d351d0c9fff19cbeb45cfc5dd05eb3543a660000000000000000000000000000000000133794839bae14fa041004f173506fff511526313da5a8f4e32c895751a22ecf01cfba564006037326187b899aed596ac54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff67352000000000000000000000000000000000a15343698b916965009f1894c8b74a790d59bc39b7f0de01095275ec002c97c66e7a6a970b4b9091cdc54abdff1cdb800000000000000000000000000000000045f084e0a7c0014e58c9988e72e1861bdb4f962ff9869d444d5ba4094178d52f9c2aa511feb6e8717098cc1f09d49eb47ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001656928ad3ee67675951e2d2ddd6a7d9c629a3148face6d1269f79c3d0699f95350e83a6ec20aa3be78a2794c3f250160000000000000000000000000000000001b8c9e4c818774dbd2416193e795a429a22881abc94ebd9a8b42bc4d7069a9778e4bdf7270180784d914bc6be99b41c14ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b0000000000000000000000000000000013d779138ab03fafee1e4bfa2a290c4f20d2b57854a5133cf5ad7817bd32bbf2945a02b4fd5c8489e704e60ee937f962000000000000000000000000000000000aa058528a4f9bb583295ace843feac4dbce24a22ea6bf412be019f590c621bdfc7562e8dd49afcc337cab474d9abd0129b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e2040000000000000000000000000000000014f30fdaf2f81f9d941af33d53e2d9e3162f62f47c60164e9b5ea3a5cf3a681a80b66ebfea391331c231abc4341cb94b000000000000000000000000000000001854addff23c2f53a21a6d39c72f91ef0e8d9a6d6468f319200466f78854c41be3e914bf7f966f00e185b44108af30f092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f00000000000000000000000000000000188a1934a28c7571ee94f1aa5c161be611939e52156bff158170d5e12a6480e3b9d1528082cc2e537ae1734b1847f8f8000000000000000000000000000000001728b57eca86cc8fcd9dfc65a8f5f055d51d300d8781839d744a1b81a0233221cd353f642b3507703880eb0a33afa05c8f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000006c99e6c8b554d748a3526da79e8a867efde15ec50ff62e43f691748996dc087dbc538cf65820ca065f3adb5884e2f0c000000000000000000000000000000000c577c42243b95b4a613c485026306513685cce294333b72388d6968019d04214ed4bbbd5b64bce78fc380115a4b067ca9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000d7b48e69a9807c6fc867f59c894d5bbfeeeacff500a3ad4528ed4848f5ce501baf8959f822c259b712236529dff0b0a000000000000000000000000000000000e7d7932084a0416a4bafe237c923d1390dc6662e7842829ab6747024378f284af07ccde9cf80042bec56e7429ab3acd80b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000a1f74acb627d1814ef90b2d756bf76383075134c1b34dc126094238eadebd780c1ab8a3d1f4d9566dbef1c706d931920000000000000000000000000000000009bf8c2fc78b1f7af25941bf429059e9f86b34a36ff865b33e918c8435a766d897df83005c54871ad0d3e82308e368501b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61,000000000000000000000000000000000a912d7d352bdd182a8b431672b496ecbf18276da61d6a8eb066c41783b7cf3df287796b34b165db186e815c8847b3ea0000000000000000000000000000000002881de241ed8109f544f3a6262eac1aae69de7a7e9953812eede9e63a970225646f5c441b7de80106d53cb6dbb43f10 +0000000000000000000000000000000018896a4d84c1ec1a20e1b0e33f159de4d82b55b6d27d863ec7cbefc2d9c180beed2285aadb34d29ceea681689dab06ce0000000000000000000000000000000013398b5f6f2c0c9095af94796572d603de02d41c599e09d3e254b326fa1575e0c6a2b7263a196c5150440daffb0d60e810e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f6631850000000000000000000000000000000015b31d591dedde69dcfe9c23df11782c090c443e505d2edfa217121a1d51b6883d782917b2a082a41ce698ecd95ba95b00000000000000000000000000000000164b18eaf53165842e50112c4a8490b8246376b58bb6c188fc929160f49cb0b68ad2f13dbeac8466fca75e6f72a398b8cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000012876e247618c76af5221a50780803ab64970fea8bdeefcdc1ef4c9a160718fbaed9dc6691502433295d54d4030ee157000000000000000000000000000000000cfd8dbbeecfd176cce05ca1663930be8cf3b300a287ed053e36f64618a14850a3e813582da1f54ac7e96ff61ae57c86290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e800000000000000000000000000000000085c5db53c4abe188f44f084bf17084d3ae409b753089636d3c528162c2816b9b9ef3c0c8c05e88189407d7ca95d40f9000000000000000000000000000000000015d9ab325a8ae365f173821829aa395db9211015903c08491375f82853d9084d8aa2e35c2634a296ba14b50e34c1feb71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff570000000000000000000000000000000014bc3ba096662dc560e88ea6b7b4363c427d038fe85a49ab8d9d63524940f26106447ad6e3d7495ca562c98b64d445880000000000000000000000000000000018bf745fde497914d81d1e3ab96630f24f6ed27ebf1208f7a46ad9fb893a3f183982c0acfc001984de34f617841524f9250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000019b15da994067a017c3040830b5e5f7eb77ce0cf0674e96b209b80c54f1307cb04799624647fd1fb990c61092682ef730000000000000000000000000000000002248d31211c2a37df59a0a4ddb0cc7880dea316519ab7baf1c614b26e2673f03b00e387fd537aee442cfc94f734aad8ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000003a499813ed2a3878ffc11d27dba4d55837d1114049a72444b6db0c8a7d23a53af765d66b5017695efa39bcf7d1c97ba00000000000000000000000000000000011fb1a989afe2b093fa2ae3c0405483bb1a52c21226acbdf2a52e2e5fd5f7404776551c2deee87f431ff39dfb031d716ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000014540330ba54d2f16a9bdec93a0b7ccd58ecb44361c67f209d36d2a42b5d5a4f9b9dce0701ad0677d6d6ca83a256e8460000000000000000000000000000000001a64d5b128c07848ec579df1d26755e5d2f70cf123013ac249a4d188b0eb56cd74cb12f7de2db69b3a0f9f4ece2c4201271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000017783e019baea183ee5d9e1f671a23108e403a22580f5c203dd6ff72dc0adaf802d031a236e72463e0fa2c5f7c6e68b300000000000000000000000000000000132d32bae3b92b7212dd7db16c87360274a409f46199f66e572bdb21c4af24af62758978e6d01af60f5fb87481d9f4f23ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd00000000000000000000000000000000000a625f252a8185bea7f1b73d1c7c9b1fc7f4ea5cdd017afbe9e56e7c12d58d893ddc387b7c2870f4a975b613bef0129000000000000000000000000000000000aff6dcf60f78bc908fc4c2466270065766792a05d8629fc7f5d2b61ce4882644947fcc3600d63bd5f49fea5574616bcc6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000008ee6e9521f32feaa034b533c0b7c749f60d84adb53d6943d3974fb4b92ce3cb3f67fbf52fff27802c893cb97e587b930000000000000000000000000000000012000b50d1c9628f822c41d56b29e21f3f496f00bcf05edb234ffda56767bb33dfae736aa9fb9a84ccb6a0e21131c5887275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c6150000000000000000000000000000000014d9d3051d073d24701f01631408b7dd1d37f0855baa64a13c493c15f7acf36da116595fb3d69dc386cc611c998f9ea9000000000000000000000000000000000b33438dc1f84da6ae50b1aa76fc52f5ba0e547fb15e8f655db9e0e26d6aed15c5cc4e48412d089d1ca6fb7a550f8eecdbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000008935c4cfe2a1620a0c895feecd91ea7fdcca3bb06fa514bafee38ea5819b7372e75a106904b9c9e8af268c9f5e5a45700000000000000000000000000000000114e9944fbfc05ee1ed75603bb9b79301a1f90d3b5209ea14989fdd16f5deeb01e3474da2b4692a3e0b9625d3bf9b4b2ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf00000000000000000000000000000000105268fff23696890182b5ec307b38ee1cf28336e1c3fa28b9b697998567035323ccc91e974f63c55c928f64fabc2ca0000000000000000000000000000000000ac2f8c91fa31e2d950385509b86d512c80f0d1c73d223f71b26040d58822e4269a85e82ae390441853f8169177943aa461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000d5a5c94375029e5511a6c6ca40108377db43e4e0b03cceaf9fb77fac7906f71019c1a85591719bfa5d9349f1089ba0d00000000000000000000000000000000163bdfc6d40c96bd24a3b83f89037ec9e4191b533e36dc699a32c854291b0823b3f071464654eed00f08a691aa68636bcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d0000000000000000000000000000000011654611997b772db3111d2d4edf92b83689451b1e7594a7a4bd40d85820df6a1ab090f6a1959acb322323eef27fbd86000000000000000000000000000000000b905fec9e379cfba09fd502197305ae39b48facdb01f52afbcdf159c5674234ac9723643830ab8e2639e7a0d6bd979267de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda,0000000000000000000000000000000004de1528d78645a4055ea348ef2618b85f8214c1dbd21ee08ad164abc02cbb2927202302dcd142c65e12934dec409e18000000000000000000000000000000000de34a6fbb73c7152f1636e5c02c75dbbc5910c763bb790d78bb56e48cbc6380bcc2ca14cc11ae139fe009135c81b243 +00000000000000000000000000000000184094ad33f83f5b229643d9808f5b3b7f3e50306788f8485472405c79e57e489549c0901c3d1694b5f61f74d87afe9600000000000000000000000000000000007ec616b56868e00563d8e8bdd36de3b5d1e314be0d81c4ee97fabab1641c89cc21e70153a4d3d4e788b04ffaf07bad624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000017e7c08cfedb74bf88c1a80762be0e0754a86e5482c27b41240f4ffa9d014e9e8560e172519031eecccd897b869c365f00000000000000000000000000000000115ff96d404829597f16b9b97f8ff71a8eaca1a76bbcb72d53803d35335d8a8c1cea58559136f9b254c28262aa907414edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c235000000000000000000000000000000000b5450038d49c91e4e5a40a31f1f75923c7e1599695b829d9975ba7d845ab20ed5a62a7238d6a6479b2c6f9249068aed0000000000000000000000000000000013066cb8ef171bdfa11e70ddf83eb2447c4169fe38e008be5787c38b1b8a946fc474e07795765ca17fd5bcf64150fb04feb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b000000000000000000000000000000000f19fc0ba8a0ec5a2cdb9844002601f580e0eb9b2265c86f6efc4b633079d43461d6bf241ccbf422eb9d7c00ecca88570000000000000000000000000000000012e744ae937ff9e8e4f611fbd1c9896bd31bb1ca36b948d9be89960fee6c0cbab3264aacb916ae3596f110cc1b26bfedcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000006ca09ce8c07e89e9e51e208b5d32b5ab61f0de60484d9185a26911b56a728a7473b70313fd18c893ed3453719b074450000000000000000000000000000000003d372a5477fe7fd84a58f6f2eda8f5c61aa0c357c7fc1708f7616b8cdff249e7d2910d753c2e531a278f5853fc065970b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000152ea2fd1934c32c3c8e27a6ffb278741b899c5e296549380d019307875629d57ae44580a944babeecef73753e30c92600000000000000000000000000000000161a77844c90a6e83ed2c40c937de21fbd714a5cde60015a71bd4c960e894d3cb54a8d1e4bb4cb0a1985d4469814a991730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000a959b12e3af03cd4629f5f6f412b7084eec6aa55369e2dd2f355c93ea984ea6f2a7a01e6a10146849503d230fb08f7300000000000000000000000000000000161340908a38e4ff5373df643e3cfdc459d872b5cfd41ab34fd3297b10c37dbf3088fc23fb71f2a1751a121bcf51ee36ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000013e31a4f0cc29a5ff7f4df39db999c95eac789656bc9c6b91d0209b8a5ec2dbab698048fefb75a3dfa48066ed5743215000000000000000000000000000000001851e72741707cf96f887d13e01981f1e3db5834185eedaafdea99eeb11dcd3e90a9985f40886b60ee2a779b141bb62082b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000001501f155cf6f053631ebac7d2c57cbb101a750f98b6e11df79dbb24ec8804535b1b24942022aa64713fc60adb2017bff0000000000000000000000000000000012a08f9b1ab90531a26221b70751efa598b4046a5482c01d72f506ffbb3430d35016848755674d01e16bb78a44f8b6882396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc0000000000000000000000000000000008fe1ea18cd8e1d2c620356430ca43782f844a2efa6a285a7c9c086e972b12735faf6237447759bd93d98b6dc7c42344000000000000000000000000000000001731f36e811c640f44adce6bb68fd71065f440eeada278ebcabfb9bf0291e551ed302c592aa4ba7e3a502cf58e3eede69a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000018e3a4176b543f2152bd7f72ca358af6226f77b5e10f3f9006c8bbe4283776ac31e6d10e838e89e8090215a133e2cc510000000000000000000000000000000000f88c3eab9ab32fc165083ba1650736e04b4e8740591f6e3ffbf684fb359fc8d82513c25a9ecf4d46faaa14d9f13a3ff20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb0000000000000000000000000000000016fb47b4497cdcc75c0547f4234ce94f45d160e7bbe199902b2af5a5896e7d46cdc866d0fd730f568449032fc3a2df4b0000000000000000000000000000000016c2da30ef51e6728c09c3b29a7abdbb104f1a4fcc8960248b9773d2ea7f1bd161bf17203a271edfb235e8b0be437957f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000001992edcbf32707e92506e5cd12662e730bc96b5f33bb88c5569fe6b266aecf63548be20b03fefaa078231b17424ac98d000000000000000000000000000000000f6179cb8878214222c2353a60e0ee210c86e306e335e929050543f084ce7c7ef56ca8444eee59856f4107e0d8cf997b52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e000000000000000000000000000000000d3ff341e9b3821ac23ff7a87cc9dec3fba38ab8f2bc0f58e4c0135a9d66c6d6731ad8bb97468ca44538ca7f26fdfeea00000000000000000000000000000000053240b8429fb290453de18000ac58df56b5bf3c279e35d9cae8b350b932b0545b6c19ec7ff186c2123731d971146df1d32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f0000000000000000000000000000000004b424dab429bb3d22d18b52c4f9412a65eb7e8ec40b5e308f65fe6c0da1a1ab55a629ef8ed57adf108d146b46e6261e00000000000000000000000000000000057b7d5285194693a7ec1ed9ee3dfbe8598d9acb670baf03bf77c7799227ea788052de690e229b0d28c0a6cd79d22b0c1f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad2694000000000000000000000000000000000ffc202d826607947dd8f63b227a06d8c6b04848dd102da57723fe20e9b06b7c125f0ab2d2f53e14cbe95f1031624f99000000000000000000000000000000000880400b425ffe1b63214509f9acb0255d089e9de8e4eb643fa3b0383aed760f4c00babadd32f48af724a2c80a8223b383052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692,0000000000000000000000000000000013c0b89e259f71ae41cc73ffa3c900ccea45a8a655353db6eb17a87582b00bfb971ba91d48526d934b95e9bb6a0fb5a200000000000000000000000000000000042a78ec26bc1ac4165c36d84588ca132b7366a3fb548801810da041213ee84c7e6aaf5ba02ac051cc1c5be5dfce0ea5 +00000000000000000000000000000000083ddce067e21b219535e477f77ba100fb86744b1b82b4ccd0c72aac69025038e719ed173e70805c025b19bf7ba5908a000000000000000000000000000000000a9eb816ed60bbe55d4833c0e91ee73669aad116ff793d941223c17c86fea3ea434172c3214a4620d4090915cfb15d11c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c705200000000000000000000000000000000050bdd7d98b9df55ec0ab87e757de009c804880f06be3ce13c5e051c3080df45bedad4f074812a698f50d6774cd5921b000000000000000000000000000000000a8dde7b81feef753cf16f0818f29256391276847cb832bc2940bddb329b249af4970684e95fc02e702f09a84e7737dfccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c0000000000000000000000000000000000d989e383d1c6e48d14332a72a8efd89260fed65a47c4baabeb0c0cd8322e26ade95b8be9f532b4813153cc39e7a9402000000000000000000000000000000000f6f7ba41c95beccbf59ca1ebb1dd43348c51de617a09ab8a2d67d3f7065d3f4699b1fc31197275e5b895f92dd106d667ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c396310000000000000000000000000000000013f3c3eec6fd2d4c830458cf58d5e18f0367675c47d38fd5ddce1e8be3d6ab04f71d09852b987d2db64652b3255e874d0000000000000000000000000000000009c0000761e1fe517eb32bc3da4f7a933e77db6f960f5405b64d9088776b6ee8e23743cb4a1779e8d0d93787ca029d7c6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf018403978000000000000000000000000000000000c7a5bc0249717c1e39a4eea37de1b423960b409f5e0b3877e90d5278cabee197948383936739ee3f25b4bbf7f32e18900000000000000000000000000000000113d6fdda1f4b2a20d98e1d458920658c762303ee69fd7273a8830728f79be00358b3f3000927bc4d26352e5b9e6652b7fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f00000000000000000000000000000000108fb7a97ce429fc3ba1ca54ae841309e2ccce748dca953cb7dd9dee3ad9d919e3f8ab635b294b94b939cd80d3435b5e0000000000000000000000000000000003af838ba4ec485ec2a17e6f592fd832d05133952f273d1b472800b210c96cc503caadc17b38d3d1e978606786d9ffcddd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b0000000000000000000000000000000004cef28329ccf221ad7ab2b851e869bd433116753e0d8bf38d22ca46fbdc71fd9d96aeb9c0df69c47905a99c96fef0aa0000000000000000000000000000000012ef5c40d8b6469d9f3921eaa99446fe494a55994551fd1996c453a4e5cb4a2cbabe20671ff51639710a5e45a57271aab4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000c81313e8b5689935fc01b5f999de2fbe9852bdccf484edd0771e8427f2a194e29d0af09db1152fcd91c8f7b665f6929000000000000000000000000000000000f37dc7f87b8de48441861ce0c88b1a24f22aef2c321ddbf385cedec7810c20c7fee3d2c5a04b5390a5fc24612e4b3e9457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd10000000000000000000000000000000004ebf9f75e92ec4fb7168bf71215c9ea8ec17dd9ab392c9810316a30a33b4ace8d93ab75356baaeb51a7f47b4370915d000000000000000000000000000000001307c68414b73db43bcd9062580f7c814c3c34545ad5d943685ed8df26acd457823ed628e4b215875a9008a406fadb5619f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000cb5860f081e314d4fa3bf70a5eb18d6fb7f5257a708f1b1726b539115050754724ffd6a34d3b5c95359f40f41f2390e000000000000000000000000000000000c392d8603c2ef93d2765d98c695dbda8e4b64ed90c4771a4e69fa00a77d788981132336f870a3a93765902fd8fe8763f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000011c802516f42e267c0f9db096fdfff77d676eb301ef1ad440b6c2129c5b5722c420f6e479443cbf43d48803f7e32d8470000000000000000000000000000000004a5ef232d3582724c3eda67cf2e69b26ce44bd927555359820efc3fc67912df560edfc4d119c5595e1ab1fd7e2a262f50aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000eef1e6400dbda287910c117ba17eee1137377e262f7f5cf13710b521bd26eca2aa9731b0a1cf182a0d57a329369125400000000000000000000000000000000188e925365fe7cb96875e85f711d8ce233cadbcdd4c892eac52d9c77f98082662410db4cb6b24889b21f162eecd10f42d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad434000000000000000000000000000000001307849ed4d685815c670477ac54826e94465aed0b70df9683d09ddc62597e7a0a7a4b2839fbec735eeba08bbd3e821c0000000000000000000000000000000005dd74ee1018ff2280c3dd8faec3c97bbd00bbb7cfbcb849bb003b590a999b6bb3a973ec96bd9d825206eb353086283485c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae000000000000000000000000000000000458181a1019a65c34835eeca4898b88b0351da7422bb5982616c90740e8773b5a03272646f26c3a5801c6c16be33ec900000000000000000000000000000000101c2091a08179eb0be41e20a545f5b53b8ee39365dc9b57f12d75b2beebdad488d63e857ba5187c8f92af447f72896ebacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000cb8c637a9b8f053d5104b582ca03ecba768425c639fef23c4b624f31523e0ac669183639991728135474ca19e0335160000000000000000000000000000000009e0798589417cff12eef14f00e415c51c30fc26461e92c4e3fb4a5ab1a653ae791f05f4cde0cfe2132c377175cec1c2473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae0000000000000000000000000000000008400ba7dce60413ff085c0904066b8e9e9ae290781132e739a5a8c7bcbda322fe1c8d0fdb0e9b0abe44ff99d4ca22ee0000000000000000000000000000000008b54feb64f59541ba3b7c6f86d24b69fa30ba057db890cc6d958e3a7de8bd379257c90a413050f7789ded9ee7b28bbd2774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a,0000000000000000000000000000000017377baed9953cc7fe1868baa9e2f6c87edfab1700bd1f60e2644bb97cbe2dd1fe031a986fde6555930d4c464399f1f6000000000000000000000000000000000ff69a282658b49c270e829b326502d83a82dade820de50764a7df957e94e7c0a1f0e3d9084b45d9a875134bedc4a0bf +0000000000000000000000000000000000d1de82c29aaa76b17079b2e1000005bf37df08de2c5ba7a0f9a14870e0ac327f46f59a116c72db57cf5110aeed6c76000000000000000000000000000000000a8ff0afd1cd7f541775567134a889d82727e893e4f57d1b5981fabd4bbff59dd3d3995a181efc9b5fc078eb3d4cd0e7d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f026672140000000000000000000000000000000018120f0d0dc908dce4adbe50b24b66ce12e710fd35e5a8a8c357dd80c078d6854f20b12d40279b9d6a895460d8989cda00000000000000000000000000000000064f4e282ec5cac74e1a12f678391730663c83afcc0b415fd21475875762de2224e389d607cba84788a16d622d2ef5c13e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000dae339b418871e2f31ed380824412acbe44e6c73ede9b4c52c054924297aaee1f7da749374d7ca44b138acb85dc182f00000000000000000000000000000000155cfb670ac94e7d5a095d2797cbbb5b8ad3e037fd246246f8e8c2278f5d4e53a773e6518ebc3ea5aeec6383d6fbb62c145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f000000000000000000000000000000000c1f7aeb05294c1b496de11f743c0c7aa4255211e1e36389bc93dc8d0e73fdb9af7bfbcef2c196a95d1d449b9983b2020000000000000000000000000000000011251668e9edb38ad147f22cbab7d280d436d11039d9fb823a19dffedf2c6a484f112560623cde7e5525c85b4f5d06accf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed0000000000000000000000000000000000ad488f5b9934adcdc834558c8db1d62574e1ffcd03da30eed865042abae4dc03d69010e7e591d9f0a8e421d22cab23a0000000000000000000000000000000002cb0a8e0713dd3c4833af74767ce46aca6c1efdfe75d09a50fce4df2eee3fbc031357691e23ceee810d30004d03f6b9d9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000465d95750a3c688f560ab9ca6fd1f77457592a0d5f54c17904a222010444d048df2be3dd402f046b1375d75de446d2500000000000000000000000000000000166289d948aa518167e72591a011b3f5ce209bd32ce091543bbdee1e8776269347ed711e1e9f1193f818e3045761a75141733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced157586700000000000000000000000000000000012abc4f1c56f9ac3760acec3d79b77e9ac71bbfe4d2a90cf43da3607c99035e550a4d0fda734bcfcb16ad08f773535d400000000000000000000000000000000030953a6099532f7ac352eff43569914c3f8d736b8aca89f778b4a67c754ada78e121dff664feb751532a41c8081380eb21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000138cb559d92b392e94cdd8666605cb5b05e585dccfc023bb6f1abe82fad35c108fca7a41afa49a801700dd8ef89eb3b0000000000000000000000000000000018cf89ad3e05492ac8699ba0723d5ce43e81b0166fc33653c967da921faef37f3ee2e8e3f71f983774966ca183e05f9eeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000129c48a05e3d6bfab6e6f5200fbb90fbb743b045509b129e3622929712939c5d15126a09f1a650489c8afde7ace8baea000000000000000000000000000000000abff3803d605dbd63bb8453e304335a943bebd224d2d8067d76f5591cc6a2b954b9156a243b0c23d08424fb9edb52383d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000194e6f217b863339824d95c77253ddef4ab97d9744d10392d399b1f165170bb8c13ef1b7cbd995c1c1dc2a9d1b87f0da0000000000000000000000000000000019fbdffa8df167a5e891d09aa1e79049d377014e58523c0eb453f5f072a468809dca8ce0aa22b45bad4f8853d985be1df6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda40000000000000000000000000000000006f0b72c2a934e430e4b773a61317007f1ef02c5f978b3565d623b6590b6cfec22f98b49f9d7f7efcc6913c139fb27a60000000000000000000000000000000018ea7df5f807d4c4981a9159d73d83ea84359d6aa00a5ef019b0dc307d096676c0d16c6b167fc55e14329a858c044c5c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e108700000000000000000000000000000000130fae66f6b4e1a9b0b39906fac847f1285a7d37bdb0d3ddc2c2bfcc6320ccbad2ef1f119f2663e3a45dbff005a469a10000000000000000000000000000000019ba2ae0c371256e4c3dd6f9ae2568386d3a8bd90a57ff982294eae9194494add18958dd516ca9dda6a0b334391cc211f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f000000000000000000000000000000001152651000a16809ec599f2fe9f330b0782685f6302254450884f0ee61ee2dc2cc9211f69d5d9dbcd7fe3345542a0159000000000000000000000000000000000b5c017e7ef71eb089188ed85331815b40c37abb6ff73d76f40fd8dcc6d2120c6a52df0da042b2b63dfd0da7db2bbca9ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a3130000000000000000000000000000000003a6f178d8c63765b2c8df834ebf7e96a4f451c6e05692f96b71c8be2a6e9af17a5cfd8b263eaa254592ea9a898488bf00000000000000000000000000000000185537df1a10c4c12fbcff08de45b349a90b0cc8cd17827df87abe160e84b661d58a1fd03c669015b991225ba08e171e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000085475c2fd70cb7caaaa7c5c1fb17e2346903a962fa68536240d041f2f8cd3a7b83aa79a77f713bc31f7becd347d18d7000000000000000000000000000000000c98414bc318b350113186db9e965a238f1f181b00a2265638d914d263e4a71ff643907ed8dca814e5b8d5713baa8dc9d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd7100200000000000000000000000000000000001064b94e868fa82c892dd244c6247063a276cc651e22d09695ac6e73d20bb801a189e8fcef8a711ed471fa3b2c7d19000000000000000000000000000000001561503962d7314fe41f7b2d34eadcc985fa748cc98479a06749692a00a46fb2fe5b5a68f7001a0f89f20f7f42f4463c38f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0,0000000000000000000000000000000003e62892118f11065ebc66c58c86e2f0d3c79541aca8db33bd0e12f034f0e106a76f0aecd537539cf2d793cf823ebbbe000000000000000000000000000000000067e42ecf23e1b0590c30106b0720c806ca71fca0601b499025b58f137ff22aabdc6cc5eeef232fc9a20fb9c2bdee16 +00000000000000000000000000000000184a661b34e18b637bca53ba60c891da69fe743d5336d92e811649094c15ecf2445736d0c1577bba4eb729aa7204b44f00000000000000000000000000000000129a348f7fa726585badc23f5dabf49ae095d300056b219bce0ce15f1f6a9fc5c8ebaae56362c3501af3f3de19515143cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a000000000000000000000000000000000badd515b1e0959e77e0f00c7420b46bda5fcb6db59cbd431a1b0ca68c291c6dfe89ece299434f83a980613fe73ab7d3000000000000000000000000000000001266343ad330fcb2cc8242e30a8085cf6995ebd810780115ef881516d4227c6051564d7343e4a5d6bfd210e2e40b91069a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000085e7c22d51db0a45d8db7d5365de9541eb87b81c237fc47cd25c297da4435b4c9b8212c76c929b7c8f32e8d9b11374c000000000000000000000000000000000a4b0f905b48145f1831e453d0372b7861f7be6e413182153cf77d737450a58f378652255cb4516a482d166233dc88c574e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e00000000000000000000000000000000086b97f87625356425a79db717f940debc7a7e932370ea315d1f94b1ead853e3ab6edea6302b6b5b0eb4e4bb3c7fd14e000000000000000000000000000000000fde70203ac7a82901250e9798ef1c671f8d5f878fa3bc83556437b9b98e77f7fe7d3a0f31b8cf05ff6332df0424136fc23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000e629b2d9a57bf96cdc6871ee7dd7675257cf62dd10028201448d8e5b1c0abe777190a868fee83ff5d067252312e82dc0000000000000000000000000000000002102d461c9522542acc185349ea93810c3e2412ebb427f8556b947efe198d616fe00818bedc22765f697507d7678dad972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a32640000000000000000000000000000000015727f52d46099c0ba041be660ca312204afb0f927fdcf0f1afa4cd3448cf3e9fb76bce7ce0da8b4c0048f76f0e7b1410000000000000000000000000000000009dc4e213faf0a8216061b59dd35a135b364431e2be37e42d065a42fc8e42eb8669d32a5f5ecdfd9234487479543471bdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000afb691289f877e1de6fbeb38cee0e36fabf3daf904256d5d6db6e96ce555a9304219bad41400ab6278727e5fe2faffa00000000000000000000000000000000165a54d6db7332b12224d59d8b677517190744c039d9bb401c2e3c4437dbf230b67308fa2d5ae2bf5de282c9ae38a3fa4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd3300000000000000000000000000000000032dbd300fa383541e5c40c849addae3def5a4f5392c44b9e96981dbcedd02252f9bfe4100de9954ab34fae9b2ec21ce00000000000000000000000000000000185e62adc2a44462019c86028c617ddf59a6b1c16071624de5ca755f936e73c47cba00f552d2d79baf60a1796dee009edc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b0000000000000000000000000000000008a574ccaa24ef76112a25b990b5d3b462ff9c43589c9efbb617b45a87bc26eca6dfc6c9e58a12650c202a06d3c86fe60000000000000000000000000000000011f41e39dc0f0bdde1b9e1879741824b20d9237dd7b462272115e8ed44a1e6b7bf82e8ae481204dd8662418fadc63bbf8af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b000000000000000000000000000000000efa7f183cdfcb25cc5516bdb45c409581b6f2a5bd8ce8092dbf9050a20b2ff57c6add39e96a6f1c8d2134a5a37778c7000000000000000000000000000000000a8213977e8512648b6aeafff2cefcd17a14a052791d20236a78e0b462dcac81db74f1625e787540d7dc279846983f647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b8000000000000000000000000000000000287e0add9dcf33f37a10a5ee89cef5240313af0bf0dc183d0c3d6b919c88b979c932c7f141ec5faf012a7f33fe56fa4000000000000000000000000000000001313f591d1da8f6baff044857d2c04f01935b493f5b951cd3538054756d33a52f71be92ef908f016c133aafeb9b9ad2470cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f85200000000000000000000000000000000192d02ab0a323e85e9fa6f553eaafe0d8ca2de63f0fec8139e24805f0785cc85b39908756ab4eb39354ecd8d9440d5260000000000000000000000000000000013997cf706bc8d40b019c2dacf6a7d269e0ffdf8bbc1b4b39e75b48ca5e5e6eba0007b8c55b59530b34b7ebb4c657c57244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f000000000000000000000000000000000a61b3cc7913e45c132cfb06a26fdb1882bd700b32361572fc79a3d2c432644392f341cc70905b86cad2ce52c30e2ace0000000000000000000000000000000011bb3d958600993ec04d9f98ea3f29df0dacbfe6557b36bed865c564595a64132e4036b6240c97cdb38a60533d5a08baad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf30000000000000000000000000000000003d2bc11fa699b284b37d1b45c8dd6b41436a7b2fa09cef316821516801afaa4e1282d717d4eb3d46e54c0208548dd9100000000000000000000000000000000123f8cdf2bcd7d6eab31975ddd610afa79c3c95fed2a6348fa6872b74a6e2816509c71f11d1f272dddb59bafc0f48fc454b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c6000000000000000000000000000000000982c1cbcc39867c7c8c4512392af1489a5e6aa01ecf56abf4cd9050a33536feeb1866421958b929096d2c3f6923891700000000000000000000000000000000104ba4defb74b35d15db80df1f4029650f00b306d702b5934c1705d226886d4bd22b6c88e71b862109f8dceacde3c6d2ae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e490000000000000000000000000000000006e5fc17bdc786eef8cf2140bd8002ea859619d319126fcc5053be9c28526e14e0bc8eb924fa242305069226d766f71c0000000000000000000000000000000017ee60b0dc932806dfefdff2cdf00efc4d5c81a1e84ce48a25db1d49ca26232d4e4cc1f37b34c80375597587dc183b4259b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4,000000000000000000000000000000000c3dbdef90052dd5bdb70f15963091c8fccb5b8720a16140ec96dda18eb6cf003b6f5f7c244d25cf6da34f2a1703a9d800000000000000000000000000000000187456c5af42c3de3d5e08936e8a3f411fd6458d017ec508e48f2b634319155428733634e22883492d7612b2bc38158c +0000000000000000000000000000000005a912b6b2b00c2b2c90ab1aef8c9240831ea9ff2ac3a92753054f159f5ee4eadab8ef57eb5972e3169ff9649b886daa000000000000000000000000000000000981b901734dfb3b5f63bcff802536492664ac13dc695960ad89342ea865ac67d00da7130833126a33573d55a9baf128a53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b46000000000000000000000000000000000b052881b3e27d232ec980dd99bd0ece4e861cecff2496472caffd741f2954718d605de98d9c27dd3ff473ff12b238400000000000000000000000000000000004de4bb9e5a4cef93662cee72259b88f7ccf8a495b733e868d76cc25e04c53a65a83c853c98a25f7a551641d54ecd9534d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000016e4d257db25c08a68943e6e0065b375422fc817539d2874279e2b41428da449627e6e04087fd448f651a23fb01816ba000000000000000000000000000000000e80d041b65789b3289a94848ca4b1109028c9fb314e652486e650221945ef4224ca03a693e062b06036898eb664fc211b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c74000000000000000000000000000000000542fcc8d668a827daf3726bd71d7ddeabc440a6fd0c08a4730803be6e76613cc0265252c41123146a5d7aeae93f485f00000000000000000000000000000000109a61920ccf34a0a71f51f4fe7c882b3d6fe449a8c67711dda64f9eb684b4a28cce6e8bfcd6f3cb599adbd0771a132dee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a50000000000000000000000000000000000a65c746a1206b1250598823b9b6fe5df3dfbee21cd31000e50140893875d1ad9fcd4fe12bce0758544ad8cb4cf5ba700000000000000000000000000000000038c25d3c35fb34151428d2f6bb8a459f834902334d195da214ee9fae4bc6099d225588a001f8fddacadeff0d3d215463e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000158f2288a667f475c736dce9e404ed37b18d677f984b6d9dafb051b87901e6fc60289161bfcfa46a3fdbea7b4cc3f795000000000000000000000000000000000d7faf96c636ee38347b0046d333e72d601e20b018d554d50ed63e30c62db7fa20e29b3ea57b1f31e0d7544ad711c96aab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001933815ab2d8b6cef8790f87dd9750bc2b7a75a9d6c425a3e84cd109f5f5ea866e171dfc212f6f8641252e9421fe3aaa000000000000000000000000000000000f8ba799ca5dd885046a4ffce1d26688d0bc6936f3a5a943dd54f89d991500908c81ec4f9b116e73f74d46b67731421bc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000d4ba404254175cdf5c47c08ec158ad83b6ff5b8dd32b8cb9753fa157726271f343cc0cf5231e7e31583877d2591930000000000000000000000000000000000191f45fc4b8c94519d13ab28e5f84e22dae2f82550b44be737728a695865973ff5060a639e3f03904d74717963dcd764ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba0000000000000000000000000000000014d348b7dbace24bfcb258c853b19fcc1637d7ed9b0ec00d4124cdf6d608c6849e8d2f9858afa83ff356380afa1376fe0000000000000000000000000000000008c509beae3cc22f0da64bccd2e0387c05d7613460942d25182605b3eae6ce052540142d5975733cb6554e6da9f473b6e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ef9aac66681015bdd9bf287caff9aee89225e30a7976e9f503a1712fa863c8d6d46a80952a1d94d96a5e0496f64ce5b0000000000000000000000000000000016c66018f43bf585195b256ca106f47077f977701d97f42564223817ade0a520aa3d7f06d868f1e91705232b1d2440d9f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000042d1d00a946085dc6329e852342573db7dda7385e6a50a2660a924ed6202968e787559fc58a162a775bcb115bf1fcf800000000000000000000000000000000162b52027b08b7d91fe0814c7be69414121cfd452f4d0407a2300bdfe9ba81a4561af74d8067e929b71a92947eac4fce71927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000001598949030cc21d76a9c69305f023bad3cc761d5f857bbccec4de6b0f7557395efb2d126382731aca994a5020039acf5000000000000000000000000000000000dbea8852edc6bef41dd317e7d70eb2a5416d5087ec5207af3f5b3fec39a416dd9ccf4cfb5400cca152f173e66df05f75ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000081e898b02838558c1c9d7ef9f86fefe512e2e7364ad824506c886b4cbe947657c5480353e4f72e237da013d81e5eeb10000000000000000000000000000000005353bf2dafb1b9b4f2cf58e16645aa3fb759eef6eb8f516db068d2768851e7724fda5cb85241aee62b4404de2862dfbd37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000028453aa48ad0302804f9cac568467668b1dc0dce2cbbaf280810ead2c0a94e156420f4fd2566ee7f629e57c3741b8960000000000000000000000000000000001cfc5ed80924f7088ce6a5414372d13fd8f6eb3dd83c66d8b8e4dd1d4db2bbbbbc6ffac00e3a880d8a8fb5dc07fb23f06d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b4000000000000000000000000000000000a236833fafc3da813b95f4562804361aaabcd8166780a4646734e4b65e3a1924c075d402404b52adda4902bac7a2cbe000000000000000000000000000000000def6beaad6a180998c4c70f9a8dd0d948a79524b31fa44874908058e9e58caec2e23d5a0787f1ca05a359ca276c840ff56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af00000000000000000000000000000000188bb3e69bdf0a5f31ad16751a12c767c86df80f53f6688ad74cb2fb32b81bbf9d60be1182ea1b6c0d6fd12ef73e253e00000000000000000000000000000000139ce5ffa569548f1bb877c3d573136a8eb12e7c69cd21a70526f8724bc67e0b37cf7149dac3f78377ae7d5bf4882a6771e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c,0000000000000000000000000000000006e5af407ada013abf1451bc9d5c08e5ba9cddebff0cb77175b152fc19bbdc48e1498673ae4698dc74d039a671ecdcd9000000000000000000000000000000000c8783b3ce25445209b9f1d8bd3ba539c01d230c07c4fdff38ec902467d5f7e9e8e660d8997939684e119fdfcc963836 +000000000000000000000000000000001731f73d2ef1f87fe1752c9d6428de241ba71506c76f31aa9697d1c436af51de363405f60110e8e69ab268280c20f92d0000000000000000000000000000000001ec6ede05f60685e39acc7e105f60602f0fa3c4a6da7342da755eb34aeaa5adbbac4c13197a2c93314ec79f5da8b90177f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da00000000000000000000000000000000158d295d41540fb1a27d8200ddf51fbb9d31a70fcb639c42b7fafae4a95b90ab1ca777125092aefe20f856e3291e528d0000000000000000000000000000000019670ff04a77cfd367c5f0c14218b5d95ea2eae8577da10f27d96e58039b7dd7e9f7f75c32f99dae0920509733ff9c96630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb00000000000000000000000000000000052f8e8098f9e83eaaec1c2638aad30b043c2359f2551a02b2b95816e1c55d37bbfa6e284f280f15dc174d5f03a7698400000000000000000000000000000000034bc698f07544952274c21f416d8f1281ccdcf6bf53ad352afb15a3412879a10b37e6b8b9fc5f46ad715f9ce7b46e3d0969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a5162000000000000000000000000000000000c2e014d5068adce3049cc326d36ef92f294700ab64bfe170260727117f098727cef2e28dc10fe473a46c98867c618400000000000000000000000000000000005b3ea9c12179a47f7e69690f3303ccae614e06878189b40264f02e9bb26284dde846d704121340723bfd1fe5696410dddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000119e9fe8723883d9ae8c61efdd3ae961795d79409750dd39aa6f0f8727ca2429856f977697c4f81894061da278a0f9a9000000000000000000000000000000001438a4dca0c786062aa9cb21e26b87e92f90dcc0bfa014f654b1734cf7cdb8a2e62fd3836a802a9917539dd068c6b4b1191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000012872f4dca3a9f3fcb07d67c76836c23eba3f7957bb77950a4b43ad9c7ee54f53187a742b13e026f8234df9e91659c400000000000000000000000000000000078b9d597bd9b5ed2f7e0d5f8e4a518012591b855c5352fa1450704a33c3cbd5695a0f8da235411aa99aada88086f643aa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f8480000000000000000000000000000000006c414c6611e00c6e98b370bacf2ffbd7ebeae890278a0e951d6aff7dd3e5fb90f82b4e65dd007a3289f97a9600786a9000000000000000000000000000000000cae4750f99ba13f03d3e0769cccc879a4832210d6a2f25b2696099c0cb184398b7d432e801d23200166a4c53a3e70f3049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf100000000000000000000000000000000122f404ddd6b34938d8e57d9d6ee78c3fdc1b771dd7392944ae88c625f81df63915a87ac63dbb69adf8fdf856a92bffd00000000000000000000000000000000197c20bf1392d4d68efc6ac3bd5d8b53b360e305a501dcfc2e350e3738503ebd44a574e478757240236762db2f23d4310502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f0000000000000000000000000000000019195a36dfc449c19b172ec061b4825e4de85fd5b9c633c953ba7a5617973e61abd0de3d59d441f49264a0dd2e781b20000000000000000000000000000000001430f743ee98a2b2f37d9ecf2a7d4dd4963707fd4cd6ccfdff55c3eb189aba2fb295877bc2d3db9032af26eff6485e459787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef52371375000000000000000000000000000000000be5d90e5fa172a2034667160f635ffc190fa495aa9af51b648125c29bcf9b4b31fea7a7e4b49d91b4a8d081c9aa2d3d000000000000000000000000000000001721ebb02265f698528ae1bdc5bd4500d7612bcab9ea939f552ffd8e9dec1d267dfd25ad4d3531676e2ecde3d2170c4810b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000012b7607bd9f1701ed002b6f72b2e832dad7c9b2bb6eb6368fbe78c48bdfa17b2546574d7876425cca7986fa6839b6da2000000000000000000000000000000001975f41ed7cf252a658e80634872ac495e4b518349487930610906bd396f7fe4af3c97acd0ed3b3f265917560b13e6ef072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000014ddf2cedfda66e12e999d0b280883c546e00dddc0bd17817d6df90b7a614c472cb2840b133eabdc7be39b63e50cd9ae000000000000000000000000000000000b86e0559e27a6061aafc091f93b744a8273032f0e8b1c8b7071baf3ac7008a8173b71f51b27efccba27cb018b25257ff3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d60000000000000000000000000000000010f691744e7094b801c180810b24f6a29c21a13514bcaa6303ae49067bdd001213f13c6f980c51b050a684b525c2dabe000000000000000000000000000000000e4e4cc3769cd3e0e458ded43b5c7c481c17efd3283972919212b877c21aa7abd31cf86ee2bdfd3cf0ef6d730c0907db393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca177000000000000000000000000000000000b5e86c2be33255bf6f2f2aa8b17109467543168c0bb92a9ce19bb64c5f84188b2e9f93ac85d948c76989d9d4dc9eafc000000000000000000000000000000000c5244fe670dcb16d7994b7db8f933ff98744e5c6dc124e057c05d2697881115a99f983be480e30ae3e0ce75081b261edb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000df7f56643536b20f65cae1ce4c67c6bb6def8c9b514d6edc92673ae743a2f4e4906aaf7e3b048f88f08a4f5c9f85c8000000000000000000000000000000000176cd183f547a3f38a86d604f8e76261755f72e7222f3734a456a3bf7029590848970e8836b3570e9a4f3500e54fa3008e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf240000000000000000000000000000000003737e58505d0f4c6890c7e03d5f252aa682c110f5bf5dfe8bcee9393104393f4a6a22c34c773e1dcb78881a31b33a71000000000000000000000000000000001988ab3430de7a463dcc2156db572c43b68e58ac2ee26f1ee1bf8e9889f6cd3250e5d7f9464a8eabb127306af39c13140940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c,000000000000000000000000000000000d541103aa046ef53761e929d50c88796b90b3284668a2a75ecd461ade103f086fc443a3681b6440e6b8a8b9558870f20000000000000000000000000000000014120e26b160a37f8e18faf541bb863ebb425fbd0eb072268a12657b6e5ff62d4c0e504691b3d09b710741820f747b85 +0000000000000000000000000000000015c91ab58aad72af3364a3d05e2893c756a273b2c731ef421c0552dffcb32fdc4296bb79afcae2d3c8aec6e0dcd27c17000000000000000000000000000000001901b4fec7a1324a34fe403dcc51656145fcbeb4eac94f955f4fcc5ad6a016eaa436878e85dcebd8992e1a177c5bdbf80f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000016df7578f74b1ccdfd537a074d71f2dbdd581f1a2f78875a7d4e1c3cb772aad0d02bf4935f7b08aa5163e82e5a747bdb00000000000000000000000000000000053931dd0624377808705d3fc6e12c4894414c8f6a5662ffd71251bc7725e6d23b7781286b8be1e35eb615bb1efeee9c34680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba200000000000000000000000000000000056f0c5d78c5d4e97fcc7d6c3132dc4cd802eaa1bf18921d039274104b56e8a701c25de6ad33e57997b2e8491d7cedee000000000000000000000000000000000c87632eb73c464f53c15ec127cc5c72fe6a413e74313e80395b55e122108e2984eee6f53742ce4445f455108002398fefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad02200000000000000000000000000000000040f1ed7a9f7c70a546088822088c476f8954681f3741cffb7e6614dcefe2963253599acbd263b988af3764331a273030000000000000000000000000000000007f9d150a4b34b9a6f872f9bbec4d2e0795d02c5411d6b3a844ab95ea87f9330662c8b0789e12a8f6dafa2f7cf2f13a12c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000002890e211b1969c72a15c0f24b21dbf672b2cd33ba9ab79790c07f0734709bf13bbef4f54bf17db9629cd7abfcf1fc2f000000000000000000000000000000000010f13eb17ab7ccaa0bf32b8d4d38760b72fa0fbbabe04017d9d8283f6dcc5500a336339400bdfca06749f7c1e08f748b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc93000000000000000000000000000000000f7e328026c07b116dcb8950273579e0c4af027bd3aa442a41d279b1b7d87d672154d2513669428e8f401db490404e6d0000000000000000000000000000000004208901e02756c5a2430200d562c0ddec0224446b3fca62cc98e9efcfb3508f50794301b026d47eb99aee210dd2f898202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000001309e8c1cd6ca596ab2c9605ed0e356cfb97c4079518b0241d40a3e0e4769a8e58c0ec6a7bda173fc427aaedaa275ff7000000000000000000000000000000000143b1d1bb451cd56d800d71a747173e56b75cbd6fd28ff4abacbc1dd87653abcae715882af29c29a1631850694c5aff8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a0000000000000000000000000000000013bf8880a6c95a8791b8ec37c2188e4c0c2cf188e2fba01a9e7e4b81116b10da49415a0588385156e4bbd45b168467e3000000000000000000000000000000000be052be3f3278259b6e01d9d81afb4d4215b0b738378e56719403e2ed31bb6e15e47c9986aac19f79001a76f35e4162ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a730000000000000000000000000000000013b27128d2e8bde36f11503986c226a1613ba0779de9b25686284d12bd995c83e0db9eb0b2ea759ee81bce0ed2c0c2ad00000000000000000000000000000000128d6ea67c8cc9ce6eb93111780989b4b33afff45a5075691026ebcc607e61b7a48e2549ce8286cfe4a72b182073f373b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f600000000000000000000000000000000137033427de6a6d23e0a2fc17d396114f8f4ca3e56e42936c96029c5b829b3b8b7ea46fa47fa39f6e5dbcd804873d3ab000000000000000000000000000000001986563cad41be453d14ea3f166c2ef2d89ada32a345554ea7c7141f6b1306af815579d7399c73039d1696fb62edcf80b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e472000000000000000000000000000000000f0878d6eda3d119eafa0e5cd0260cd5c9bed5fd3251f0eda5a6aab6b475ad8982b55a0c8c07b6921de77c4e23478f2f00000000000000000000000000000000181d4cc9e77cc1e21145457948923cee50db145dde59520e6ddc2da13c3380188856c220cbace98f7ac4bcd7dcbfb1812810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b8936389000000000000000000000000000000001267b7c2a91132c46ec835a5c2ea1f1c1021449d4ab3c14355777f1b7771787ca8b72b61563dc7587db6318c2661551f000000000000000000000000000000000d9f7257977b3f207e889678b72b584b84bf736bc23081d1267145a886e2dd6b669bcfd8b58414def71c27cae868f39a175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b6460000000000000000000000000000000017c223749282ef77696136edd0b30041b7743e40c2cadf8b491c2dee0730554e39ecdce41e45d647340e73bfe77407d900000000000000000000000000000000025924e40885fe566166bd4c5de6e5bdb3ab993c154ce908afeded5614cbb0c00e6ddd648263f17ebb3d81bd6a4f79afe7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000730c41758d12795c7e5540e4204e43c75a01dc6263833f8db435117429ddff6cf4fbffd6cc27f553b8524710aee9ab000000000000000000000000000000000154c3ac230c725594a3c985b7ad71d98c172de8764926e74f6932f5a5d40543b5060c5d604877e3a8df093927b0b171c6aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea50000000000000000000000000000000005c6852bd3eb4db383e9aa8c74f4c158888ada1c9ba07ab8c7b4abe9c05bca51f0065a29a814892303a42a6f2736043800000000000000000000000000000000086d733e758dd4f0f911df6cae3d678dee3500a53d8a364986d88c50576ca6bdcd10fd31f3cebc7a35f43de1d90ee4bc985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000008445e5c464c4e10fb0a10c97023c5a9b169d042971597eff4380821e44430e3790683c7c66afb89921f06199c72c87f0000000000000000000000000000000017e55467ed664833131b82a2875e22fc5b29a3808639e90741b731d4efc0420b4934fc75ebc2048e8196be55a600f9bca3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010,0000000000000000000000000000000004f03dd7334a0dfbc7cd3b74a8e06ccf23fad0802e44c95712e920a2b227f09ac3d62429f40bef8241abe41575cc541c000000000000000000000000000000001092b20e8618beabaee18b4752a5b320307d22fea2125b3d23c6ad178f0cf2af7a1a1c0af3cfc669929087235e2f8960 +00000000000000000000000000000000121d2cecb2c9892d69e6a15561688edb5020dc39fba96eac835c0577ef017191572f8bba780a608c41d53544d24a306100000000000000000000000000000000080c59704a5ef9251654458bebe25d949bd5c7793c438a50019a9a7cf26036f014fa3f024edb767d233dc09710d53daa709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000012be549d6b4efbe6e8c17393390f3cf190abe4621a16e951203747dc7faf6d6ac831582fefaff20c952502fe43e2020000000000000000000000000000000003112e26ed614405376dc1af80b9f1984439c0b67863f5cee6d3c44f74f320e66574aa1501376cf8f924efd83655a72b9ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e0000000000000000000000000000000013b6249bc071ab2f9f048531e6bd27a1b8a45d34c66623268402bb37f6be2d71bb5127461221089ffead4a9f6c708f0200000000000000000000000000000000016a321e986c6301240b1e9258423bb8f38012ad533b42cb487384d9af63713d4b84c383ebd4512145b3e518e0c935b1391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000069ae7a90e9402d4f9f1b4a8a799fd5bec30002683692a700ac3a25f8f0a8ef9fa9e6f34844a6c320877f4b4883f36e7000000000000000000000000000000001214fee37b448c79b5c3097dfb65f8b181f16f0daf54861d4e5e7297db7981f2ea20622d12acfac04c066fcd23169f0294f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000006e8cf07f48627571ab5fd1a6f988723465ea3f741b71b9aa9156c50e13d5481d66f7fe4006a54cb283c6d43eecb4ff9000000000000000000000000000000000ade4e4a949e6dcb45cfedf2eeb91abe406cccbbc7b4c7804b77d04fd7cbd91fd44f0053196bb344fb8ac1ffa37c83d470f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000006b130d66b74b99a2048127c24899ea6ccb0a53c4404f36371f30fc1ea99d02853d4555385a9fa022a552b85422daa71000000000000000000000000000000001824d4d0eebb0178947adf316258d297698ef4575d8ebc2bd300558df914fea04f0269fe67205db1e3dbbae74c0db22bbd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c0000000000000000000000000000000012ba5b9c8a86cb99337a7c4955b1a1b459c8a1a7eb6ea908bf27d5f7e41d5f3423c1ff44b4615c689df14709c703e9ae0000000000000000000000000000000008627851a30e33fecf67dff807bfc5430a77d0a85f1e4f8b790b2b072fb7b86d5e81b934ce197fdac6aea60414a616541a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001051ddad286eaf9c9ae5b3757c53e324acfcb6a1a7d5b490eb9479e337c9824bf619167bf8f2aa5c7f175da534e91a10000000000000000000000000000000000754b16cc6cc813c5c4d44eb4488b04abb659d89cf0dae5fd5f59f257cb396e139443a99b71079c5aa10f8f48465fc398ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd00000000000000000000000000000000035732a9fc03435f3dc3e31af693b1d1ae79110cd46d07541a35b956b928cb4a2de2a16cb8295aa8e8d0c74556b8189a000000000000000000000000000000000d4e762f40fcf43635151631fd6238ab3e1dcf578dcc84d462dbfadcdb621be918f1f0a7015377b5ff9c182494ae149c26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000018f1eb31d3d4e915cd1e0cec33b4838da1401c6667d8ea25209e4c5683dce96b1d7adb4feae7fdb80144c30145d7f35c00000000000000000000000000000000050693e8b9c90d12af4ded25e05df86a3e233425e2f77c7ca9e99b0868eb8d9337186113b078f8083a4273c9411ac1dfc470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb38000000000000000000000000000000000e1ca58d3eb507f977257ed8bdff474a05dee19a00818754e3a85f1cec882b8e3e0296d5c3788b101da669a716772936000000000000000000000000000000000532526ecf42eb00da76db02ab6236dc51a346f0a1271f1e9d721a40a4569d46fdb63e0211f7986b98475d81998dbf8be53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c92690000000000000000000000000000000017bcd6bf54d51fa12356f3428f02ad8ca31131a77951459d32c554e2dc2487be1bb9f10450e5d1f38af3cc7de1096a9a000000000000000000000000000000000b7b5ffa4d08175916fcc542660c85063e8420987b2e16ed2ef9464adf928a4c0b8e6d5dc870b4f00de8bbec6f0dbae3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f6200000000000000000000000000000000151e2e32203b03a054459fe391ff4a4e962ba5e10ff93a1592043ad968c9f968a6e50b5943e50815268a4abe055a1a4a0000000000000000000000000000000004bd116c6857c2f4efa087272df160b765dfdbb842a342f9cd3e5cff006030f32e5a8b60acd8a376378096743000b2fe95f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000ab6c3210ca0b70b2b3bb916f31e17b8632513b15a99c7cc61cd21181152bfc6ba6ebaf8e96a05d0d2d42a9dd3b61a53000000000000000000000000000000001308a33fccdd6cc8990c21fe7ed03bca42e3ae24bf07aebfe6878c2c8316a7a52477c929fc7c67a3a13ed811a2adda7b740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b00000000000000000000000000000000010db7de8485e5504211088ada8924386b36b7dee37170f73469bc77212d56c3dce9802c7599c83c5cc5b18883cca5845000000000000000000000000000000000ae8d817daba71325b57f81301c17f401a6870a13506de2a443602ed44b6b0824e6cb763ef556908f9b3f30010f86394f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000000fc19f1ad220ef5bd76cdd7d3ca08539a97514bb21429af5b1774d4c58a7e4ae137505fc240dd0ec01d1a9eb06a157c0000000000000000000000000000000017ce712d74d68568a945fbe2e0b21c180c58e9297f1f4dbfb0775a133832d4d8aa0688f031385190324f1e8ed65bd5378390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9,0000000000000000000000000000000016cd97de498b8809f4de87bcd5a66c41c231653fdec9bc1ebae0ab3b8c56258430bb13bf400f5f971a1ec558ee66ef02000000000000000000000000000000000cf57701a5038ec26e4027c3cc5a8cc298c2d86e9425ae027dacea92d4d475130487b2743c64459990e88768d8b7a4e0 +0000000000000000000000000000000016fb67277c28b5665f1b7aaeb1bd70f679b507a6b30f956a1fdc0d522e430cb4a9c089093cfd14714f25cc9498f89b610000000000000000000000000000000018ddf06c643bd77c953a0bde77e80e77334410d76910dfb587922e6dff23e821ebbee2dd546e65591726f9743defaf9a414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d0000000000000000000000000000000011d630f01000c6e1279f330893a18b903b7246031d7d05d80d4172b08e1da182594cd42934de3d1418445a76bc9c8189000000000000000000000000000000000c3e335aba4402bd3c711569e466293c15d89f4893ba91d8690e4eaf4c7962da458471e8c7f22c417abec313c2fd223399eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef942000000000000000000000000000000000c38e3a1c95f0faa10980976f83d85954813faea27c120fc3102de51096f6c3ce89fc4155c6fc878fbd18ffb32092d7800000000000000000000000000000000178d0c64b3b7da5b6f57c69bccbf73e329b18e29e9187a7af31b9b8e480b210dd36589540d77b3041472d9612b05693a49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee00000000000000000000000000000000146c5b46bb4194ec04b5b63f09e8066f24e350cc62fce016b8a25ae57877614162f2733a5df8909eeed2df30374004ba000000000000000000000000000000000cbb312823ea25bdbfc4afd00cb65748401b47ab7dbd5a40905162c1ea676268745af11a2770509eb74aad45663f7f5b6e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d000000000000000000000000000000000ef489c4443175873e33111e9ebb3140ca0796f13ce8d34b30d8fcb7b9130ea0574754e800fa0ad15d71c35a3584e11e0000000000000000000000000000000018bd8ba66d5b67537a03030f5ae56c01e640021ec2524a2cb4b2005ba267e737d27916dde1e94d1f15b6d3e1d480ad82d3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000182acbf5e02a0b1344779f7ced01961f418fa8ce94f939025110823e5d5116d771328362498324e1067a3419062341aa00000000000000000000000000000000174d3a7754b18715722a07ecff5ee3b7f30606c3c573770c88703b6e0abd9ff4aa4bd2879c4c0512f879af95554f47316de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000178c3a28f9333be85ff364329fe897660261092d9bddb36687cdbf5a7a450f27060a3aceaf45fb8acfd123116f195d8c0000000000000000000000000000000015e0a930af79ad263b115dc733560752cbc4453f111550fe3e9448b6818a75babbd0044b9b4f133bcbf16f8fb7586055a28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f0000000000000000000000000000000007fbd9b191af6a797c68ca85df2100b898e3a4d9569c717e3d02c259eb4dff3a1ea948e56001f33a3ee1c74eb966b6260000000000000000000000000000000003b892510d5073bc3597f8f513908077814a7efda2df6051c08f7347433703496e522d70ad4093f76a3e5288044ba5dca3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c0000000000000000000000000000000015d26d3ee6fc5f98584c206466d2c1a4323f597e0ad665b289e76184770e81856482c9f45ff8c891622d8de353b172e80000000000000000000000000000000017fe0582d363a30677bca1feb6d7f16be6b07d6e5d6b2a2080d07ca306d5cf733103f20403ceb486ec703277804e7971b90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d00000000000000000000000000000000108de390a69c6001124820072eb5d9ed9eb5b5a6199c33db1ab0239c447e009df4296f5324660e7ea1133df0c8e6a9de00000000000000000000000000000000040e7b3392a116c7289644f393bfb24d84b76d8378c042d86cb4af861af42374b709cb0ff5341e3ae9d21271c32c0a5914f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb5180000000000000000000000000000000001ed57bfdd0542efe8734b0af448c025eba4d60053b7b45baf682cd310f4c2ea07e708bccaed390c2b061c89c2855c9e000000000000000000000000000000001496190ccfc4bf428706ac344ed691fbcc7b9d6a456f2653f0da421a44653d4b1e9e967954b847a4e6014df15ef48719e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a100000000000000000000000000000000147e5056444c7ea97a319bc71a3ee4188f68b517b92c64f556d22382389c5bab95110728cbb7d525499cc3b2d70541b1000000000000000000000000000000000f05b91c8d05b31ef6497595ecee6a6766f03a006b4c2da408f4d7b7601915cef64be69735c269007fa23e5f91fb07148c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e038930000000000000000000000000000000011b1b7c28754f3dc8b21dc823fe02d617374bdb9b96dbca572eaf8897f98ce9409ce8a63eafcf5308d8236bc3c18b4960000000000000000000000000000000012360ef03ee4dbf0bad68232b8454a26b666d827bebac03da314b2631a45cd365248316f72e991004d0158f89ba5811839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc36520000000000000000000000000000000008b563f6f97fee7e2852b44d8e39ca314963b517116733924d2f57d9c4f202b47fb3fdb85fbca42ffedcee290050ef0f0000000000000000000000000000000016112f264c2b3c838b02b78822d27f6351860d10da3ccb763c1650420bf22755938cb45c7566a2df0e4aea4f0281262ac395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000a8397b009cac789cfd496f4f1237e92ae570f67b4bfe7e8c80171bb9d9cb53201c2ce112473b74646a4948d7c10c338000000000000000000000000000000000092b7425031fc7c328e3be114916a06305b62ffec8e7e93a591fc5f4f9022333cc664057ff6983677cfb998defe249553ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e1000000000000000000000000000000000df6739202d9f1f13145b697d5b78ccb84845710923a0f3bfd5a3f337e200b3ce5390aa185ddbbe8088462926a7f4b40000000000000000000000000000000000d00ec3648b2e5790ca7b05ff32c6bd3249296bd693f520f6d8385f15dbaa9f808d770f9ba28efdc4aa6bcf862c17c4abadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa,0000000000000000000000000000000002665808cfac4b9befb1f285c590d639132adf9d36a4fd460de0b3347303aa056a14780deaaa02072fbb08e1dea06b940000000000000000000000000000000001ef22acce32662085c53b2080df9354903f151089faffa43c5f6a1a306d2254fad240bb1ba3dba4927ea5640347bac4 +00000000000000000000000000000000194b906ed067bab0e26b9ff4c0ecd909c6aa23b5cab3a90d1761840b784bd2af6e9f9ca570ba6643d4781885553f3e4b000000000000000000000000000000000e8a480cf75e20cceb6e1d9db5594d19849aee6d45bb3ca7c0311bfbff8263420e0278b7e814088abb69e73bab6368a92c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000019ab570a48bf15ce6f007b528d7113cf423e1c04d9af9497dac47a69deaff52dc9fc4d202649fced07378b84fa1b0054000000000000000000000000000000000e3c2971aefe89a629600a243c7967ef001ee17f9ac452a8131ea44815ecb6596f4fca4f47a316f62234851dc485fc50b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c0000000000000000000000000000000001250315bb81e9ef7de73e709f18003018fc1c55f694c0e28152fdb244b07dd2d7812c3ecc4ba362fdda0707d02d697b00000000000000000000000000000000188a852c5850f471d4ed207d5782518f189cd08d63279c4cf19c76122df0e4663217f1cc8374c7a02d99bf6d59a80457ccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000000df12b5c659c17c808d8e875a1b9c125396cdc3d8a2bd6f99def15d9fdd1fc7fcbb309333cce1b778612d6114bba63b500000000000000000000000000000000019f11577152bcb0229e168a8e97804e8e00a58fc236c8ae59c575c07d6a3c1864b7c8132f245aeec55d999d54745cab99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000004b007a33b0ddefa5ca9379614f698dbdbbfc6bf8bedaa485dc360cc759ffa4ace304fc64071e8f228a8882d5bbdce22000000000000000000000000000000000927e9f018b8cbc2f21b72f0f19994705197d4b6ab3f03e231e51b9cb3d899fd8f8b71feadf3c9be61994936535c61e8338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d0000000000000000000000000000000005decc41dadef7dc4ebd8911af09974686531907e41dfa16c857fc3a2451b96069d06ce1159d47e6f1c97cdd932486d4000000000000000000000000000000000151d369a147cae5d78eaf7ed99623675491f20aa2cec9700053f853551208fa21e085962342072c96d79233bacc7adc1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a000000000000000000000000000000000fd41ed8d5b7e5ca6a6feae98592217dbe676accaf6e73062d9de9eced8af59563f7f441a50ffbb591b8a987c47988f80000000000000000000000000000000001199e002504726f2ce429cdb3da304f9b54a933c1937e8dc39a3a416d068cf46f411b207d9c6862a50962516b2867ea5202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000017cc9741662834dcee7af988d3e4de2c30d4f9e90f2b3f7ad07f756acc793c58acb2a04c2726129d0f0c959f1d3154650000000000000000000000000000000008052061afea4c307df56a72530effa73b34beea4d731b1562de1e985ef455d39b0d6c57008ec092241262dd611ec598a2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f97220000000000000000000000000000000019adb959f4807d3bf7e0616a8a3c02e9babc94b8ee9f8898f2ddbc8fed7a5bd88e83c70c5a98afa823a0f46560e32198000000000000000000000000000000001189adca458e0ef67fc686b5a94986be37c414cffcea5b4fd44430c8d5902512d84200007a93104048160ca3f5bbb9a8892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf0000000000000000000000000000000013d55a4b466ddafa04c5690628dc29deb0ae9115a4549767b2aa22b8aa02a13f1db82dc86fa3df85a6a15463fb0e7903000000000000000000000000000000001488a03340fadc9e8f7552273699870ad444ea513cc7bb91259ffa7cdd5e7377d8fb5510adc2502fb8124d7914af85d5fdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000a847c98ccbccdec67192529c3da593f1d6de5d7dc0bf4452e4f09e93c2c406d6eaea30431ba95568c92938150a00a05000000000000000000000000000000001201397edaef2f9b89dba7f67b22088cb954f95b9db3d1c11bd77aa0dc94def6283af2866a64f0028fdd87b587669f31660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000017f76412c8e679676eb464204348d591221ba17a1c90a22b2482991deee6b61edd7520ed10b0105426a15fa3282cbff000000000000000000000000000000000c65a821d170a9726e947868d861717e8cbcd2438e4d4b8ffcee38eaf033f8f3a57af68ab6314a52952a305db54ecb361ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af84090000000000000000000000000000000000b14fc1d34bd7d85fa96a4d12ee99a6d327347dc63608f94bd750e2096dcf11066e384ba3c68610c70dabae795e668c0000000000000000000000000000000004f3ac3e885cadfaa565b1ec15cb81e3fd4d561b2a8d92a9287bd0de893563676118d34a9ef3bb3112aa534605219feb2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000000979231339f20ffaa38ed21cfcef923fc9a4ff77f7d6fb4df212a530ff456a32f50a77d2e7f6d87c4a58270c006e68070000000000000000000000000000000011ff95871a91385ffeafd8a609a0c562bbeba71a110081e5db6c8035d8176067a528f4d1c6d7dad43b3bb8d090077e1357f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d000000000000000000000000000000000e2c7c67fd50bd2cc8ab18808a69d62bc2d3f110ef49a02259163f8fb152da6ca9cc771d1221d7719f9bc349e68594120000000000000000000000000000000008393769453eec7639d66525d6e875bbde7a4a28c434c82571468d496c4313e12414f929139c482569c003a6c0dccadf3017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f0000000000000000000000000000000012cfa8448935a292911ae6fc175f3049eae5e30d714b3439f55be9970ca959f218157097bf9837125bc8f772968b0d52000000000000000000000000000000001747193c5402daffffe4b1ba9034231321d01966befa174f526014d6c27fe3683eedefea8690b95c8f71fef1152929bd08bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd,000000000000000000000000000000000bedee9e836b3e046bba7fca7632d09a5a25fe1f0fd25cc6ae1d1e07d98ec52742a60bf346285488dc84b2360e0db58900000000000000000000000000000000071ef77988eea20a38fe33564689a39a7113b1715dddc1b212c6edab6bdea8de54089eb7b49b63296792bb2f4aa68733 +0000000000000000000000000000000000b7db363585b0061a4707a2ee67d6d7220e9209b4eb9a59c02aa6e177c948057826780f292dbdd824d67ca9f78864cb000000000000000000000000000000000a31f49bfddb5c48730e1cd429f128a540ff44b6a5031e7975ec0c6661f9f3f2b79ccd2d13cc1b50d50ef9c7f658d412cc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d000000000000000000000000000000000d4fb266e9fb18590037394b18971cad5840bf89047dc11e52c90284642be7e27007c62a1e331a2f90ae67313efcbc0000000000000000000000000000000000047b518cd6a7d7c4d262d1f9f5f11480e30c626d45fee2d6caa274aa1353035a3c42ba96b5875de36442aa5d4b92d6d257b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000000cdc9bca5cc807710948d5189dfadca2cdfa6fca5496234f13024efd84a37070a2fd51a609c4ed6aab54f8687ac9700200000000000000000000000000000000011bc450e4222090603ccfaf7c1dee67bbd59aadafc3810d3aaa8362fe43f48952320e25bebef482c5d21a541400df5a03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000000015d20abf274edf0c9d45c2675e4af7987e98005b2a0d128ba7df6b16b88784a7134d37d0da2da02557f88d26de33f00000000000000000000000000000000190adb20cb0f5902f7e92f79dd6e7d214eb892834611ef222e9a80ade4c7cf96e0b5f9382b61715e1701c7e9cc4f4ba5976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f0000000000000000000000000000000017dcf175327086e058e4696d689f2e8a167aca5616f2317b7673850a2272fd5742b70eb362b37874d573cfefa25ce3ce000000000000000000000000000000000e5e1af08f6174641aaf4f1584ac40d53c393314dcb1c405263e8689558445196371e2858a4f44d605550fe0f15962223aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c830000000000000000000000000000000008456d980ecc64b04a203d61bdb78bad67b4568b2dd9a123634cefbd7f7077cd9a4c038c0aa3654915c12242dc594b37000000000000000000000000000000000adbd582b0a8ac28ab21961476e163255089c2d362bfe9daa7007a2c9d8d261593eab22a6bdaa9740da81efaa24cc3d5f9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000b02d326ecb5c04ccea4cc3d29f82117f3d97f788b8e70cbb346d43d27e674540c7a94d242d290e55d992eebac546c9b0000000000000000000000000000000013901f8dd68285d73093c30b37419ef8e4b28371474a040a2ea293f7274ec4d6ced0f32686405205324740884306e3a693be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000181bf2fe4bc67a1d10335a0ca9427f603610646de485a7cf039f0706c0a0858ea694db3b3e5ca85317c98b5cd75865420000000000000000000000000000000014b1b652e2ec7d05956705f692860b83713c5cc98c6532b3df50259f27f92d485e8df846883a4af4e46020ae54038d955d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd3000000000000000000000000000000001084f77ef23ac990b43363db38d652f0e6dc04a4bc395c8018083fae6fa6e42f463af7748d71f65b14f94632ca0eaaae0000000000000000000000000000000004ebfd75ecc9cea5e49082e1adacf6b50e4f14600d9343f6459900605c5f36ee51e95408a3005c0c1093e41794c282a0854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b2400000000000000000000000000000000062a062d2ccf5c131e1278a63e713ebcf8a221e429b52b3a7688f7e68a12558fd0f584e03835daa3988233d6a84010310000000000000000000000000000000013e9330d29635892fbe0742d1a8c96ef527b78ae389385a366b6dcc6a04b8cd1d5b8bbb79ea649179e78fc061d23cafd6f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000b131e0623b7f30bad70145cc4964257053f2ead992d28aa5b24c04bc316d479d077af0ff409cd395a86b808bd3e4f02000000000000000000000000000000000380fe6e79e5e0a399365d73244f2962facda8b7b381c111508819309ec5b1d3d8783067245dca26641a966969dcd0ab191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e20000000000000000000000000000000014971f46efae601309f3d16c15ab5c46ac48d2199431fd959cbf4efb768ebcc4f410fd66de04d3280659004a6b54e64700000000000000000000000000000000113e6438dd8088e73eed84d24ec286a45ca51f0fda88c7ae3f1e6a2195f6b11877e606773bb9a8db19dc92c3b0729754b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000006fceb59d8baea4a10aa9f1e825631e28bdd379189eb464a3c6d2482319a09337a78173f9207a58ce15bb1c518b39328000000000000000000000000000000001609e1ff34ad2e4bea4cfc4a993d8d52a1a8676679c91544ded432adfd7fdb5c016f8d825af1c6b8207170d05c10e04a7d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000069e48b113b822cdfc02f2f0efa02724193a5f032dea902b189290db91c6e4550fb33e2915eaa8e56ef329d6c61a0d95000000000000000000000000000000001426fa2fe7c160e8e32c3252383a7c7967b3515c3f76eeefaa5c76f02b3308d86ab95f9a3a0dfacfa6dc12eed2f3a5e8a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000000c173c6c949a7f21df4431025ce16c18b1008c75b8b1b23d03122c7c6ef714b5741804ec7aa5ac40f6b72a1a74ca5c340000000000000000000000000000000001b32d54f8f9839dc39e08bc6a5f0efc5db9bdf487a60004ee135c30efda577d187d9b9e68bdcdad558f2028d66e935cc0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc00000000000000000000000000000000037d1cbe4534b82ee79b2c957a6eb19d18dd3f3f6faf3313b0ce12a98953190aeb55f9d494bbac4f56ca6986c65f7668000000000000000000000000000000000734f505be94516149bcd6302a2c9f2f9b952c9e614c8e90b5466073a7e734ca203fcca242cb97abe1c532d7f59a783aaf833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94,0000000000000000000000000000000012de1cacd791ab81eb49d2e259f36abfd90e8c7ed4f9f23a4c95cb48cc7bf9df233951291342d1266d016a7fcc6b9d53000000000000000000000000000000001938d574d02b8802a45ddf8488340f2397e93125678400cfbd8e64cc3805704bd5f85f8fb2f64a96ec1089b03d9d1c24 +0000000000000000000000000000000017387ec261c6dea7bbcaf4537182de1620adaa5842cf52c8b5b6cd851ca3c27abafa584547db7366455281d82d3f83ea000000000000000000000000000000000246dc1cc9773db7151e05d131398146b28850e97f6b13694d696be374095fb153b206723afcafddd4b3b56bb15bf778b16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000a909dad5029834df0202c298a577f897a376b205812d79e0bb58b91ca11262a766dc396f69fd2b199dbfa52670515ea0000000000000000000000000000000003737873dec25f011b24543071a61590646e4319a2128eed87d40193a22c47b1a6c0f807ba3115a7e45823e5a4bb433dcf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000002b959df6a1badcd306209c1f3c4c496cbfe4f00995cb4403b04ffa6b9f2c8dd9875a2747354a653a74fbb605eea50b00000000000000000000000000000000004d6b15939c8e282a5995c8c0b67fcba3171b35ecc039fcf32d1e96671698d8a9fd2cbcaa7019cfd01e56d68cac64fe51cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f421290000000000000000000000000000000006be65e97560a40394d9295fac0029a0c889bf803f09926359a1ac40deb7777cea7dc5d2c4a9600328605fb994f87b5600000000000000000000000000000000128249d2137f7ab1c5622a8eb1c59ee8ed792fe6b09e4d868c9d9ba900a8d28bde5b783ca591f79e1d729c99e10d5cf6d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf00000000000000000000000000000000002244c1e55324a4aeaa07c414cd3f9872290e729c1cf1c05a5b1de3443e12b2335cd36f0e84f11f04b62af37005ce0ad00000000000000000000000000000000151684aed084d38aba7127434ea73e63219c4f5b4b92017142d19d0330417fb2806e31440e0bb7c9fca2bc8dec73072f02e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000009f0c959d995af6cd0d45750cb35a28461d0f791e59b2975ba4edbd7db015858b41b3b7c5c2da0a4c6a5d7b4e855329d0000000000000000000000000000000012d495ae3096c2399149afd00f640f8840c3f8e5dda5835b62ef0dd8bb7303f522692efa72c37190bf6808ed3d4fe8e89b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e000000000000000000000000000000000334cef31670360b5ac7550b55cb03b770660ee79816a2742c059b2ed6cd9d5c53c5ca54793a9912ddd7603d975c3f58000000000000000000000000000000000144f221db562b0daefb20238a527a10ff1ccc279eda86723668f8ada40b41a2825f82f5ee5d619fb193b9c2b4180d932f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa0400000000000000000000000000000000037f14fb2d51b25cc04768d50fe26c1e156a3478b80e32da980f7e8d5692a4cf282f75e5d8be325ccb4223c7ec2c04af0000000000000000000000000000000004eaf2c069c96dcf18051a2c1d7ea876af67bf344070415894c07b3dd69330d8ca18e1313ff57d83b70e5cda3c9ea8582d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000104c1f5bdf874c91020d410d8fe74834cf15f341b86e66ac693003766484cffaef2c57fab5888f02f5ebfe1b9ef2fffd0000000000000000000000000000000014a2f6d185c2989ecbb766179c0b0d0713ea9714da2ac555bebf0522ff00766ea7e39c8237f8515224fd096d2b1ede34efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d80000000000000000000000000000000003ebae6a494d46ade2dc7d4630a420b519df7086b57a33da178616d4242fc20e4d02d38b5d00675d2cfdb51adc1921f6000000000000000000000000000000000edc56e6eb4aa8556225d928408702042d49cf3e1410e1c78d8ed5832ecae449d17c9d8f2a89ffbfaf01bfcc85ebc1669848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000000f96864832e7a9602196f0abba78f456300796d5afc18b0ff0c5c23b61865256fe5cfb960bcc8f73231c21b1084cf04000000000000000000000000000000000c59dcca2249b5b01c1b54be0e4114ae8228bc150e5ac7593bdf96136cd7cdd7562eb936ddc5c9e42bd93abe91bac5b0cbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000422c390e56fa27e3d7d5da1b2ef00a29d5340026becefc095d4cfe830208d3b94cbf5ae6f4506ae45d04764acc8044c000000000000000000000000000000000d1cc7c147cbedefa854fb9764352a9689fd157cb2540fe070ad7f6f3eaf761b4670ab9334de4002fa811aa7a01aaad479eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d6300000000000000000000000000000000018000e31f0ca43417865a1cc128f33383106f5bea71015e9e77cc5320cc3e5704e437ae8d84d96f2c4530c41bfad29b0000000000000000000000000000000011a74c3779c8f351d39db6745210972f4f299009afff643e944f30dbc4367e17271c688e1858e6f79b6636787fa56e6b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000002a6c7367526da989ae093350b7c1ee9013f977d6e75563f996e1f15cd4279932a3e4060a26262f27403966a7e0111f200000000000000000000000000000000038a85281b09e5e68d7e31bfc323c9c250b42248cbae47f9c018d72f3e69ec572779d7f8fc6ed3f027499741565274e5eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000b788a0d47da0daa1f0d802d340e68f9bdb5ddf91875732b4ae82f1a89ebb5787ec1c9f539b82e3c94c36a5df4ddb4ad0000000000000000000000000000000016f46ff55e9f1e19a332ba4ba43d66d2a11a2728a484a719ddfc9e223b54224db55af162e73a8f5c3355f0127a6b7cb652b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000379d868d91304b24e19694937402bb685f064ec5a89b49e243e2ab7eff5ca0a2023af9828c4ce9f768a1d6488c10e110000000000000000000000000000000011a9b9432ab253d47e8dff776c8b5810ecf7f7aae2ff36ce06b87436b4e20c22596c7713def3886549a36bb535a96fd1e83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384,0000000000000000000000000000000001a50ddc4d4542aae264dce69f5f5e93353a9eba30b177b33c2f74ef61149a301599beecc4d0f78078dbb9638eea526200000000000000000000000000000000160a2fd67df885232b06d3dead7ffca93cf0434a3e9a5412ed3b4c342819b44aad5385bf5ab60bc2b7191722d1372183 +0000000000000000000000000000000008788a699276abcc2d8e4a35a9d0ddcbd8006a809799374ffd56ee8afa1a89461602d92fae6eba7fdd4045ba34d917e5000000000000000000000000000000000c8e03ca0da00c6829e2d7c49360e67e46ce12e0c99cb3d957119bd9c8bcac8e03cf32ec71db2a18568157f4b44cd4dca4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b0000000000000000000000000000000001373b4a0653f48c205b36bc50541a43abfcf35974a584953bbc40f5cabdc3ac2047bb86267cdad1e8f00766682d2e6f000000000000000000000000000000000faa8c977b4db7a3c9e65d9cd5af4ffd2d7d67fb038d92c1096124312a98d94e6dc3f3b8de73eeb057cdeec4bc0e0482bd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000012dddf5b96d0dfd2fd619b634b086ba5d5f25a53e93938559a7adef7b988749ca27d14f2ddbf5a9e7e6c1914403a45b900000000000000000000000000000000044b5c8041fa805cf2ec5a243814308369e5af534729cc9608fd17583a48132809f507cdb5b76fd6597fcababa865ddaefbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000017c5807458fbb875593ebfe83c49ac2493ddaa15671a59032528e0464360c64bf564f9727959108940ccbdb8d01f329e00000000000000000000000000000000121dcb798111976daed483f4efc95f968f5212cdfaaf0497eab0419a1b55c7ee4e2ea26716d0c1a8aded4804228b8ad860d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000717296a20594f940a05ed3ce4bf2b7779c428b33a297087e08b2283b33228a7d4d5b9c49a71ce036d6f2a078d8344540000000000000000000000000000000000fc78f64a461fb66ca081ff4d67369058e57e5ae0e284562161fc3244bae0b9c70ea6abb2d0da6cea4942530c64ea0e386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee8000000000000000000000000000000000584bbc0c537e7f37ee64604a134d5fc21d838c51a89c608ff9e3684357ed7f931fbd4fa4a5a56d20304d6f6f072316600000000000000000000000000000000191ea3bf1016b6402dca2856845017dc49c74d06bc3c5f10de379e04302c469015f205cfc97fa142727ba7e2439c15575ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d000000000000000000000000000000000f1ff007860ac58bb04d992d639a5f882c3c647e76e2d6d96888a55648f81ad8b7edb3dc2b0e56b6f2dadca73db7cbda000000000000000000000000000000000fbb952eff64505e02e0ab34875d7a79c72ab724cea7cd8f28df2578b50f78601b9a9eb4170e1b7e8d94d9db252e23c592837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c00000000000000000000000000000000011dad65f38b4c24527ce87f8893c8331a32a3d058cddcdec9f8708a3bd1e31871cbdcf944ec14d5f101b8d138b2a46c0000000000000000000000000000000012a6981c5100177e643dc421c5917896455107c5995b1e969bb18b4b2752700a18281f732530af9684db180290dcb138127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000bb4dccab7abf3f5393a338a3a07fc20d337ba2ec3b33227e8c9a832900f347d582d88cab123dab489daf471191538b20000000000000000000000000000000008589985e2952db000968a793cc0fb5bd1764ab1ecdc6f278a11dd4a1de87823016e14e9fdd682e6c489192b154cb997ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac6148900000000000000000000000000000000056fd39f2a5356870a3ebfedf35769710c16b2f2eb4a061c936f6de4f9001990769795b1c756d7c67623ce3931ea1b5a000000000000000000000000000000000b7fcba295d34fc38739c4b36689653731fa46e6029bf8e38ccb6af5ae08ffc09c86abe0de62230844a66cbde876f52663da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f000000000000000000000000000000000039b08e7110b0d17c41709378f75844379c662f7f3dc480bead6bd4996de2d8889f458aabca142d50ba0e34c0c327970000000000000000000000000000000013363b0da7c7dd343ffcf6cc5e9ddb5b51480b04a472c38f90ee08cc97507f5dd665e15a160860c6df4dfec154c1504bef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e00000000000000000000000000000000034edf693e1b201be14c496860d508d12d9180b62cf3bd2407b8ff95b93da67dc0c4c43344614dfed516d7828ffda4b20000000000000000000000000000000015246f388664b1d817fd17831f85d84cdaf31212f093820835f201c3fe6ac99d67cdcfdda3c2d74d75d5114e32c65cd7bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f000000000000000000000000000000000982b8886abbfe18cfaf4c0e16c2e7045973f5efa27e5cdb56443a22f5434e2456cad041bba3e6deafb072e5fc40f10f0000000000000000000000000000000016a45f684caf0eec143cf8f31ed5111750d8c4f1092651a471cb88cf534e81df117e3b0e8238270d3b03aeedf04d7a9f70363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a0000000000000000000000000000000016d13da2900e2b2ef8f6ae295bf16d100d451ac4709455c55323988c71ea6aef694de0fa5a33cdd7fa2512d3548e39a70000000000000000000000000000000005795677001cab950d1a7b802bb14f9203036f15fb335daa5f0b0ece4bcfcd3b31b581b439da46452e4e688f16685e37997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e88000000000000000000000000000000000b7d422ac85798cb5ef5548805bd6d3de20ada4994fc38355e92cbf0d0c9da356a5e9e1674a50a017643f652f71226e8000000000000000000000000000000001715616f53a501acbaaec470121caac29827b6b7bfd7e689d8e48822d2c464ae50158662e69c1c232ecd09f5ec946a7a5ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000010e7530ba600fa531878ad0f798a0ede2d025f149ca980bcdbb0e4316e8d2e7d2b248619369e36d21dfd766aba5918070000000000000000000000000000000000ecfa746f1cadbed34fc1ee3483307de400ded69af4a7dbb598802b7908495519b0cd4c1fa98c9cd8e82daf8b3e836e03c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26,0000000000000000000000000000000016bbc2dfb25ff6cb9cd03b0cc91f53c6ed9be933746d59e78c7251080856cf47c12bbecda442840675b03a812aaa5d98000000000000000000000000000000000555b0683640ef1795c918f5d10e8b3e07a09030fbf26eb34bebfbeac34e13c93ecf53d54dec5c8654cdedab2fd60570 +000000000000000000000000000000000f08b9765910d97ac42bc31d4b4c8bad5f3db3fe5374f11ae1c08af41ee226bbb4b0869b039fa81a935025de11b1d1fe000000000000000000000000000000000ea29999ba91652e2e6dfdf77595b44da8e5cddf2e3ae6c782dbf1f972717833d03478bb8651bc0cc7946d813371aaba2849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd000000000000000000000000000000000997b39892bfe0c67c296135573975801ddb99d06de02d96853f44336fdaa25dcfe253708583f415d882115ec68dbaed000000000000000000000000000000000a88e2f75817ce91c7dbe365d67aca52186b5e94c735e5893bef6aabc61f015f854f9bd110d3201be6f35147f9f9b8fce6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba6990000000000000000000000000000000012a730eaf214a874448e654a06604c4b9218f163b979bd3700b7a7fa3856b814c380532afce59b6253344da5bffd684600000000000000000000000000000000182fb293f9a63c705501aa0ec7ca72698d7d4d50af3a0f68ee849cd3f82ac24aca2e2ee813f68e708991a97e58f2d03d8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b0000000000000000000000000000000015bbe08935721cc6199f9255379589a4512c178bbedf69c82a0d9cba22b285730d4f27a3629d92574b2c24dbe09300de0000000000000000000000000000000007aba01238f2c4ef0192fea78fbccf2e669f802a2822baf067632daadbc1d07e70095c14bae959a0f706092b0be10335c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e40000000000000000000000000000000011727d6d6cff667f5bdec92a3b502f9d9fbcded2ef12ac058ac51ccb4064443b7a2671e9ffa2fefd9b121d89bb4ded1e000000000000000000000000000000000960f8ac1e52246529fcc6f8f7cbaf42677297c00022d312e0deb5fc45d3685bb33fd68c193758258439864ba4a073e5650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd50000000000000000000000000000000014b3a9296c87b54f8f51b935a8d9ec0af44d711e3109e75fe34f07d0705e9ebf0ba5e81dd8b7e3c4b4f862570637a7f80000000000000000000000000000000005b834857b8629cdbf514e5ac2e0e2a45e4374c287bab5e4c163d669e7b1a36c72cec1ab7d857e28f2633a6e5f298f55c067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ad8c626ba39823a33d17a4f06cf17d29e9e0ae3f28db0b369fa0bb4b7343115fb3ded39862381822c3b2d74ab7f70e800000000000000000000000000000000117230d8da035f40c181b50c12370f159748955f63ee1eb61e8242e476575e9aaf16bd43b7e79a35ab4e2da20f43fd92b448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd00000000000000000000000000000000027764a17af5328811b305c21b0fecc54a3f225eaaedbf453ea4c0724fdbd481873d84b1a7ffbdc7f1cb07c2d1efaf5c000000000000000000000000000000001090ec8d750ceecf682de76d4794f9a8bbbf3a3f4ab591fe882613c1b6db0912696974a1f2ce349bd8c79acb4891719d441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee350000000000000000000000000000000015e707430eae84b75946f21e1fb0b6ede203b843671911923efd9674421a92ff13cd900bee1b27d70b8e8cbeccb165930000000000000000000000000000000001263ed28f531d8197606a038d7d7c3e1d732690cd69f52533470f6fbef193be5e63d5af0dee3aa8a73a23253533f8223020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c0000000000000000000000000000000007fe70102db7df6529f732b5cc2b1caef0fe03af9824a5097922dc0b07e5ff32bc195fbdfd7b5e4b2bbcd75b1badc6ef0000000000000000000000000000000011b40afd78bb5e835227e5a08f94f7c70b06dc010f5a710a025f589521543eaff27d789d4de10fd4020879b45bc0a9dc82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000e4df86963d375710c681c5b3910fe79446e73e00613bd554ee20f47fa9e2b0cfb6c14a29ed6dab0a56c49708fc624d80000000000000000000000000000000010029bbd62162cbca140c56354ea070ae3f1028e438c70dce31e7bc8691541e59e9168e9b689c19d177d4fd68f8b1081338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b00000000000000000000000000000000097f3f61b164193da313d88429a4f34b0ef2f864ad8fdf7183c3e1da02dcbf0ddeba9bc04a7594516e6255ed59527e110000000000000000000000000000000008133f297b8da5dac5e1ac3db3073587b92a5d821949968c125e5c9c79a19b5945ab47fb0ce5d6f4269231596b157826d29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee7502100000000000000000000000000000000028cacc78001b805c3e43e92fb8c4477778ce81fca9068240e0088e344cc8201ed5bba52e7ee09d5ea6f982f30d6ea2e0000000000000000000000000000000012c5db0995324657574a27c48313674d2ad3aa931cee78ade96408c5e04e6f5f8eae88018511ff156bcc787970ec40ab675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb25000000000000000000000000000000001768f68b0ec15fdd37c3ad9445e53a582ab5546f9eeec590b84e11f5a72585eada71129d1b93a72b334bec4df57ea4c40000000000000000000000000000000004d6e137e66243b56bbaaac98717061b36545c1c3e24801e6e054bdaaa6d28d641821a51233175f5e5823b7d2b7b42cc544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff70000000000000000000000000000000008caba9658e420fa17950c995efd00447bd5074af9b57122240d4e709229d382e371d7de867005745a35a2a7d68fee8200000000000000000000000000000000072e0c25435616f157284b48fc8da4a3fdaefc4f6d484e071cbe648fedf30b5da4457852d7715741615317e21110d4c2bb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a0000000000000000000000000000000016c418a3b3f054188d6891ddadb19c00ec629a3ae0f49cb1b6801a9db0afb1b5e473c75cc8e9f352adf7ce8ac738ae0100000000000000000000000000000000110b8099a39e40541dab01e10314a0cc10fd2277c8766c7c73d32d7d0c6edd3ed3984c8bce249de4776920dfa28ee86994a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc,0000000000000000000000000000000011104fff0cde20e65e73185321d26defcbce9c9d3c27923a2366730c1b560da80066734c61e0c42bac1da45787f39e2500000000000000000000000000000000066efa830182669715da4dbafc6434be27a2e95140cb39738fa75cfba2b5786d27fd727df4e9b0195b49be2dcb9c2c1c +00000000000000000000000000000000188c13fde41e3c7d84ef3b5d1fa869dff4bf02cc8448ae49c6b72cc005bd06916a5d0a74fd770bbdd3d2c58839840095000000000000000000000000000000001637ed432b4ac6b5021aac0c9d5f084e1f6c541c101a3d650861f7d860572795f04e986c4a890ea0ec049da7c6025fa3f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c27700000000000000000000000000000000063a1afe2f64f1d04f7a5aa727cbd0e9dd9b66234120118db1f8fc3b90ae50cf493c3c4a48949441cc1e46488972d39e00000000000000000000000000000000049261c42dea531a6e8fd82f77605ad0cc9addb23e429f03f1aaf2fb8d9dddaa89101bd5b5b169dce793de9bcafc3b5ddcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec0880000000000000000000000000000000012ecb0f3bb6fbb4802479611a25781ab09c81ff7175170805ebadbc5f25d2c40bcaca855ece57f481160d49af008d2b3000000000000000000000000000000000bc4bccd65e010b69676d3c226057528dbe08271d65f83a918b06969c1d5303cb7383645fc19548eadb83649ecc54a551d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c67000000000000000000000000000000000e3dfb86c2eefe0b25f117484a9d693702496124fd0dda80830a4e917bc418a793519dc269fd4932236f73506ecc949300000000000000000000000000000000140faa4b38ace6e80e5d3fdd57079c215792672ce651563eb013a90e66665dccf6bfc9f9df145d34894e3972eb524f86310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a160000000000000000000000000000000016e70554f8580b8e9c5e421c6a6495df7df846ad67d5d4334e9aa89f7e3fae505a2d335d21624e66aa542dccf38081e0000000000000000000000000000000001090383d5f42c056c291a4c4c6127315849c647783a556aba3dc41c52545549d67560bdd697fd1f47dace750483ec9b72fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000e8ba15ec58e5de08935384a3674418942311beff3887d7b5b81da0d03348791e4b17a06397e33e988ac6719f4d6f5c300000000000000000000000000000000159841665c915844ed85abdec0c1e78f178df2511da4d3be989f27063a8e572fe746b20e3aff056a63f4832d82a7cc75aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef00000000000000000000000000000000167848a43b68c8f4c205613e1440f940735d7d44eb1b046e63ce50fe8d7acc5b2c020fa936d6e07347a7858be57870e5000000000000000000000000000000000aed7f9b7108aa4e7445be41bba256667ce7587a867b9b8ca70d3c42155521ea3bebbfe01bab038969721364eb758be10227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa8000000000000000000000000000000000699a81c47bcab8342b11a207af072cededbadd374aa79f6b401e4bd5d429a0443234522a8955b3a62a21ef6697410270000000000000000000000000000000008ec25a0e0dc6a3c8906a1b3413f522440d56f67fb780545fa022026c6faae016108cb6eb23d6d6d519a4aa790327ae6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc0000000000000000000000000000000009c2158ea44c3b590df30e15f97ebda263670c1bba0d97ceda7ea674af0e61f0b5928fe0bdcd8f18efe5340525259b4c0000000000000000000000000000000019a5534906413fdacde78ffb03e6564d8beaa155f86e4f19be2188854a8709e82d2ade21621934c1aef8be723ea91a141a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec000000000000000000000000000000000165b15830a84e786d563cc3c5117a3e7dbe9dda178bafd225503467ea4c9aa894294c4fda58734eba9864796974a016000000000000000000000000000000001285a2be50f38fa6a068b75386d468d8fc1c11405291e794d5aa5157cc81d7d66c1095f2fd9289f1306f74596e9b5c21b206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000019d5819c1c4f10c83ca6f1596e6cf9901611c1407d6d7abab989333b37a8c21cc3deb039722a51e2dec161c38f3ce74200000000000000000000000000000000136d05ff33253260cbbfea0390e78cf66845afb4ddd0b684b928da017fbdf6b0e840431064e6e6d5bc8e417a74c811ab3a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e000000000000000000000000000000000593e66a323cf3efa13fe19cde7a3c254c90b23bc836e1f437f4a4b85790f325f0746147aeb1d0447022bb138178bff50000000000000000000000000000000011a4b1222d0b49a27e66cd34a12f252296ecd1aeda435035f06c059aa3e6ba69acd1ae6d7da394f32ab78538f4e50a351231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa000000000000000000000000000000000613bef17f6b6b39f9f6bde785a82d2e4c368ef231d8cb89940059ac2c16bdd707170b660c0faef9e927ff7a72f6712e000000000000000000000000000000000fc85913ebe30f0af146df556c6984ab442b286fa70ee00d39a802f4c76c3e41cee68802982ea42fb25d4bb04593c0b5393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000003da5997b7b3677f6cb03fe969e328549b1c0b083a6df457a70f1276d10e01d65feaa5a36cfad19dbe41cad9eba2fe73000000000000000000000000000000000345176bf6a03a49ae0b6d89d07548ed47dd67dd620e5e29066d09a00a7e3bd4b7fcb79b114a046dcc0c705068f71b50d412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000105ed7acf8c7c116842dc159553499aac7b8beb36dfd7eb717c571ad4ee1f86b82b736b72c2936925afdc3c739e0ad56000000000000000000000000000000000618b8fbf8a2aa2d1030c6304655b1df3cf8e8260b7b2d97639bd857d58606d0eceff7ff0fc1a811396552719407daee5b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f1000000000000000000000000000000000eabffb8ece92d4b22ee47560984b3efc33913953dcdf5e22771bb8db2cd8eceea21a2b14d70b1d467d692371ff499a300000000000000000000000000000000143282a2cc502f477be295d5fb2ec847cc988e43f72be848464eb4c1dcd0b1ab66a6cc30dd4b465050f6c37e8b8e08a7ab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5,000000000000000000000000000000000378217eb65cf5ff48e40ebbfbf03c307aabb63955f755a93bfbea0c176e90dc986a8202c6ea4e9ecea176068cfc62440000000000000000000000000000000012ac5e1a0c75ac5b96af3959a65eed134dac6b35559cd923facd2062ee06cb3ae2018db7d27dad2927b3e9c2e3dada33 +000000000000000000000000000000000d6ab2022d950cd2ad2f0087a079e435634a1e24008d12a349836cb7297defe857cadf3adf061e8b55ece662dd36ca280000000000000000000000000000000007682f1ced1ac2aca6ee9de682c7a6743fd32264eb0a087eb1df7c520c5748cd598be45213b398b073dccbb6bd67b44c3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e000000000000000000000000000000000927b5590892a4b897ff2d6ef6d5abe32bec8233bc5f35ea9ace2ec516037a8f3d162b0161c91d4e06d80d73528a6ba400000000000000000000000000000000064d3d8340eea43bb2d54dd6f5d9d49fc2275ca1ae7212329a11ed9a94c70c80584cb6ccc1eb653f001a1c1c4306e702b3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000322791d0e53364128288e40b621e6c47324dafcf86e9a8590a79eddc8d3e6c9d74cf9721115550e7e33868ced39cc4700000000000000000000000000000000112a246f82756d88f30e74b3f5df21e18ffc9cccd713e6509572338ccb4f52cbc0c3a6d5b5c112e304f90ffb9179238521953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b8250000000000000000000000000000000010bdece8fbaa604439e942e2c78aa5904cc1a0532d5bbf624794d3f10f4b64df30838799e374982feaa7346c039c08ad000000000000000000000000000000001085372e79e1046c870b1d49a2a8ea83bcddd6bb8718c7cb340dd3032739319c54eb947d518c7e17d6e603dd3539f269505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000001551cb2abe299a01cfba81bb306457b662ad57858a30d55e0ae0c0f5851483123c388ba06ead8ec4fad0b1e4f69ddd6b00000000000000000000000000000000159e5ffc459d38a6b1e49b30647939f37c0d4fc02b83f9dbac123d64535752977005e0cb1232ebaa7cf0bfdc203ccaeac4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f0000000000000000000000000000000004111e81afa9fd09e39df891cbb99d9b62205777bebee33b2914e24570db46f75db5dbe2e9831c50f9717dc317f05ceb000000000000000000000000000000000a999eb350750cd505ea9de43945cfb0c9c4ea412cb0f0e769e62e47d08f8d50392d3a5e821f1e9c947990e6398b5ec699762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb000000000000000000000000000000001496d3b0062e9e7166d777d90553545ee7dfdbdacb355fa7ecfecd65bcb96321aec0fd835b32c8bce462c87a2b52a58f000000000000000000000000000000000ef77e6ddce1e0eae50a1c663374c31a0c5846d6c2d777bb2f4831ecc806ac28591c3ab0222a6cc7821a45ddde1ce23e298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000194bd82f02047bc08871e431ebde41327a60e838d3a1ce6eb5470ba21a9b863025c8663f7d509a73847ed41515fdd3ac0000000000000000000000000000000006c9303814ddedc68b0047b5b2f0333cf226908dcb14ccc0aae4e14456a0c83eb4f498d559a649bb64bc78900a788a4b177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e573000000000000000000000000000000000736fc761eca44cd197ec6fc680de349f96e5294e42648825ce9262fef91766a8d7a084e5b598b5b47d947548e0c61860000000000000000000000000000000018eedf050da521b9af0ce2007cd664e2760320056e14ddb162db5cae78ed7ec859bad03fc60caa06081f0c24bb130ea4cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a990000000000000000000000000000000018a8e4887f0c08dfb7a741858580a1e0ba7e7ee1959284ad0955beb186e84a5d503ffe4000d5a8641575540b6b7a3885000000000000000000000000000000001946ae0b124fb60fb4dd32181783564dfb8ed0616a220d5650fcc1f6968ff70dc74535c71b0cf1019eb038c19cef0caae03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f5030000000000000000000000000000000003cc2093935fcacc3fbe4429868c7b31fe8c8b12c1184e2181dc8da4d56b9b3ace85ad8d6b850deccd047eb002acc8fe0000000000000000000000000000000008cebb95902576d96a3a257ccfe76bc727174e08d70492dbc2132b9d5f534de3b6a7baac2d90338278064565aa67b22c804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f10000000000000000000000000000000003a03e6c183afe6aae9bee030f46086032e9d81fe337e7e1c77ac6c903fb33154bebdc15e81422f057ba1853c1f7cf110000000000000000000000000000000011f5e4fff35ad1d6e2d2d4e30ddeac28432eaf13fc7c35f5a90f7f8a17de0f61bee21529b3db3633c178006f5c5fc403ed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db000000000000000000000000000000001986f950d86f35d45dfeba6c3e484a6da296ccda2314d03adc37bdaaab374aa9011e07e6c8fe056e66b9204c5e16fc990000000000000000000000000000000003220ebcac8189b30f6efe6051a2be1001b85a7f94d9ce289bf6e04edfdf2ff17b17702a1ce116445d763ed1c0dee645e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd50640000000000000000000000000000000001000e0934c04c36c621d9b308565cc75ff58f6c1c778b8e0926b4d22d58025edf8a853139667ab3d3616c33d8a98afd0000000000000000000000000000000008776b843fa3b1449a0879616b3a37bd5eff5c809c077fb0274fccd67d645439a79a410fe2c2db44f52887ea7f20c6062f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000e7b037fccbb3fed299960355ff2c6a51562814ac797ed6b4b770ec565bae5ac998eeba19819cf2b3d4e91591e7f051f000000000000000000000000000000000143dd07288b59a279de228ea59aecfba3275a87fd8307252e6b5d567bde87088a8a8f52da57cba4c0fa0e2aed423241e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d730537000000000000000000000000000000000b41b673bab477cdb21ae5f1c04922f2b8216d7a1423a6f6b86d4c33f0b4def9c553faca2798cba20a31ee7d71422b21000000000000000000000000000000000b64686b90964104f8e79bf9527f452d25c3c8e9d53e715d884e795d26e391dbf510d72fb2850fe66e35d31444814e650cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5,000000000000000000000000000000001673148063c4f68c59d371410a8ef20cc4516c579523e2d7d2ba04b03fc5a481a30fdc6b9ebaee1138b28a9463011227000000000000000000000000000000000067e9b23610ac41d41e0bfcabc455342360d4ae4c75fa0303631e97f702b9cb660b84b5d422e8e9a74890b803c944ea +0000000000000000000000000000000014cf7c57711c1708096cd33a9efd4f907112a3d4e5bad1767ddc6fb408cb7ac3f866143000154d1270c07b4294480543000000000000000000000000000000000a20191e6786d94721067d6942731110df277047541383ef9847fed9e4b8599723fd7cd7e2ca2186d56986feb8dd24d72063b046a71c2674e35466657a85d8e02253b42517b033619e31a53665917212000000000000000000000000000000000cdb0c20ac2c22a458d2370662d665005cdd8c662e318bb8652a2123f2d65d21c8e150daf51d7874c69bc039bb6163710000000000000000000000000000000008480687d726eefe93d5484ca375557e109fc64f60666e1b8aaf440100aa15e76aab6f821fde170046d2714d8986a1fe92fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000011f20086905f64c21bec021e5726c05158f892658cd69536945a3337a8075994caf4fa16fe66b85e3e0ec71ae5b4c09c0000000000000000000000000000000006d71057aeaf26fc685bfc0ca071126a81224692b3eb90e37a1941782b8f65d45b6a31567c6e3d2935d38e9e02ba08654484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df80000000000000000000000000000000005a186d0ceb2535037b22a6455c49b6227e54c6e6dcdd98f46d996f23301b208a87c4bcd0608972961b67c523f01c99100000000000000000000000000000000142367fb02fc6b2cf52a78e4cb1157d273e9fe13ca721e0fa725f2a6dd0b4897ffe7affa25925da47fe851362700c31bfae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000a758981a1524501c48ffc9990b738d51ebe38a0ba07b2b049110c7aa439253bfb0491a66cc42eb241a47d5e963db75500000000000000000000000000000000082adfa66bb46b97f14dec70b970469478d73d30216201e7467a927ae4ab9d93747b07ea69c406dfef789226afc4240a3168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a2000000000000000000000000000000000de8dddf04e0c2d9ef1887ea598030f2bf3bc7bd98b8b218d19f661ec4c9a47cb087639f72fbe97afe9617acb162bd1a00000000000000000000000000000000127e78f1f41df717e5f76692b9ecf21ec0fbaf9b1d56e51b37cea02143f3b91eb1f16a65046527339dc65d29435a2874f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda0000000000000000000000000000000009c37c64ffe9bbf264c475076ccbe6638653574ea84b30f4eb2601f1990f73fb5708af6007f21e4dd52f23ef5041cb3600000000000000000000000000000000170177e891c421ac91eac0dfff8bb397d7fc531e0fbd275c17cb4d894d18278a40a6c3093b92fc537244798f24eea4e92d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d000000000000000000000000000000000178d45abe2415895e0a550005c76522962c0ef0193cc7475a52f4d9cec9d4789406b7afa2872485722ec034df4446d90000000000000000000000000000000005e4253dde4284944b2083e07b04940cc72cb24d9866c953564bc0e847b72da59888e7a08cde7aa7c0753cda94a6e97c53bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000e48f11dee27507acd407ce1b810cfa8d0ff4414380fe26aba6c608784ef756d605c8c3ba92592ce342baef8aa927bd90000000000000000000000000000000000e604525ab4ed10f3a9a688774c6b27e679fe456190e67689959da296b650dbfb75610dcf54b30ab891c40784a9b90ff944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e43100000000000000000000000000000000199b1367bc3aec710e82f98d3564debe9e01ef2beb878935df4ea98e3725391e873d2661e2a27d778bd29ce6f66a9b24000000000000000000000000000000000e77a3ca6bc4584cc1c3df35b18402b75936f68f0f70193708da21649b6def59f1baec4d6d1a2733c369cb5d9a6b39347de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e10000000000000000000000000000000016ee4a1a3f99134ef55398e96b86a21708388c3ddbd86746745e24bafb062a6283c5bdd771f15eb501df6a19920162d4000000000000000000000000000000001001936f457d8241a4929aec1d3769bd1955433b340481936f9443c63a6c6ddb3be4f4e1ffbf62a5c4b154fa9f8acba3b0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000019aad23ac037d496eeedaeac9248842b0dec15478f62ab61d000a402cbdcc240186248ed931fe3eaae5a1d7153d3e135000000000000000000000000000000000fc1c74c4d8488edd92b42ca7c27e22a4776761829b06efb0d1b2cfa37738efb276cc5121d926665b99497841afcbd394aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f88990000000000000000000000000000000014c557c44a90fa9d958d2e701cb2aac1c0204246fae4ba7b060e74e5d4ff50630fdca918c47323f5d0eff118c7595a040000000000000000000000000000000015821312dfed1e0bc2cfb23536baceb7ceb45c6c5a5f15ce0d4d67ef261a30ab8154b873513e2c44f652b93989cb6f1badde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca00000000000000000000000000000000001ce81da6511eae9d2e155efb4f999a5d75faa99eed8fe784c7a398bf4b0e135bd0e8be8d9dfa2aa8ce9c63e091cb44b000000000000000000000000000000000695ff4e598b9e469bc62dffa214418536a6f49fa5f05680e09783b2f29bbfec5d43d42c969ad3b62c25c6192e328419b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000002f155e83bcd838ee8840996a3d8b0bef77334b0e8e75c8e4278411ae1012bae06959e8394dc4d1fd4ed5f07804b41870000000000000000000000000000000004daf1423e319b18dc57753d39777bb127b651f5294fe03a15dc4974eef8cffe337704c7f867fcb4c2fbac382e444a2b0cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e00000000000000000000000000000000000162f530647fc6290626d74753efe315e64dca2d73571dbd4416dbb41b07e8ddba40b3dbe170922c64fabbd937c961b1400000000000000000000000000000000021ac62abe15b0f1318063428d89f22d2090050b913973de571871125a391affb1cd595f9c596c9dbeb6025fc8392e48e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7,00000000000000000000000000000000127c2a1365d966520de7aef12e66e55d807b1a3e2305ccd5a78547fad6e6112d60a206412bf56866ca1b0af1b422c7230000000000000000000000000000000003a613a2e2abca1f42d3ed594a3b99a6cc51207b249aee2b5829aafb1327cc7bbf99604473b4048c4b2033e1befbf14a +000000000000000000000000000000000fd31933662cde0814cea424436ddeb6a668e20b69259424a652bab722aac70b3582cb641d53bff963ead87ef5dfe1090000000000000000000000000000000007d17925b0309fd8c92e52c1ad67937efffa7ae3c783177a82f1133c8e3aee2b8fe71095b6b88b01576c5203d7dc8c3f75888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001782f625bc3b25168b1f5b388b7963b9d158c09abbc0bc4c0bf5332e1817fc083d3d55124532fee2665c536b6923fe3b00000000000000000000000000000000118650bcb2d32f4e83257cfebbe8209c2c9062ab0eb805ae2977f79ef48af6fd78e7512b331933edd087054273eab52c18ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c4950430000000000000000000000000000000014a69e56a173ed13a9e2568a8af49d74c74dd67609ca58744f96f9213197b45de6468d69ed084ed8b1b29104322ac517000000000000000000000000000000000739671cdbdf98251ed4bf13d23c675500cb66344731ea6aa66ffe401dd6daa8157676fc46b413378b8325ed4cfe804a24a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005c95d722f8e50603951c21421e8532eae710929e976d76f28c050fb2b093618489c5f892198ca822d3f287fea6eb83200000000000000000000000000000000077a07fe1348e4b6b2a46f444137eb86bf7c58e320afda3d75769a9839fefd9142cfcb75da1d1aa0e7ce84b880ff1b3fe4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a135000000000000000000000000000000000efd66388da0825c846b6437b13ce5014b94b20cd3a713bdbb41a80892820ea7b12b6f6720fc7aa6e6756d496ef5ffdc0000000000000000000000000000000000adeb6281219c324d14ab4dc29841d52f3f21b512ef0a784454a01358747684afe22b34d4ff1ed29ea013d47d9059c838573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000010367597f1deb2ca9338b59ddcd8d02440ce8cc34c71a6ff93205375077c00f3f1c22e00ebc9fb60de7475400976e1860000000000000000000000000000000017d148179e9671959bf03fa1c95ab608fe2fb8b9b1a650f524a070d7857dbb8b14a67a813ba1b22e4b71df52e46c42c002257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc163000000000000000000000000000000000797e0eff7ff579b0c5161c8ee06a2b99ab44e515045e83438952226046bbb4adf3c8d0538a0bcfe27a533444e2bfc9f000000000000000000000000000000000c556867cb0238505da3b55321df66611e6a018be4e181a1ec121dd55c509d501558af880a2bcc71fcc641edcffdb13076b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c8000000000000000000000000000000001357812e6d93272645cacde880754514ee42aea3690d9d5d67e3bb5ee4444b7a3473ea2af0fc563d246b4c3e8ab5525200000000000000000000000000000000176c413594ca45019a174848f765f69e138e70dde1e184515c6f3012df4c5fa39a28a7e202c6c563db7681b0c4f8b3a9c9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000c5f9145b11f6af0895eca18ba6338408ce40ae1b25f8c04b40c0410a6c69b0144541e2ca1d4303c4c55fc407ca11b1a0000000000000000000000000000000010f2a09fd8b6cffae5a06bf50597a9c0d496bf5529c8925c1141cdb25ffd3afc6b51cb5d21d97c99a8d27281c657bd842467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000000c16911df03f532313d162bae1eb57c947059fb5d776ce3bfa661bad92ebacb51154697593e2321bbf85d43ae7ea567000000000000000000000000000000000564ac0f20388ca3bd483033994bf76b1ba135e229487e0c8aa10dfdec1887c62651f4cc0c05622de6356edbfd9abfef2f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae0000000000000000000000000000000001f6de29a7cf8a89e3cb5befc926eeef59270b929edb68e9b0cd96feb5286e130f1f7c0e0d46cf2a411e499be21d47a00000000000000000000000000000000002b4c8ff1040a843a0e1d691adead4fe3d5306f89f83724a891abffec3c742a3416fe54c27c97bd131730ad364373ed0474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000005d07fdc2e2afd92d5f0f1ab6541313b5a58868d1707ff0cc9e4ccdea0c105cf9cf1f6e52d0dfd22c70aee1f7835ee90000000000000000000000000000000001229bfa1d5c5e4aa5ed0f6753dcb40952fc5446b0c5d0d90b22a7b2abc388cc18e8ef74bb2370b6ccf036f09040f62dceaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec0000000000000000000000000000000019f4a0cb264a617986898fbfb53d1bde9cd82c092ad86e608750ffa990d6926644c717f6a63279f8061b066f0c4e86fd00000000000000000000000000000000082f1b79a9ccf56b743e14caf0cf18b94f1978d164d9a95fbf87ce15c3a9b414b098fb09654c23ed2981249233e8baae5158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000011c516cfd059a1b8ff75df3b9b6b135c2a52371f1a0dad631e96d8673f1b26daff9e776e9dfb225e9881635a28dd34c5000000000000000000000000000000000bb0dfd476dab29ccc80781a92f5a998b8ba2464d76df001440240957eb1237d9d210be62c9187d7f17891e837d52635d66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000928c4d78abffa6517742e617ff8efcf59b48efe0b55eaca1d93a434b84c42f29683952dd08546dc1b88bb63a35b49c7000000000000000000000000000000000d63b1f625ca9d33aaf51f8251a088642211a474deac9931c3ff8ad45f80782f62f71f014505606cc4a96f91c79a25709acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df00000000000000000000000000000000131c7e90e794b09da6c4936747e6509f94a467f38ac7f4bfd0c5da88d1733d1b6871a9df498b265c65695ab3ca889f9e00000000000000000000000000000000190e566597ec19df03c473b8ff4ec0cf24168f47c89525b31b1f3592bc7f87540caa8f91e2eb2f415c05502f72673dbd0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8,0000000000000000000000000000000015610fcdfe0bc92be1f3ea82eb0b0512ed5f448e0000951fdcb0e637ecca602f715674aab1b7555c8f8bdf886d3aa62b000000000000000000000000000000000103c3ceee1b45d9d8912887c40ca16dcaabab3dabf56b8f77cb3186b267a74a5193048ce4165c419cf727d6e9925ac3 +000000000000000000000000000000000cda9f382fd65f5ab92cc560477f1e3b69d0efe355e40ad3bcaf258509b6ca5e179deed8348836b0e723d5f7ca4c43ea00000000000000000000000000000000037011fda0d188f8d17436d21b9bce522cc9f8e4f473965803b242694f403ecee29d2abccbf56ab0a1f2fe5831c14380b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000f591bf508a5076b26dd8ea3b0f7a92889131142b34cba3f35a9b861cc6deeca7378d5728c0af9503441144bfc82038b00000000000000000000000000000000156067cc00e82414150bc05ca2d0c0ce1c19e5276e00434754616c9021120bbf9d1c00df6a42b76c3ffeb6e32f8fc3eefee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b2350000000000000000000000000000000002a8128978ebfb99e20ac99ff5b3088e8eb95a7b6b354d46e775dd1662a27d5adc9513467690f377d4a13766276bf87d0000000000000000000000000000000012ba903800e9641de498d8e286c7ee48b48f7d36255823b88a24cfb67f8d2b7b6411ba3304819f588fff0d730cf130e428728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb96000000000000000000000000000000000b107e62453c7181b26a3accaa624a612b7498ccc50eaf0d47bbf350b3c8c54e940266cde786c608e42f59d793e45eb000000000000000000000000000000000194c2c3717a8284051a29586e540bd9e456c0169eab0412699865c12226521796a55d598f60280cdcf37b54a24c931040fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000013228e1a6346683320d8acad4a5cb1c23cfebdb9d9c451ab81335d27e8b82297b38e1fe2fd02651a8dce3838144cf650000000000000000000000000000000000c6d54add7bdaf9ff8158680f35be7f51dcd5c26a698750c7eab857140b6329157bb7aca8d7c68f107ed9f68b3a076aac14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d050000000000000000000000000000000006e1e7c15fd14ff3bab1e9b8f8b7d6244c707744708db629ef4146b8cefe68c505ea034c180fcff95a452f7e1e5433e1000000000000000000000000000000000735faa57e1c4349be51395bac55a331a04851b41d2ec98072c5ac38eb7bb03e00ed64bcf32c3eac8b34cc6e26769c3ad81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c60000000000000000000000000000000018008132dcbd9455c3932155a0b0c58066bec4803eafb0a2cc30a93b0a335738b52e6cff60b379fb04b5aad342baf11800000000000000000000000000000000149ea542cf34141fface44046aee2f6c436218374d095bdd46638ebc804bb0c9a7e1e3b01c0470bb6efc7749b8f70eb73a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c0000000000000000000000000000000003f12b0eb97856f3ead3d46a8321481351471e558add0ac4e1f285e7ee8a1f2ca88ffedbc8ed21df31d599e80b8f0e94000000000000000000000000000000001315ca27c955f3826da43745809fb1759f0f5d5674e4d94118bf2f2ea0411c7d9cbc65f054c41ffbdf196ef24eb9afc55af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000cca2b061959fb70d383f7e247c131f51920e048dc136036cc301f1ae6ce13809551d0a8074cc05409d124e2df6536d0000000000000000000000000000000000a9692e0263b563cda35f8497d182fc05e78e7bf88267aaebea1f5f41bd1cadb39c61431bfcaef208adcc9118d4dfb546541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c600000000000000000000000000000000005bfb5a43e3643846f92310e9d5439deeb4fdd6b5dfd3de2ab3a40b9b8b3461136b03c5601add616dd87b9a72e81856a000000000000000000000000000000000212c6c42e24a3f11c30b7751f37c0101b8a071a3d56f2d10b6c9f4f84ae12079d8c4f2d216cdc7ee93abf8b9d6973394b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000008adf951da1f0b64c17f84031985bd1f3561ab44c80c339c4c753a7c2080e0f57c41b79b6cccb75662e8642ae0a94451000000000000000000000000000000000d9082079fa53008a03f58b87fe0aceb121c6c004493f3da7ab37f3236942c8ac01fc28db26b87bd2546f93b12577ee57e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000000f352ce042cbbf1adcc42030ba8e0dfc76b4ca313e82a5c5105ec56266977dc83626c9a9b3b5c25ef459a6feb2722140000000000000000000000000000000009443440da963a7e64d90e4642861f3f5399835fc2fdefa7e87708c033848170eb02407a6a9edadad27cb02793055140fde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000001699cd7355b0a0be2946f8f49648bb04a90c6bc8ee7fa258a357455864022db999793771a2e66adf3cea5a54ada82d6e000000000000000000000000000000000a3ebfef4ba72cbccab5e93155429a14fd61c106ed6d2c0db0694c4733b6f1730cc9f34a5e9598c60e189b8e4943efb56f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000be5ac701c69b81cd75fddb8da92066cfc9d0d2aa7f01495afd87e44076f9f022179b7d4b4781d0b5c6c52b498b63dd80000000000000000000000000000000006f2fd1ca9a34fb09d922a76943b43505f2aad16489a138668f08b9f388c67e46a4d5df7387a1c3aa23c76954913abfae2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000019489b41d8b1f2e8ac09cf3f0930e092afd74405e213454c458cfe44e5f393a88713b62715097a1aaf01a188e8ab07c00000000000000000000000000000000018471d616eb66f1dcfaf84b7d49f632e0a5306888e44c70710bb61d4afd440e5f692eefad842b5d37762cab649fbef34762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001088372334c452709f81b57f5e5c148e0f88dc29dc9a118abd6911c46ee83d0c6b58ec9b854c15f519d33d281ac9e21d000000000000000000000000000000000394a7e49f32e4f7d27f276892002ad034dccc8263591b5d941eb2a5e60097e757ea67dcdc5242b755fce30c3b3b64cdffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843,0000000000000000000000000000000009e68140307d9309c32763e4de857372a61733060ac582922e3822e46c28f67bea87a75bd98248466e8938abdc4ef54b00000000000000000000000000000000187dccf66e6d0593ac8caf6723033db545c43cb90a5f5486418325860b692ffdf8dcf23da9103dc65336d3cec2577a4d +00000000000000000000000000000000083dad213737f1789595316285a77c859c469b9bd0cf08c61884456e4fc5ef0947847186bd420af252d822419b1de3ef000000000000000000000000000000000795a6ced1d34d91bf5ddbe77fee452699a1b32daac270b4e8661259dcacbb9c8c3776043f2e149773427fe109818c87be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000005db83053f9824116b9d14ce0173c2243a4a8506e161db7f97408dd6fa77b65d0e0a32e95062699f7aa85cc9be448dcb000000000000000000000000000000000f20953295dde557a078c981f0b988cd9da8c7469fb7fa3361f2386c7dd609bf80ccf91cefd797eb3a4f849b2cec4370bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c726000000000000000000000000000000000000bc3e40ec1b6e863f75e4adbcb8b504026d0634d1d3769f7795ed2956bd450e68aebb1a9d11a71fbe5b51bc79d97aa000000000000000000000000000000001703e1fde7f2c740ca3224c1994282e633292f86095be38dde3673b78729db84bca33ee820532aa92bfd32728d9756404f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d72840000000000000000000000000000000009622c13e8924441b0043770faaead6db793ab818532c7323d9ee9a8d118cfd2a578e1c13723c8bbdd049b1d8aaad9ed0000000000000000000000000000000009da68565c05aa28648c0d0a0e185335b4e58903982fd361fb57f544c1f253a55e8a233b341537d78c4f229ec5f935a85d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd0000000000000000000000000000000005818b813993d7c346cd70190e1e6410974e64e08fb0a70721a0ee430dcb0d92d302943836343e274b26c69030226c0d000000000000000000000000000000000ee84b6b251c9d4f7e7abf843c73f0456968e23e79c54d8742cd5967737b9cf9ae8c6030722134c376c7c9433b749563feeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000006870d696789986991a222b988c3623ffb51ce96ee35140e817887ea37068ec77d8131a97579f2ea29a5b45ab55ec5d90000000000000000000000000000000016b203c189343e67e10928c2a45259593cedb1a016491e94435a0823522010469729bd69af9c3bd6f4e71e96c7d8ca72edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a600000000000000000000000000000000191a7f7469739ef4da1fcfed877b875c4b0af45df7aa9055b7d5f0c1360e4c4b7b67958d03125fade281c663923670040000000000000000000000000000000014d5256c242839e0951390f00affb226ee6c906214d8d7dca7e4fba7eaa8b1944fe4f1f93bf6ebb21b4a8585e000a76b7a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000001968eb742dc0e128c94c1f0dab2ff3b0d300966537293ea16856e5f3ce5e12164d9c52fa59e08481bce84f3f87dae8f100000000000000000000000000000000098ec0e7bc53314fc8729f4688b99c3d87e7e2770877a30898c37c68a5e0a4459851b8fa390cab18e7cf0d325d906ce4f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000064ef5e6fe9de3e86ccc7a8b809cbdd945eef98e8e6cfa82dc64ba94070cc107090427c13ddd3bf25d542696d5de44500000000000000000000000000000000116b4babfc4b1a7a36405f597d4afb478c024805495e1a412a3ad5e9ec5f01dc47411ee6e81a9477677b89291e91c2b68031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000014d07ad766b50a6150a50decabc56f04559d1b196b713be88b5543a673ee3f4499e42b58c532e38dca0101f639aaa9fe0000000000000000000000000000000001678e7e66f44cff05163ce249df65063c4ea2d2517a31f42dfe76f67041d7927ad4b0efa4b30c33156b14f5127af190cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000745f042a917dca8e35c8f0301612ce198f75144e145a3c3041f4ecf893360eb0b7fdfaeefe78733bb88010d6a7b9bb3000000000000000000000000000000000e8879142826593a2f1214eee206ba69b7962e9a10ba014af5daccc1e4a2d3c893fa47eb533cd0c0a9fc1c09d389db19d983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000009da4fdf5b86facd674ffe6d91d03674ebfa3aeff5ca2a659777be20109946b1bbd759d4dc2d9e859d587ce50ec3bf01000000000000000000000000000000000924985f655b00fec0bdacfc6914eedab676a962e21ffedd83be646dc17f5cdcdd3f43a9ad7ff9d976e4828b4dd219b7a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a700000000000000000000000000000000187414507425106691a2dac49fea1eaa14783b2a5b79a945fee44957619793be1a68aa110867ea405a076d30568ecf3800000000000000000000000000000000034e932247b81bda0a54568f2887824028d69767b9131c106a4d204c0b2bfb929b9ed7b3fce1e354e405aeca8a28d92e7ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b800000000000000000000000000000000027361b6341bf8985d79b6dde029a9ee54ef441894f34d60a3324edb502bdc78ef60789e5ce342c240db0fa91bbbfd00000000000000000000000000000000000bea3c850bc9d0860241fc6de65c203d5a11e6425faa503c37641522fba6fcd31643209329e6ad75a3dc5e4a4790db4a297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000000fc7095889f943697577c8867b411ac925ea7182e47a7cd19387dcdd48fad5e558de3d80e3036992ba5fb8dd7925774700000000000000000000000000000000160f1fbb346c48a6cab0105d343c55b3714899e931e7b4e0abe68c4fc7067189181afb9c040d41e4c1f7c4e2f1b8a63b1b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b64000000000000000000000000000000000bf1740d01ece251c0f0ee4f798872eda7f5a4ad3152d86db12844ffa88ca52835799f0b2601ed1bae6d4850cc889940000000000000000000000000000000000557f274109f745af6cd965d6e706b9ea1fa3c295cbbdb203ebf049c1070595ab820efad6652b1f1ba4e2d331b5bc6da5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6,000000000000000000000000000000000c57ca082c662618951201a98d88170a9aa076fd1fc48c8ababdcbb09b63c793725261acd370d03d28ea47c0ef176f1500000000000000000000000000000000110234e4c6a99c6d1ef8b43fa897990d605af9b469045dcd0ead52b03f9f90dc441f7fe5e624349788746802e4f74015 +000000000000000000000000000000000ca64fb3ced1d15f94e9b234e6f6fe59d805eb0b50ae29c9b31514ea5c6e79542688e871de6ace893868fa0eafdf46890000000000000000000000000000000019c60ebb5ca4e605e3b0eabdec53f566c9b96a143631be93250260560e47a2ff6b073e432cb1f9104ff913616e7d81c834aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a60000000000000000000000000000000010c867a070e161939458694cd4015b76bc4c76eea884d9dd309d6642436a82bc76ab57b2c0e2d3ca61f34645db65f2460000000000000000000000000000000014d9df8b34369bb23fbeac29aa8c35b346992d847fc2b9e3b96345f4a2245fa8eed505daf17edb4090726052be75662308ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de000000000000000000000000000000000017ffdfa10cc8e1a8b3751312e5bcd09772462618b8bbdca59a60701a96dab651fee0dc755969e1c3a1d2aa4c11e48d6d0000000000000000000000000000000005c2aadea5a4b11077a2a1641eef2d3bc40c2d8001e9853e44bcead87cd968ce41ca50644ea0fe1d0ec4c2d7eda9dcd058c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb4150000000000000000000000000000000012bb9b8a1537c2856d4b2bbcc6fdec6d69eb6196d795bb0f1f49d8a886076e7fb424f63400134622941b2b88ea61b8e30000000000000000000000000000000017206fbf293f1ca1f2a0971b920e702ea39996058111ac2c041c12f58f67037a3840955e1185b413859a6f845b333b58ae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c0000000000000000000000000000000005eadaee4c48dca28f9469e882ca8ccb71f82bf1f2cb5b7f50b2e63a05e78415b3c5d0767a27f19a0b1c88400116e5310000000000000000000000000000000017e95e480a145b5e897c7a1ecc1b21c5a000248f87e74bfecc21a3cf8a06c04fd075612a62145ac089f208e567e4e12072022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c0128710000000000000000000000000000000007b834503ed3e1cb74738db29c91f415beeb3ac5b75bb2cbf11f4a9cd1608ea6080dd1bd50c195dbf5ab6808fe9d6594000000000000000000000000000000000eb32afb90ecf9923ec22a483ffeca3a15d358013e64e521aa42d3db1ed0397e07a85321492e0693f8f041f4f8346c6c800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc0000000000000000000000000000000018f208e26fd7c03313df686e27bb6ea09d9a998764e805fe6182ee221cb9ff1552e4db5feb91b3b2fa595bc32f81898e00000000000000000000000000000000137c06c3f9eb27f1c0546b3c7ce879218a309dc37c0590fc3e151d9f7fd5963f0fda201faab489dce0043c3180abf753a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c0130000000000000000000000000000000016fa49bb488a35ecbfa9e714235790cf6e7c3ea46e6a9a424f59c63d018206740e9467b0575077e86091ad6e0f9f56b6000000000000000000000000000000000197185b7c82ab9e6dc8e2a71c94dde328c923eedc6e305d8f36f4b636e7662e501917b89b33877cb2094b523c969dfeebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec7304050000000000000000000000000000000006b797e2bb8c0c2a5a6ef8d9f08241d42299efc8af049245c254a2e4bfd122a01954bc596750942bf7ee467b22bcc528000000000000000000000000000000000a655491c6381e81473c23565082544d9f223042c82e241b1cb8ba48e847d98a373fc68b762a600489cbbca612defc61fa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a7000000000000000000000000000000000d7b7ba451334d1391a51142c4b7cecf0032fa6d28fa7f36d2d43ba39c6418946244da3cedeb2bdfadd453eb4d54d05b00000000000000000000000000000000127655a7acb4e3271a188cfd287cc1af890756e340eb4648bf3ea3e469644e6d21f63e64f81ccb55b9b1e0a62ddf58b5f0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf211000000000000000000000000000000000129442dedea08bee8661b558bdf8c22dd391900a501f1841c77359b20c1a1ff8838829baafd2a6ab5eff31e3f9ee884c000000000000000000000000000000000ed7c27bfcfbf9b41c833fc0d8573d7b28a6d788ea3cff4d96900559cc63969ac1d5fd366fa705357626eacf402c2ec560d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f90000000000000000000000000000000013a11df012f8a55c263c5c55df0fb682e685a5feef160d77d26db7125ed08e6605f3d67878ec78fd064487f30228f4cf0000000000000000000000000000000019292997c874c72ce7c432f20da1a338e9dc433f9257b7353f99b5b531a9997bc3a3405b0aba89ab5a2f1cda98dd8199006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d600000000000000000000000000000000006a56b816898a1fc9954495b711c493ace881e3989207b2f862dc41c5fe346fc2eee18adfbb9db67e774055561af00600000000000000000000000000000000013971cff1e9a6ce35a7ae40118a007518bbdc5df5939a90fb263a9c345a70f4eef2f94ec671ac6964390d0478cfbf728f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be40000000000000000000000000000000004962ef115a4288177df2f0e4665e5d1976fd027f7f87a24ccdd0584e265e2f5cf0a7490dc7824f5eb26c9569bde9d6e000000000000000000000000000000001544f43d961320d59c65563d5f04341a8ec3e6e64fc2dba7e953652232d615c90eef2c859525fed99ae6ede2c39f510a861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000004b250ff8bea739fd73b3c3463617eaaf3b6bb9db11c2b915f7435996bb4cff3561fc268d2cf0db1705711de522382200000000000000000000000000000000001c428a889955fbb5fcba993f2defa5906ac7b6a3fee6c07f52de8d54b0665cbea84e89a0af3523213fd19f7d37944012dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be5500000000000000000000000000000000106fab277085c88a7d664587f67aac8de95aae908177dc513fa24c8115fa23db44eafa7075b036242306002ee6918da80000000000000000000000000000000009e832e0d01bb5e89460e2cab772c308da07414ff8b880288c7b55d6390360924b806c71c9f9762d84d8d3cb3c2f6a6199893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23,0000000000000000000000000000000010224cb0e43534025f8ba7a7c426355a2091473ab16a752a9819d8e5f3eb5b1b5c0891b1c0cc0017655dd8aa7102cea80000000000000000000000000000000004313278c1bbc33ae2c3010c50be0120bb3ec794d9ff77fe97154438606e5f6f04c1dbf8dc01b827643a31899342e1ed +000000000000000000000000000000001812b7bdac748d2c0f05f10edaccd351e35347a4a582671762c0567f55e411839ec0a776c18cd71cc6de0c3a3b8bba820000000000000000000000000000000011afad9a48c42d8c3bf74dde15d7b744c6c141ea57e133c9dde7fd762636115e0296a647fc3fbca8144048721902973fd8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000f4900ffdc92661bb33e7561d08ce7757ae71a2b5ebdf6427922454044c6c6695e249069e83f3053e8a8a0adb5d3d3d2000000000000000000000000000000000be84ebce32bce4d58557422c7a8c4020d1bc643a99b00231a4d4a06d5dcb56bba61ead26fbf07079e9457dd4364ab6d33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f0000000000000000000000000000000017399488c58e24c6e1f5e9a04291930595389536480ee6dc493cafa7f0e85410bccbe5c5841a1a0e495830be7e32c0da000000000000000000000000000000001055ca833e53172ac1d2d3d7c6fd625dcc72556e8d49bb487a83e56deabee4fb672b6cf7787d1231c760c2b7e9d4e55e7894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde48000000000000000000000000000000000c57fc0c785d6b81d4831ba71bf27f9af318a730a9502917a68397678c7ba22f21335ca2fff5bd495676faa418fe21a9000000000000000000000000000000001012cef9cbc88b838492b6a0074e0e5d24635d36d669288acebfe446157a202443fbaa5241b288fe418e1fa50eb3e65cfb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000000a2a4bd7c7a79c2336b05bd5e0558736697c435477d4d0dc790033366ffcdecac3bb9cf48d1341835f7a42e17af833c9000000000000000000000000000000000ba384bfc6aaa8402ff869d78973c68ccc36c20a839da8d570b6890614f692f3a3316f0eb45e4afee0cca078cded752e83c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000143e594b8762b4f821a6cd294251a114e248974494bd16a66f27192d3c2dc56c19d886b6305d420f8b81b22a2ce4faf10000000000000000000000000000000012fff0d7edf98633e1b10ba09b3c70fa0ea8674120160933689115275da6f95a8cae1ec665f89ef3c5454dd91d291ba4d2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b100000000000000000000000000000000159734584d9cceceb9a27808a5bbc1be9acc15c6d2edad81759312898be4efaf85420cbd004102f7b051c83b27bc3fba000000000000000000000000000000000eaaf5b8e35ea5d52bbba19087520a96348b418159e043d3b39c451fb77d5b98aeaa43cacacadf3e6ebb503f49c5ad4c120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f000000000000000000000000000000000c62de2a514ba6a74f66312553218cfcf49828b6f01ed05561b54d5f2a87806694ada45b80429e60fb985d9cc39e9c4600000000000000000000000000000000146b134c46ef783488e0f2d6d9b7039971e8ab7f3c29fbb2635bed84b44013159f483df0e7f0afd038b64f9e5cd105726af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a00000000000000000000000000000000189f02eda06f2d39974098d874325e4711a3f4dddf78c1b9ffb025425c8abe6dbcf5a01de0ebc802816fd67b0a9882fb000000000000000000000000000000000b378df4be4566190679691561aabd7182e68dba4ba05cc67ae19cef483fae99f4cc54540b5a5180c3854f5a82b6fdd0f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b0000000000000000000000000000000011c0ed482c1a1f030fff7395db725633a60875028e2a7763a1ac801f00a8f4aff5e19e556516df899cf5e798197f6880000000000000000000000000000000000fa7faf03f2f636ab340a9d27d9b5a66fb8daa9c083a32904a4407d408cd3a14c17734d7a14abe3655979230e1a93e4d23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f500000000000000000000000000000000101f93857688bc4e4da2c5407d8bc68b9304d27c89a44daf7cebeef81ab96d89c83ac34ccd0dcd87297929551810e47f000000000000000000000000000000000457eef8e4d47638f83aa2165c0f2581e6a0886595f03fc41319d6ba71da0193a4cf9f52c39c79327a69037b11a382f696c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f00000000000000000000000000000000110957948a78ad9c04b7abea4d1caff1de20b5615909c2f5b8ab7a1dbd02b9cf2ebfaaf3b21908aeeae55e47b9a21b7500000000000000000000000000000000168f08d45ec66fd4c9a94d82d9533aeaa251186478851a421f097d00506fe6dc0392114115e3e66d8874e0aa4b15cca281c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c0000000000000000000000000000000015fe358a596150d9eabe6f18e06d562f9e6c42e9df7ad9ef57be8c47c5764e408efbedf136059d0e04f81d4838713a83000000000000000000000000000000000ff7a343274892ba23daff40f5f8c56db9a4788483c16a4a0495a1f696d3304c6276ab5a6d7b3cbdce14e9711b033582e44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d30000000000000000000000000000000010d66f27b2da2ffe49b7540da57c25f0d36de0c43d04da9b123c153ba3eb63f3d26d28d4cc4cfef2c0652010be2f9eb10000000000000000000000000000000004d4cf53935c01bca14c75d1be55e7473d17de6c5a2d69813df90c7612aa4815ca6ea982222793ce66bd1c69f6e456feae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d980000000000000000000000000000000008ece81bc19694eb40ac3ed089d8fb0cbed88371c7e314ece92547151165a017b0a5db4eac06bb2679a8d82b296f522b0000000000000000000000000000000017732041d736996351f132c92fa7249483612bcd79532156694314834c04d3b99579d44628c52eda270ec7c3ca7c3e576a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e00000000000000000000000000000000026b3730efe162d58adc8d4845706f9bfe8ff54116b518d6c3b2bc6418997a44e98071e83566a905973a2d512878cf1d000000000000000000000000000000001449b0e28d1c43ced7cd687a550ff7669df47e80d3f2ee621b791848f1f7d6cf6272e39c66e8a69c81aeb67b06c630b2ed3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5,000000000000000000000000000000000154edd700b8cda3a0532b2d6e81cded6a9662547b7d638f229ac44975d3b4b2f19816eb664e6208f33bf6f8d070fa58000000000000000000000000000000000a41ce7605d7ec592ec3a030307391ac64be5df9a7f8ff9b7ba15f7f6931353fb824ae8aa76b31466b7e06cb89fbc1e6 +000000000000000000000000000000001227a5d20faf2f8d9242e1a7bea89b5d7c41c3e0d8f2629b4004269f9babd2521a97cc23075e13a53f4c66a82970ee76000000000000000000000000000000001726ad8abed312a369001f53270b5e7ad8f3f2a031804ac055ed4ddb2f40eadf9142416efbc90e84f499e07a307994db8c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd1000000000000000000000000000000000a07b5276098f9b3767908192f91473c554eaed23b810d3b464a3677089c45e2263600cc8d84766c7c67d9b5e6a057cb00000000000000000000000000000000175af857d5b53d195a17ae246208b55f35f4ff193545ea5a725a70f11fdd34ad2fe22431cec7835d4fe3c401c82a93fd8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000039e109e0c2ccb5e6cb4c5451125047bbb854488ddd74fc4360430fd80f16db3498a8be9514099d3ad50ed4376bb5e50000000000000000000000000000000003dec8af7f6805ff9df65c39262959c3c80f271d2f0e53e7e719fbb16080d7d90a1211a6b4d0513c771ddad7d3dc009063016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc10000000000000000000000000000000015a00f549c3a050a5ffa8427bd0c8b90a788c6f9150728b037232ce1148c02bce908f60ee367b70d0c9642114d6e657d0000000000000000000000000000000016831ffba7d7d0bc239563e9e62990af4f740e57ca56d0d8826a9738338e9a1d2e8dc2b8869d62090b06f5a3f68bbcd36c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000165e447cc890b383b46f251531cb6d29cee835fe2a0fbe14c65f0998b2911ba86337ba79decd2701a4db1916e01ff4bb00000000000000000000000000000000007bfb52f3d4a281238eb65565af329b3e043e412588ae00342144d168d903cdc9131775ddcb5217ff692b0f922504ddaaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d71000000000000000000000000000000000ef8ba702c88495b63ac012fd9ce54b4a7ed67b5f7d25bcbedf951455fcfa95a8c7775c5ccc875ca5bafb9bfa1af738e000000000000000000000000000000000e53e18a3e7d294b508ec4084cf57557dd1a96ece8eac9873d35e4f1ee812a1380bf56569e5e797ef54202b1ea69291df6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd4900000000000000000000000000000000021ef16de941ce6394ebd484f6b9de12787aef9e7921292106e6c1b18b8de5c640e448f53abd536953b07dc41db21ec0000000000000000000000000000000000a5d482a1c20571e03501b89d2bb4c6d3251bf0b015f23ecfec87dd7cfde705f946c311483ffc84381609c394c83513a0cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c6470000000000000000000000000000000019c277726fc9c53de1ef3aa2ae6e15b360a98b4a2b27f9057f91eae5b2a308b2f5d618d8e458839d1d60105e4888e7920000000000000000000000000000000012ea8dedac124f05ff58ac72fc967e325e00e83aeedf956adee447720f491ba1bcee564f52e4f0e53faa106ed8088d4cea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000000483da14288400f7b27d712ad849fd7c068db47709f78b297c746ab3e15f17f20130b415c9a1b024bd5b24f74428f0e0000000000000000000000000000000006746bb7d3a38fd833187a16d5500d394303e2edf7d5341d787257a9f811411a5cd586b300b7b4398f9d266bcc27d9cecaf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea3000000000000000000000000000000001700795ca26c2cf7dbdb64034e45362295b7e9c60753d728bf689239b0ad7073b29fb872aff047605509ecd10cbd4fd2000000000000000000000000000000000266a09604de2ccb74c5d97dfe4e9a74cf89d3612de9b2d2d39dfa3362b500be127b83566a61df49e639d548a0ecfea7ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc00000000000000000000000000000000146b2b839ff63d376db418a51890c46b0e3df6848a5a39a26a02673e93ea8dec5079e89a333c85785eb0cd1d67b1e101000000000000000000000000000000000f57e8e4cdf2670dc35a12072923d334523e7ccaca66795e3a762bdda8efe5424f88ef7e4c48b0d6760234ddaad4d7370d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001989144efb1979a42399f93fa80bdf256316f6365bd82b89e0e2371de79ce9de2435a6cfe9704ed710bdfcbc8cc2bcb000000000000000000000000000000000084230cca1eb5defbf2f2ee29fb2c47b417919f220c25bdd2a017b514840466a45b2c00047e9628852d48a057d6335ad7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b8200000000000000000000000000000000128df806a651c43c7e0a3b2c5833bf158ea40953fb0efb02620cc4ecfc4c32a409a8bd9e98e82812b54d027b6346afc70000000000000000000000000000000005e28760f1e574aff9664e373622147c08538ed45cdad72a546e4b5840758f5ed442f8cf24cb0ba35902e64d084406f32a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab719000000000000000000000000000000000289520e710e7ce4a8a671cb00a015dcf40ee2a69309cb89b514f6fb2c6e8fc92a49905893e3e0e9567956fcc86dd89c000000000000000000000000000000000d1329a4174f802680dfe8410fb45e23f96eef4649579ca8e29b3040de33cd6bc485d1339afac9593097c70a0312f5162bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da96000000000000000000000000000000000175f1ed2dcd584f9c59c9c747ea1841792bfd9a64747f84dfe32e256ab5a48eb2dcaa337990089c86b3dd589d276e2ce0000000000000000000000000000000014d8bb6e278ae9bd9df2609690286be593eeb668f5e2adfe880e1d34276ec3bf4ab5514c7898a6504da63e0ecfa49d020f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed0000000000000000000000000000000018adcc61d9162790bd8c19be058afcce08104a952b15efc276af8a8807a4d2edcf8557aa03a297ca01d6a3869160148b0000000000000000000000000000000004338e5f7a12f2ffdc8158a51b14dd36934f01d7fbfe45e18276f2432b1b8210ba6bc5f246a52646bdbf99ed91f2f48f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e,000000000000000000000000000000000c1f9b78641053cdbdd6545691d1a5238389614524365bcddb07f9b9c6c654e58a40047084532b8473c7d541ebb187ee00000000000000000000000000000000028eb1aeec5e4672c41eccb45b356529e5331bb5fb0ca8f9e10b20a2ef1ea911f03af896ecf7575613bce5eb8a0b0837 +000000000000000000000000000000001242be79cbeb2176ecadb07d205d532bdaaaa26bf9103371f2c4d86ed1df72ba8b6d5c76b7aef25c743ec4f43e5237fa000000000000000000000000000000000d2de7792d0655ebcbdc123ed6093ba68948b8ea156a31b9f23d1abd948f4b2ef2f27a3cbf72b9e5b3e966576e9ffbd5f3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d8000000000000000000000000000000000fa50f78e45b1b7b61f8508bb5842bf59d0f41f2a8192cccec6e56125ff94b402dc47d3bc7762f3196a163fb148105820000000000000000000000000000000002933cca4d82c6f89ff8db5f9239ef8fee2efdfdfa22e0b4d0fbe223910b08060a77eb4328a05ddd31d205861db090ae4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000c15db9d1dcf646bb4c169490256050ad5e408d1f45221a9b4bf02f7651fe93ffb892c98d19d730bdf3971281c9e2e3e00000000000000000000000000000000150a6d1978ec63013ef3dd3b258ea3a716c1e564469d2aba343f3d15c30cf287b706b9eef8363351cccb79ecdf5aa189518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af07000000000000000000000000000000000f7e810001b9e3a11a535f6744a0dd357cffa585baabf065f1e72c9bab5484829a94159c72ff2221406c8b15de465f8c0000000000000000000000000000000009d48808fbf21370420cad4df7a269e1eeac98d2aa5ad5890ff362d91cca5ab1b57fb079caaba3a135c15515e98c6b175561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c38655100000000000000000000000000000000191dcaf13a62fd6de0bdd16151b3c27f54b40ad82da1299164da87d0cb7b4c769f941c39fb4b68a8915fa95a5ddc0e900000000000000000000000000000000008b0ad7fa07edefa61ad026d42df18273b6628b65a4e655a98b705f588494d06c37153ecdadff83d94739bc254d6d8f837c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e260000000000000000000000000000000005e3001f37e840a9edba48b3b436dce520203b0b36c3871933464be1c41178f7a8af9b14000b713ee8fc0faf5cc1a870000000000000000000000000000000001732dba0dbadbe7db31ea6af17520d791feced0a7bca298b932f51f3dbcb355699db533cfc8b61d35d1a346ea5de8032981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de400000000000000000000000000000000072e4d38aa0e168255f1d69ef129642b4b1b57289e630455b147574b03d17e3cf0f32326afb7c45da468e0d8c2276da9000000000000000000000000000000000b60685ad05be8453d5d272c73365d645dab6c50c820c1fb7fb50d82eebf9b03ad3c8f711140ddaafb2bb128b7be2e6c9913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d3000000000000000000000000000000001401e023aac71de3398f89893102efa8760cedf47938a655983d73ca8d394a239f37959e629cd908b4e4f5e55955b153000000000000000000000000000000001458e304efcf48594d7094d30a804742b08ec94ae479cf5d4e0575828ad92cfe8e11847d6078f5eeea4308a8f0644172188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe000000000000000000000000000000000d446202ebd7a7995a4e8aa7fcbaf6c4c4591c4bc40b374720752a150b452b461f59b775e3088733ca967854413a9f0a000000000000000000000000000000000d5fcb5510c0f7ee77c7584631149cd494a5fc496b325ba93ac5f801e34c815fe562be4758212f32ab0978930d142adf5525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a0000000000000000000000000000000000e034e4027e846a8608680995860b2673854d8fdf0e61e2663d7e0d904b6725ff28bb4593e7bf5e2c252d9c9710e39c0000000000000000000000000000000010bbf60b95669468e5dbdfe912dfeae9945f44454df62ec116b097b867b14c402349af692490269797a30639177151945ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc5700000000000000000000000000000000095e1315b3568e8a069dee00c3676d5d6ad94a2164795ca5f1418cff4a25052e741530c0df6d50c5cbcdd55a084227f3000000000000000000000000000000001993b036a3225289827691296b51ea4e42735af0506b317932b6719a381a59c89871a2a394f4a9de0aba3bb9a2b881f86ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb94280000000000000000000000000000000010aad99bc8570d83847a2a2688fa61d5d0ecc978ae842715a084d99392db343f581290478bc1bfeb8bb692e0d6fd58ec0000000000000000000000000000000004f82c0527d3e9329a6b460f1d781f881073b87711771699e9cc8c4229d5112d91d4357380c12c120313d2c9eb7bb427854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000008ec7244587110fd3fa0e1888427fbb3942d0885e002e4f846fb749bfc4a82bd7edd15cf81af454354006a2ea85234f6000000000000000000000000000000000fc7a19df5adfb5a154f32b9022e54b1560237f4319160c9c945b7bf4b55e45fc86616d3ec3cecc177c9f6bc54dd2cdb7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd00000000000000000000000000000000037ab89247516909dceeb59abb90d6968ddc3ef3abffac93c68757f3c9309d145cf9350e4d8f85db810cc5f156f8f126000000000000000000000000000000000289168c6dfdc25ea10e1839e10ddffbb25522be7ff80ef321241c6cc887fc7a42586dd9c1686c6c5c2e4caff0278155923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f5476000000000000000000000000000000000523020b4c34e867e75cdc668e541cfa25f2afc35573b2db083987fc585a487f1eafbac1c4267d2fdfdc5d2f94c51a84000000000000000000000000000000001581bf2744d78d680c9bb38a3f0fee76b6f0231f011b3f7ab3fd59c1ec6c99fac518857dafd410bce2e8610c6e5efbb1e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf9000000000000000000000000000000000615745e737980a923e87c3ef72330f55e38434b3974c1cc997a9d1136527de9bc21dfa73ea0d33d27324a53f12bf6f9000000000000000000000000000000001164b6ac376ef24ce3cba8e2ae74eb58437bbbedf68b4d0b6e8b7e213a789c8c3b7f173bbe52150faed93fa83bce0a9db6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa,0000000000000000000000000000000016e6cb1f899ee8f3db4b934c1facb3928b08fabdce74f569a06ae6eeab201925f6acb1a47ffef3c608fed32c949786a7000000000000000000000000000000001796fe817d2e4a69b4e8c539f6f2179a802cb02caaeedcdb316b3ec6571c13c349e115a7a05b296d6b182c8a618ed918 +000000000000000000000000000000000cd7cca90c8742e7f541981a13b177a4e639195af5f15cee2ce37b01d50fb8478a3f0d0abe4312a4d92a201b4dbb030e0000000000000000000000000000000018e2a69bd1cd9bb7ea75ceedf28ac9c9514e8d28223af69dc991e46a03d8d272d267842f30552583a1f08a07188058ce13ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff5660000000000000000000000000000000011cbbf1ee7e9cf8deae286ba67ab0eeddabd2471d2ea15e86c77c4f2f23ce38e17ac3f3e3c2a40a1640bb3485be4e59600000000000000000000000000000000108e9f887f86f03dcbd515501f69a5983b4a6d707c26b69cb9ea7a387c5a914612ef645cbe81bf29ba91d209e839c72abcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a0000000000000000000000000000000015c1856c661396f8e3a477932e1eea7e124b2e9ae0dfb1df67c4b3928c462cfbb3220c4c2fbd755fb6435e144a2b937e0000000000000000000000000000000011b114659fa71c3ac2412d5c2cc1e184f05a45871e5ab08fbe5eff68ef9e457c4f3e2bb4f16d10e91f7ee2231bc3266359f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000b693c93d4f06be5bc8a84157c6f407c3db14175c56310e7d041118ec869f3992f75809b209f6dd01085991deaae2a96000000000000000000000000000000000ee21d90cc3825b401e6d452e27814672d849386eccec7be992581b1fb9f4ff4f3892d63e124bd669603e6269f099452995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c00000000000000000000000000000000013b40cfe91492dc53089325be73b5d404288e8056e30cfe4bf3feb6b854eb7d0efa3ac4afa822162ac16608555ccc92000000000000000000000000000000000576146711dfa2ee08bf08121c30fe63ef0ca4448b28076eaba9298ab925c615a56d497044be803f73e9586763aad52497b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a50000000000000000000000000000000009d66d52069b0d23faa33818a8c9bfc812ae6938dd02604e98a422f50c085a5641a46272dc9c8801a9c76cdfc2020a0c0000000000000000000000000000000004dba0f971336c813933bc6386e55044f5e3d3e5cf38ede5811b4e775fb41cd09d7f136d9de6fc36f2f435b8cdfdc26198115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd17810000000000000000000000000000000005ae8fd5c52fff0b80a2c5c4fca4bccad28f580c94edb7e28ca2ce2390cc2fe476a2b11f63c3c8759847e647d5fe5d1f000000000000000000000000000000000edbff5012f6efde3a9bcad65c805b1c4ac0899fbba5fd760513c673ce8ad18d3baf28acb3344f511fd4d9785afea33c27370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf50000000000000000000000000000000009d003d4213a46812ea1565bd9a6f0f3da1e69e289f026e619911354cd7444dfbfff1d842e3d9c61c305b2154851b29500000000000000000000000000000000070a1387dd16f9d8b4306ecfe0e9ba7aaa5959ec917e06da4ddf90c992fc569a56c61f6372bd26e21f5cbe7d720b68c66bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f7100000000000000000000000000000000008fccb033a3e10a0015b11ffe2ed5f4c96ea2262d06ca4b0eabbc15c9b299a5220444345c65e7092501b56599980bd0d00000000000000000000000000000000127583566286e52f2f2c7809cea1170a49993f171c1c217b82c17983e02b7e69cb8c948725c7a613c41f96e80c3f1aa96b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d000000000000000000000000000000000084501b09915fa13908466d6bd50a7e0d8b39893bfcec9c6876b7ed8effd100b8f0a459d754efb6b110af2becd882cfd000000000000000000000000000000000373669b2a03d3da4e907da24c61f5e7928c5fcef4e6c9ad4303fc4cc2cb641212680f7c33605212de8914caa58732f44f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce110700000000000000000000000000000000010608a9f87f46e528d782ef81493625f9a47134832eecca6471d2113060703750b679e64179e7a1c1c81311c38c493400000000000000000000000000000000032a0c82e42be6203415638e6cca4dc1621f87f030a9d742bc77862f4f10ceb44f1ecd377acec6587be0fdc33d8c17c98b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa300000000000000000000000000000000126f62cc3033b7235be5778289fc568a1c474b70cba2d35a0b9fdab5cf239a2d4fb03f0bedfa84425b142c04284da058000000000000000000000000000000000dc1f91754d582f57b413fde9b837cbfe3430582b0964620b02bf854c6f666914157d44a165f16ca1d7204f35caa7b0630e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b20000000000000000000000000000000016d277ee7864b3af3102190cc99db1cff9fd1b1d6e7fc039040149c5944e7837895532ae41b4db50e29a5d6bad7ceb630000000000000000000000000000000016c3f6e29114782c84734cb927d1a89b7755c3a8fbc99076ce3ae17f7f1d088e5fb9757237773fd4e14c2855ec12b93723a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe000000000000000000000000000000000dc8f59e410ef7145d636d2c7d43fc4b1c903d6c8c0efc3ae162293c7c65c48182f9a25c4e5f111635881533cc558cf7000000000000000000000000000000000082dcb0872d815465131953c69e260e3a9ae44d16975f361b5effe13ab1d61c18f050108e73f50871221faf28fd79771ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000002536653a945e03329279f382937d72bddd71ff8f19053e1fb19ef83d9751eaf101676249ac65fc61a0cbacbfca3cfac000000000000000000000000000000000806ebe4d62e62904ead05f814dfa6e8a392b887bab4aee61552c6f93ea5ffec6593e9078a33f4cefc96393a667c934c212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000018fe7f7093e0313737b8e0c6ba2fb0c93afe1e8241bc769f14cebbfdb4c73aa578fe3d37ce1221f21aca8af9ab99201c000000000000000000000000000000000ea0f2ff4c8ed0a51fc8fedaa056a369c5e97e347c6883b215d0f7e019960c0178a7962415c220766c16f4596d4b9d8ce9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37,00000000000000000000000000000000184197d1ebcdaa25c041e95424bb891fc9eb38160cb0d91e702ac283280c6df697ae85674eccbd0fb130b6f2e1c193b00000000000000000000000000000000015593ed27279ca601616dfcdc493b8c7bd68260f97f8a9f10c03cf871b17cf8f492518d2f8569d60056721723a0172dc +0000000000000000000000000000000018c31653abd67897b3bf8f046712b12d35ada1799d1c18071182fb61273b7bc506779ff2d774576a725f2f1035431c82000000000000000000000000000000000011b2fab972f183c75df3bfb7968dcdfeed0755f71ec118e56c61203e97064355200c5f016b9ed66040fc886062dc58f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000030d7e368c99113318a6657deb3c89424b9acbc5e3568e03dbf629333ed3a5cb45ce6988a3e5ef79e5ee91aa6b990b1d0000000000000000000000000000000002700af33eedebc8a4847d6772cf615413149e6d98ba3b36c96e43c8d97619cb01117570f263bb2f7579c7da67f40a25c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f0000000000000000000000000000000008cbea0d07e870d679cd20b4ad088bf3c5c23e83266b20e816f69bb918824c9bb4d0b3216f8da5a5cdc6f43359e02d06000000000000000000000000000000000d1c9949921e37e73f95b0e4c444e390bb71fef0d893d1b341b9338321bff4a23d1da4ffdd5d7148fa9fe9cc52ebbfa8f2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a958530000000000000000000000000000000003111c080876670db10abfc439b17b32f9e96758b057d3344c7823af1b0320037906b1a9d8fc42cab9e9e0e8449aa997000000000000000000000000000000000e0c7d19a0362a173b70b6fee3d3feb541c7d2ccca71f1f01f8bd105a18024fab05e0a6d448153139f2777b189ba0fe41022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b0000000000000000000000000000000018bcaa4869a5c6ae46e6f5fd5fcf835965d21d48871010245e722bead79d844e96e10558d71e425377f4adacb3f74074000000000000000000000000000000000414d616a4207e7cf79352dbf7f319bf554f043710cbeb48aa502235db7d30f4983b5381269f34ad6ad4fd5ff56d9586b80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000c86dc8b8f38d1e4281269ca252adde9f0fe933d4cc051c7aad55f96252d1e6f9eb6f4f876e153c11b61714d985d318c0000000000000000000000000000000014113f8e2c3ac4919de334eb5c04c909b88df39998e58883a5393a4d760cb6d07c65eae053a7b2100ff3028a786782bff397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000009b4437230d9dae44852d88dba2655070162501702998ea5a035cd88eecb64ad7c9ccaf696545dff98d778cd7400943f000000000000000000000000000000000706b196155640680b257a537c836507d95e6d5cb7f163ca340dc0f8b80859721b7b2a2ba51dd4d72ccc4c3cb91030c928e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c50000000000000000000000000000000017bf848757da8e7ce5e5e69574a9b31d35eb628102897922d4c996443fbc970374ebd601b96b3ca9412c13f50943c7590000000000000000000000000000000014741c0b49e4f02630a6cc1a723cae1a6a9862158bdcf996b46a9614dd34527a859db0b5718788eaf2caa059671f3c683cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000001269325967fc68b78cee64d0386e1fa6ecaca1f85d672f8b63831a1adfcbdbb40461a77ee0e59b1fcccb7c1d543f08a100000000000000000000000000000000053a22e8c4219e4d68a961c2127201a23443d8fddb02e3756cfdf74e616dd4abe73c4ac498ff5f6a68d730c0050b79e18e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c500000000000000000000000000000000018248505148876ab5a5ec3be7e3a6cbac30798d52f437bea7e966921723e6a4a30a0e53518e109d1683f3a4b3432136e000000000000000000000000000000000120602fd461206973e62ec8a3f1cfedddc1e9f9e1769ac06e2a1024a9af19d402f40ffe30f9cf77b8704497d3cba4a3674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000009cf2460e5121b15d177b8ad803c045529933d1abf62205d04726b67d64fee85e2008b5098ceddc42d5c8d95d39147600000000000000000000000000000000012749abe2d8b47bd9c899b6726ccc749bab2786e9568d32299f0e659664ba1efe764944c4087c549e2bb717c87c6b876288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b340000000000000000000000000000000008d764f80994fd37a21f6923d7fef255145ea875c892888d45efb7a37310182b04d2c16d4d91a2e7c41164706afdb617000000000000000000000000000000001156c016a289989510f1c8b39bd6a8c358a1c5611bd2286e9f15983f984e89e061e60717f1b700abaed57076e148a8a956e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000734cd0d73ef7d79fa501b98b7211d551127abf68c473c1c72c591180b605c938ef71f66c422bf2a8bcf16c6c8946c050000000000000000000000000000000008ded96a9fce61040c1acc71d6496cf72590c63c3514c4f1f77d4582635af9eccdfab2e60749ed24fd3b6e30e3576c58ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd000000000000000000000000000000000df9ecaab534bbe9c8531f813a95a7733df6a4c8785575c5ee89647941a6984cdb5a33d2eced340c683339c18f5da32b0000000000000000000000000000000003632b2377ab368bc9f735609452e0ec9fadd6f261cd5352e0a5ed6a37b25ff7a49fe57452e79e7330661b81d7d80a64b96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000006bc4871c0271394c9d6099667ff68e1dbfa9980976075bf81fc18f1875fc91b50a0e3be622882c90b1594419da7dbcd00000000000000000000000000000000168e1dfde47d19280dc213bba9fbb61fdce41f81d4b25b2a7abae0404bbd7a413cdd89611966a7f9bc32617dca51f369d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb000000000000000000000000000000000132a2a6832653eac18e2fcb2c336292dc7990fa1a004404973029a227c9871181ffdd88a74adc3edc7a8308dee803fa000000000000000000000000000000000b230c171d5739fed98d32a3b27584bb0128434401e9e05ae09a4dcd7a017d1cefe7a46dad2db5addfb389feb9c846181ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5,000000000000000000000000000000000cc6517e655697449988bef516e60c8202fa48c3573967491ea2ff2db9fa0de3f542f656228a5b90d7fc1e5eaa8b06d7000000000000000000000000000000001191ca6ef2791726a31f77b0431ebe170d8fb595cf6e6b9b33b7fb0d4acbecf2d171884c392832d2a91832e982c5c0f4 +00000000000000000000000000000000111de2b65f5f94851aee2861910898b74dacf013591772902239ff7f71a9cf84919bc4a84d6936f9552e97314eb52e7d000000000000000000000000000000000db96af045180bd4d88dc8c40f8cd918d2195c2f3651c176c1ee3ccb583a7363e2c2c900f2a54f26a881938cba98565f7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd0000000000000000000000000000000000e48144181d956ebb37d72c38c062958f73de8944995c7e7568997b04ec19949b348fd80e810632462ce43c7c6571ae000000000000000000000000000000000b4a19556d8c21206c4198059adf5ac2b8a0e08c948a8a4d7465bd31c5ce5887a069df5f80b1df89ab868ca53e16c730c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb10000000000000000000000000000000010547f218e33dd9f9425c8e7be4136e65ee3dc23e0cdfd5f1caa8986162cc13b77d30259b6b9c359ab0faac9ba29bda00000000000000000000000000000000006729e532ba87a77d1e458663690110cf63eea96f8e41a5a338493ff71b68e78e78b9c929006c0410c3739b15ff2810069700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000000d3630086b7e0c068c60192be8724ab4d18409fa6ddcbed02b52fa776e84e2115457c40cac7e903047fc435114150d5c000000000000000000000000000000001066ce26d2e940899e80e9c0e515ce9d5810a4048925a7ddfe0cbb24b3d8d654c6835c6872fff5a988f525c648661cbc19e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b000000000000000000000000000000000ae73f595bc9d22c8c959eedec4d1301a13c9b8c643f4335160bab4a99886694d112ed6fbfbf082629b76d1e2509ed280000000000000000000000000000000013dc07950689ba36736838714eeb28ff3be77ef8ba181718ea7b5229e01d4e036c98eb9ff7a867c017857c029f7f13e3199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e0000000000000000000000000000000016e11b169dc405035037a10180fb368988498b6e209ad62260c7ef45e9bffedbb0587fe282d193bbf88311f3d2880cf500000000000000000000000000000000090a277517ea7a1a7cbd68598aa1e16977cc57c8d095f66a7cd3f67814c2b8f35e17e20d7a26fa67274dc5aecbe778648159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b0000000000000000000000000000000002ea8cba4bcbaeed7feaac63caf21645ddc97daf9250ae29994fd04e798f94dab33bac6e08eef8e6c20f122bc5f88996000000000000000000000000000000000f7a0f6ac02bc9821a883393c8265ba748f9d7c3ea763037bde3bb0178067e93aea4dc70d25e5bcda642d06f41a7f18bef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a000000000000000000000000000000001618ee9c413dcf713699b7910989c20bffc5ba1ca03e973005f49084aba558797e7f9ec20cb86f308d737b97c08f42a6000000000000000000000000000000000db1daa5ed21250c696ca4da3e82f6623c54d643d773286811e21c09e9ef7c9ecb9d84d90b9c76ea9f65e04a29f82750d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c50000000000000000000000000000000012aece7d9e7384ae79e047ca4b4fe72fe541a825530d6c38b9a8fbbf8b801883ccbc3cae7c33e4d811198a7b7876c92d0000000000000000000000000000000013fb42fb1b4e7785c1b66364de150d1e38fd9fe3d8f209b7c168beacf4b26c35fe0fbb4a41f30adabe4314b20b16319561d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000033fce20f9202b89411dbeea59a5b1c632435eaf29e2739163b0837ef9278ee3903ae569931e70f79a9af5a2abd29749000000000000000000000000000000000a50360c73c3f735f97d7d71b21b2831f7d7fb59c594e85b604dbb79ccc884349cba8eab9ce613ed60416994322916db03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd0000000000000000000000000000000014d9a7dbc453effa7a76c774a289957b0ccd72994e568c0de345b482ed2b6db9a3a3e56e0fda159c25acb43b4a6765d5000000000000000000000000000000000b916f28e3fdc62d296e421b1684efd4e9a4b523f79dfaecc00872a1d17724e1e07e2386b4bc6d76b157ae94559d0bcde4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b0000000000000000000000000000000012a4670c5c2847bb188464dafe41360f00621ceb3b5da0a3dcc16732f4baeb0491664ed8c2f95ff9b44e2b77e698eb3800000000000000000000000000000000077b561ed2fe5c91b30a12a2df71e76cc4ac882301d1975c3cb176e22874e28868655db9d0c91003442b0277eff52669c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000570e1a0fe7f82c0d3cf38d90f77634f8dc2bf9b58ac473d9bcbe7242a4bb76d11f36083c90588a680004c077e957a9e00000000000000000000000000000000038ac2b58a16af0a3a0070faabe3969025440d9781e3ebc22ff873dab532d6ca1b0bbf21f32eb9728a322c158f5390fa7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af40000000000000000000000000000000004a3a287fe4401c48d7dc804363941b5836cfad6490b00dcb0ee830e876fa05a42d6e2b036a4e213bbf5b6ae5a4e31ee000000000000000000000000000000001877a91254211b2af54ea910d9efdf4b4e829fda5bf6b0c2dc849903c357bfc6f55b45c7437ba538ab6cc795b71e95796f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000159bf4b0dc89cfc9d1687d8552489b5c3e2ed059164197028bc67c51ad18b341d04e4b8be660880a76a44ef11e785ab5000000000000000000000000000000001643a41fe4104ab0bb96200472ca67064635bb728e6d909fc0026216a90083eb612f11bd5983cf4d7fe664f1c527b96a12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd60000000000000000000000000000000003dc904709f1da618b6a623888015a875b11e5baa5c10eb6d750354c09359b180858bf29d24bae18e7c78c81465659aa000000000000000000000000000000000c61dabb7085a1937782433ec46b0a063a34e102ae9a6b6bae7d82c94e93c3cd05afe19f0673f729761462bcd0d9ca5e5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256,00000000000000000000000000000000109dbdd05f92274f3edb0232b827a4abbe115bd4d51f8a1e7b8ee609511125ecf35ca60991e75a0b7973c4085b7d4bca000000000000000000000000000000000e14a168decb6893e9c447b4366247d997701471a33bf8f762bde44473d516d857d1825255d8b4cee8d04736cb369758 +0000000000000000000000000000000009b85ef81b184c6383ff4e2695a8c261ab252ebd81bdb518001f110b2ba72fbf5014214f816c9453319934d8a010aa0d0000000000000000000000000000000013ce486b15a77cede98a46f66ae51d17713bef6dafbb2ff34c8f441271d52f4fa27fb88c5695f4af6d43e32333e68130acc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd0000000000000000000000000000000006a10f5973fd2aa312ce8f30ba5caad0ae6028bca5c186e4fd55ff4e3f5ce00220b94683e440b09a9fcee238af140699000000000000000000000000000000000ae8e9db6953ce2461bac3be78bebf6c4df8bc57bc7de375aa652d793bdb0899477464097514f0fe2d0badc9027baf3898c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf000000000000000000000000000000000c0c430ee1e9112d901b82e43a25ce4e5b61c81ed7ac7220d88bd10d44d28c1bd20fc8e1ad85f9b6eb43fc232594b4f1000000000000000000000000000000001233dee860032e2f9a67d7b3d61cea99f18b91620b76f8bd178295ac4fc3b8d0db4c4ff602085c7a897435a283e2a4eda0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f000000000000000000000000000000000061e9d3a919bdbdc42500b7daec837506bf0841caf35aaac34a3670517a59bf52343b47b46e8212208cd6fdca6b7140c000000000000000000000000000000000b87f7efb446cdba6e619d5fc04ca8dce8e57f6a76faa4a773c03ddc0666ce2d83682f24d8463d9331ae58e8afcc5641e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe000000000000000000000000000000001606311f79e836a03da5cacc4e1c3930695372f8f679c8f910627f86af15d1612d653c76d88b9d33f848f94bb63fa1ce000000000000000000000000000000000075b5d9626107a486079315a85991f3d77461b45e5c8aca6876287f624694c8ef1a4f5f0a5b65eefa8d6a4746fd2e5fa32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000012bc152cb7df01fd9ca35142806664fdbacb881adcf443051abac7c979d09a1c887fcfb8cad281f376ea3f6693812914000000000000000000000000000000000e32d4d6aa1f5046382c1d5e6e2f97319e8c6887b850b3cee498c482e35319a4f062be80f7f48ff3d1160ea6b18cf67824301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000001599c2c489535375270f0d1f370c6416c83c4043dbdb4999256f187e29c198b1f6c5bd1a52c997f01ebd3622c40feb63000000000000000000000000000000000b60ea3ee221eeac4a8a364eb52ee08579cf5a907aa5642971bd5523dee5dc6d6584ab993d33d9b8ad9de4a1a4f0cbb117a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff82569570674000000000000000000000000000000000b85c776ed6c9c78001ec7bf3412be495f40b0978d0582ad4f86ed54464fe562f9e699f727f36b2fc753f4328f0b2c6b0000000000000000000000000000000006e11a826fb4a8f0ac32f5c52a531508ad1363bf9b09919ccdb61ef25baa7718a4829fdd10fb6b680321cb7ef12d0c01d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a3180000000000000000000000000000000013539f96257faa2ae642c15f9c04e8fa7b2d6d095f7ca285e0dd90f022ec4a8fd74cf48557afdb57bace088b017b8ec20000000000000000000000000000000006cbc3e4291f373ee280eaface275e0334e46e54f65efc4e18b4ebb8ed1e61941d9c859903b56ed0d4aa3f4f3152b5b4cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a000000000000000000000000000000000675b4dab12db428a14afd8e696a64c0bb352bbcbecdcf2b064428b489194112f1cea4a383788e0bb0e97b7f88b817700000000000000000000000000000000013273075195b02abac630211c5870727a42e11bd96a2e2c6057d0c96bb60b73db72dec3135122865cd520c525588664a6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000000cb4629e659d5c2d91c5f909bbeb3381271ebde4f8486f76c1903e86efa78da06af752404ebddb3fc5d1a09ed28b3aa0000000000000000000000000000000019202a57e95d8d2623851973c324d1ed64b48b15388e052761493b1cdd6f3b54c6f47d2b312edec23e9da4c815f02e172e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b000000000000000000000000000000000a80bc4a39d62ca891044795e2b78f4eb82a3bf38c4ccb2e6d24ced4526db7c57ebf8b1951af0707af5ae5929f727c290000000000000000000000000000000001cbe991b082e840d8bd505a2eeeadf034f8f8c2bb530c742d7953089da1447e090d82399bc332127f14f1521c95f0042eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f18400000000000000000000000000000000112e7894d90a5cba2a8bdd0fa750d6e57c0a9938ca30526eb5289b4a59f92bddb33f59ca22a51d1bae03b850999180fa0000000000000000000000000000000016cf6b093a188ccbf1a000aa860fc794546ab0cf261784e7b7bc5750848f685d629ba55f71f2266edcf24d27667d2720caf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000005e2b8ac9124e8ccb6665842d77a2e9398e5b3519fa4fddfc4b10acb5eefceceb1cd6cc733e300ff95ea80d09e3bbeba000000000000000000000000000000001273d1990fa922276859d3921bbd49a452c821a9746c747734692d12c6f7d45533c0a7692d1a2d95e2d2be6dbfb3f6ad106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000dd2c7410b5f5ee63ad2a9ff3a96df2bad103caabe00a9892cc9b2ed2cc3bbbb53724b2ab63cabc44da7097b619f34c3000000000000000000000000000000000f695edd4b67f81f09fa89104c81717577cdd16db30901f4f04ac97e2e0749a80d34422bdfa85b5cdb65c042d90515742b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd300000000000000000000000000000000113c8f77a2409e0c7ad34186119833605f924545821895a283ec83bb6cc38c549a356b205c24f65be66fa627a378eae30000000000000000000000000000000013038ad87e3b3eb6545a0b5f7eec060895deafaf509ff6687024ada75f700d466df86ae5f95463c05f19750c0ce6cf56dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5,0000000000000000000000000000000015a7b2803cd9b078d457d3b7c62a2418f19c0cfa006739cf3878844c9e1ea115fa982a02fa6fa0cef74404bcf145952f0000000000000000000000000000000018ea40f019b9226cb0428356483f842ad73140a8da065889d81e35a564e99aacc5d5d833d35fd15713ec76c65f9d3307 +00000000000000000000000000000000163b380ea90b97146aa11c64b34de710e41b2ad54036a1a98659046f0e051e5961f30ea5ad78d8052f4a5d2a8388c28d0000000000000000000000000000000012afed5aa2e8c75e437fd796067e0c610a8a4c2f3368752413e6f179bbd4db25b18d5b3f8502186259a6368dd4321148ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000001bd4abe425f0418c86716516075a3ad09812650908cf383ec1396cbb6929bbc791f5cf65dbd95b51690b58ae3cab3f20000000000000000000000000000000008264362c7fa8021dec396c8355197ce4ef70e7b8894fe23d881d34b9a1b883cba1eba0e54d928b4eaa27aabde0df9b3c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a77300000000000000000000000000000000032112872b64559a03629b7ec8b32344b7d5f044670f6099d8e8b1a1d47223f9a42a072975c821d03b30d0994d782d830000000000000000000000000000000016042f6baa48d7c571e1f6c7cf3c7a0887bc4e2b2de51bae133d266dcad23c579e03d3284c09c83a54eff7f2151ce5b3dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b00000000000000000000000000000000028ea1499ad8761d908d863849ab4bbc155edeb03a7ef4bb93e96e25ab11c6dd0c21a6f06537a688189f08a00aa33171000000000000000000000000000000000ca3ee57dbe627ae681b12e0de4ed602bc3c09558444f38b0dee27320708549491a4482f7f101e8a722ef85e3fd742a5da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce4780000000000000000000000000000000018f397d7c84b8125844e874ea31d18b8705a75027d5324390e2eb7c9962d9de07add34a436db21a34fa7fc7898ef04aa000000000000000000000000000000001591f2cbc58c0841e5eeb8d9c75d8dfa0f2dc5e479d136905abb772a6170d131c0f2c9e8e55ffa215a4bd732c2fd85556aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab7700000000000000000000000000000000135452f0f8d4559ba041dbd2ac45f15416070b1674c9d8094556a289716814d2a4efe14857aaccb82c5ada5d6f0d15ca000000000000000000000000000000000f1c47592319db60db724c9d0649d0d713320be7dcc28e7318517ef80a3fda71fd1f4b722633ed7ab7df06218ee593e940273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a00000000000000000000000000000000003c75767678539abf7a62dcad5f90a3b4a54354fa70206e789a1f9b5daeb5fb6d9aa222476c68cf9db8a0789d7ad43d00000000000000000000000000000000139bcede61bcead99ef0d9554ee1c19db1869fe041671c199246824a923f5fd94e1da04fa17ec921bf6e82b14f126702b77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb5400000000000000000000000000000000020aadb198678aab5a71cd6dc33bd64c47be6d080d24f2f1bab7239808c10867ddcec65e27977b9eabef64455cac25e800000000000000000000000000000000141e58a9f8c9bd92d2de58bf3bbe77a48fae9290815915d7980f4835d805486d678ceee9676ab4fdca51d0fff411ab1b0be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000017abf5f132e8e466d2cae445d75978645c3b24284e1b7df7773c256ffc342d1484976ea1046aeb5307f735a69e2fd20a00000000000000000000000000000000087ce2fc44b9ed797f29c352393a8ea109281514490fbc7dc489acb55753fd5c577c4af0ca6c267c83408cd95b355e26c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000000b106b2b94858155849ec36741c7fef4d97ac704baa6752e8230e172da7208b7e9f187ef0a6cf054d00f2cac99235b8000000000000000000000000000000000d94c6e2349941a20884b9c2d702237c5b5ca2ed277bfc79e53452f1cd6f9f49360215d20fa06df238a7ad4ea253c93ec6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b30000000000000000000000000000000019b33665a81d0ceecd43f003eb34e1292945da1361adf118f36aa5acb71bd821a6732758a4aa6988e29d4cb70004df45000000000000000000000000000000000f3a244e578c66a9263f020e2f6ce49dd655c7e40a992c44cee40e1c874588e464f6254ba644e46adf348a26025d6d3ef3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a2100000000000000000000000000000000015618db18e00670281adb20c975f4774aaf169a653d5f583ff6966113fa773075db78507847586fcae82d6a468302706000000000000000000000000000000000301b18d0fe7d0db7793c62b3da072f4cc2fc3425583537110306e31cf63b228cb8c285029044c7b9439c1227d4c7ace2809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c00000000000000000000000000000000053001a82260b26e34e05a203c8233095da1da58c5f804da9cd6cffce07170e39044394f379173e1340da055066d320f000000000000000000000000000000000bfa2bc7fa0476eeffae4df98bd814db751eeac1dc67205c7629c9921928b55c70c2abe242728bc078bc2685690a38503521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac680000000000000000000000000000000019a7f78102671f6d84ece4a5bdc54e59cbeab60a8c6c15a708e0169f42a52e98bbc1f8ff52f34959befc859d308fea250000000000000000000000000000000016b5d76caac944612d1dc687c6dbaf10ba60a12b491b17b6c1c876a5dff933c4bd9c6f923e2ca4cd1dab38fb06dfab6a9c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000007741d8f72a5ddeea2fe82fbce4b3d0aae61e1ab9243ae6a3200711051ac74f30a4dadb597130fd8389353c230b6b7d3000000000000000000000000000000001809f1cc2fc23be0f05b3d12e6891a6aacea121e6db77400638031065d75c7b3fd9a02ded481eb3893b2449aadcf53d6dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd98966400000000000000000000000000000000013d9fdf041ecc7f2c728fefbd6e9da3169d872406b6fa77a52e342fa8852358b02bb2ae7ac77f83e2b25f0120603d0e7000000000000000000000000000000000101ae8e945d31a98c4dc3ba0e01592285c0c92721372bee6b138d9148883970708ad5e585a1b81d82ab0656a3b03a2c00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0,00000000000000000000000000000000193115466c33711b07826d2a021792b7c238ae67e3bcba00b24026503d818f912c5635f85e85402e3a6b2a8152027afc00000000000000000000000000000000157fcd63d3a0e48e25ca0196b7ade453fcefea33f09123434f418cd1d65bba65a789e4c7d6ddc92d4fe8aaf6bffb1ef8 +0000000000000000000000000000000019f625f232faeac09266c2c4881f92c980db551ea236dc1250189c1e71dbeb151cf74e43b4d5f465c6ad92d75457d10500000000000000000000000000000000175ceb7cef0f8144fd4dd82428bade99833023125d34fb296f198673f4848bbbee343f2f2137b55b5f8c5f74032c1ccaa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000001353a419548d05e568f36adf72d40ba8b30be9a78732660331a5196b0f81b52330ed70e5c635acfa9ffbf083e46c8ea40000000000000000000000000000000013ca17c0dba35a747bcd314d87d1c6558e9f569955aba3d958cc5736db78d16132c9dc8f93d5eaea749a0452c13139da92073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000019a1bdc1f5a43fe746df46a7559bfa0bc5292f574fc424b134fb8b2d971e191b3c5d222d39515dd145819d56d5379d12000000000000000000000000000000000a08d0b7c7f5d71222e984bf574cdb7de76a7b3c61ab5a3ec202b295c62366dd958ffd5bb5a5c6c84584342bc76199c62117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f50000000000000000000000000000000000f2c85f34994643712207fc431219b925f4e701732fce95bfb387ac26ff95c9b10408d24aae5005e437bbae924816b2000000000000000000000000000000000d4377368df00dcde448d8399ceb7508a8fa1c17e9d9a5e09c4fd7c09c253529c07068e4484c7e7c6d3ed6fd3ca777fd9087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000d1483ef230a2ce75a59e07f83091291d2524b5d043db8d5583914a6775ce2c80368d9441aa2dd53061a8d9121a025ac0000000000000000000000000000000019100e75a72e07391db9574b3fc4aa1c669436fa802a1a5d71146c5f4b7fe118a5ee71a9df50ff67633f161fd151b947255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000003a88ed50b36d92aa4411afd0a340497962c7740d629edabd505d6023ecb8f9daf0e5bd8ab9dca26ed2ae3ecdfd98b680000000000000000000000000000000013d9d64ab16ce9401988db4855b26b994da09481a339c2a2597401adb72c80718a4df242776f09ed208a8f34ef7f67e6e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c40000000000000000000000000000000013b16751ff7f6af64c06f9ae6f59e1eb6c3ac76355e6192e6eb44bd1a9f866705eadf0d2907e2458462ad731523bd340000000000000000000000000000000000ae691a4fbf3d0fc72c0e14d4b31fc19c52ca07a81db0ba93949c56a9b75433257d784f7bf0611259dba8af77403f536fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000008ea61aba918d691a0d04582e1f48d671df39bc7de29a6ecc17b31a32d485fb1dbf499e01a9aae5ea21be5d6ff9808de000000000000000000000000000000000f7e8863a541be553b36b8424ba6ad057986a9f78454aea770449a23de70fea8eee6bf8aa30e96e90df9a373917452f70e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000010d41968ddccbb34b3faee226750e99301ac068d8e6f13e72962b53fa2d019da108af82bdadb3cfeecfb85f53607400b000000000000000000000000000000000a90e50ac4e0c39f579a19d49e6f64de6bdd5d6a3f9a91ab654f5be01b258af8709ce1c5a994501177d1c70b25e474a9a49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f000000000000000000000000000000000e85db21593e8d3d86df87ceeea7d7853758d69e15edd53fd7da52f0328805db785aa9aa5db25417d76d796200a37d1d0000000000000000000000000000000015d76c5317e1c8cc5a58a0cf0700ff73d92e7f60f4094030716bb8c657d5c75262825fc0683a88278018b4899a1c1ffeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a70000000000000000000000000000000014c9d6aa24bb34080b9a99d31e1bb431e911b2ccda3c8dae9c2c2114abca597b3849c5b3dca756d0f9ff97616c0b724600000000000000000000000000000000050224129c08fbb2f2d16596f83e2d09a09526851c4d52e8d5f0afdae7001af0006edce648efe7d94b6712d012817ff753961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000da4574f20849e04bafbc41bd361e8f4411815b9e7c2fdaa9a3ee70d4f608f89166dbe9e1cf4ff0fc9ae98f27e115c24000000000000000000000000000000001463727b23e6afc17101cca45de7d08b78358605c7b1ca089fc52f6a3c46f590210083103e51a122ed0768be2adeddefa04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001363808474ae9481f54d40fd35ed90c23d4349403d43af0dd603f1db6f5fd5ad8b77d21426977b78f1f5397df17f0bfd000000000000000000000000000000000118560d0cb0eb2fcd3b2d51fb2aa379112b3075e1d4c20757ec241a4877af271700d3412a8fd6f3f5a3dbdf4dc8cdc9b688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e2252700000000000000000000000000000000040c13a6f53ca485a578c6f3f49d917b774f7b2d1b15ed3e748a47b0bc0be8a7809f0ccf509f09121fdebcf8af46023b0000000000000000000000000000000014fc7869df366473b2c4adc2c0b12acfffeffaf22b4856bed6ec6d15f0f080596b81f3aceab9360e99f35ee7c43f1e2fcf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b600000000000000000000000000000000177b54249c613f044b40a11047778c86f09b20ab387ecb8165c83b36a1af046936623fb00764740a90aa232b7f7ae6bc00000000000000000000000000000000040a52fc58007717d6e1dd8486cfccb1f75827c2feb2b7d59b927c4bd23e5ea80d120875f611bed4b7c12b8a5c929475528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f365000000000000000000000000000000001918e41c557305934aa72aaa361d15843ca77c747ac16cb4c251a2f0d7c218b60a5588b0e5fb3573e8186a48d725e50f000000000000000000000000000000000cc4fa5302c177f9ef018445ab722e568347f4f970dd893e3227756dde9dc8cce3eb2bbbb4c3cd98af0ed4a45c022cf1c32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1,0000000000000000000000000000000016da14ee1ec80ebf06c0622a500a8eb5a560dfa3439a8e53a19b51c6c4576c31a5486c4c49a8050cc1dc30287f33b5b40000000000000000000000000000000003b04355b2d78266675927706874bb7fa67d441886972a8190a45398942622f09ece67ea74d49bd97827fee82d4a7a37 +000000000000000000000000000000000e0ae8df03e4d6e36e1c156a35425a3b8189b56e8ce90045d16cfebf7fdd973d207db6391dcd007c311af34f495cfe0c00000000000000000000000000000000198e58d5278b2a82606af16a9af3f023b7182b6b5b2d685fb667714e9fb5c7a3fd5c98dbcc84ee31fcbeaa8f832d7c854f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe0000000000000000000000000000000007a130c85d67f97fd0dc2159d35be8984bfbe94c28d9d96bca8bab844dffd9a6eb3052c619646a4e564c0d47864b31cb000000000000000000000000000000000e2b8362ef5fa5be398a3589413ea69e98b15cdccd203119b79d96405c2c9ae9ca8eecc7533512a25421e1748ec3a1b74b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000acec379756a1fe9fa72f03da4dfa18de1fad19281f262ff39fec77684f0798b6d8aa895db93dab58165b67a875572cf000000000000000000000000000000000a246df19a23260961ea578a68ab4ae8811f9f391f673eab2b6fdd56dae8ff3b059e5b69052c9216529603e7eaf4ff306765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f00000000000000000000000000000000001bf749b61d7081f1e6141380deb6a5517d64e8c290363306fa23d6ba3b4e72ef53933f15ae77060758287a5a5c2bd4000000000000000000000000000000001661c564a5bc4dd852f35660d1e7c8193d76a48d1f0f3dff25adf312e28ebe9ce8972366ab224a95a7c1f6146b9f22412eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa000000000000000000000000000000000462a37cc68530a1c45001cda667e1ec10283b826b52986adec03db59a266cafc18ff76a666c9de9fc2384c5e336404b0000000000000000000000000000000010736bad21840f49466d9db82f01a922f4d6ab71f8d8ae246765300531b2f806663da2a8c16c644cf871a877b210b9e3f8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000013ee85b0c8f999c9d0682bf3f18a553b64aed8addf87e4baba55c6ad88de9c9955b82155caa83b8b6b7961d88c16c7dd0000000000000000000000000000000011bbe00b5ddab0b579375e2014021e3bfb1e11b7ccfd774b8679896c0ee34d1d19890fe5cf10e33e3332283b3a3dceaa28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fc3750c957b3eb656ad552c3997755bf28a54fe4aefafde15619133ae04a47f7c65122c86ef36fedac0c8e0d93c3836000000000000000000000000000000000f7f21014b7a9f07c2212af1b85395ef3072b84ee5e59ae675f6fdb9cac858b6213a264a202e29b45a57c69be5259470dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000d1fdcb6768654b6bc1b4d885039f1649066db8037f212b2d699c02606257388000b0543d25aace7cd1426462ec25c6b000000000000000000000000000000001386eb9bb7d8be5cb9e74a37759458091c44eb814dc3afbdf017a891359831ffcaad85d00d8e100886cb5624562ea0390adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000f4d6551f5587cdb4d92e13e3749f977f5bd35b5b71667edd79b5006d4b0943331a0b417f669c6125edc42099bea22be00000000000000000000000000000000041b8ec8547b710bf2c15ff41ea779f996db7996911a5b4ae9f23073e02b2c252592229af738f684e9cdf48aaba0512a345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000000c4a3756f2affd338f688ee90501f4bf4be43a4549ad8ea6aea69e5a4be015c97ef088da1a39d1103f866f1675f401900000000000000000000000000000000023e5d0bc92794536d59425c4bdf18dc5a208841953e5d45ae91f25d3c61bf66e704a8ca62a574ffefaea854fd23b8d65ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c0000000000000000000000000000000010129a00ea1c30e98c40a6c86090327d0a9b6c25b488cb0e369bc5a0e0658ec9ac9305e5d1469dd43395f72ef8a0e7e80000000000000000000000000000000006d2f5d4f3f8169f722427dbdee62f45f9791e55988910fefe188d6535fa15e2aab8de5130e81183e6ca25a8009be66f853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef3800000000000000000000000000000000005364313c0d2220ed57bf22cee05b77a53c24c97addae502c7b3275a19522b8ae8167194929770191b96b957b19e5550000000000000000000000000000000016ca50cc1aef3890dd338c8a89b906812ce26e0ef9035d1a026f686b0eecab718f6b0ba401556423ddc99d96dd812d566dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d00000000000000000000000000000000007dc52982caf2f5efa3e1a21e22cb8fc53cd0355f2777272806710a96a22f8e896d001bec053acac6241c7637df158a30000000000000000000000000000000017e9f4fb0adb96150095ad5f0d464549d1489d04c4556576865ed3045e0c477beea3115a6ce63910f797fef29f75bad521d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000000695feaefc8fa22f81bd48a41e6c85acf38fa542e96a7562b8d65834c2f64cf5770ab6731ca85b0c5a80a73622acb83a0000000000000000000000000000000003df65226205511218c263af6fe33a09fa3db22e636da54dd967741657e9da6367fefc5e33a370947f2003dc139765083d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000001588a4aaee74856a9d41305023b7eee367648085516c8135fca8c0a6c9cbdecdb2d7b44317286f3a06f92b9eee2470170000000000000000000000000000000005aa06c47bdbcaea82e910b8a2c43c13c23bdfe1897efb2a57d622f5251f0db6293ad21d988c3ee30e33f3a40865fadf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000140d9a251d355cc6a8ff9fdf2223df59747eed11ad140297b6189a8d49a711ec748447ddcc45733a3c36a48da8cd46880000000000000000000000000000000008ce7046871c0b7f781c667958ff22da6ef5447bd319b2df36c9fae9f5597c020c12c7fbc733cb75ca8f9d9dfd942954cc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3,000000000000000000000000000000000f1abe4dabd68ac4443ff50c0ecc5425ad60973dfbd2b29462254ad618b303dda061d80b28117803159ba503214b9ccd000000000000000000000000000000000d3da8a424576cdfc147e18fab66de0d1f93c05c0dcb08d9d5b2a9f5dcda76600b771991bf2d67061ea5b96d20a45c14 +0000000000000000000000000000000004d4ad5e9acfcabc0b93eb9ea59a778a37d7beca03e285382d10d97803ad63e11aa2e3cd1eabf72383d93528e309c28b000000000000000000000000000000000855cbcccda0476699ad3de8d58b4502f9e61bce8d7db37e9fd26ac4649a4cb831cbb74ecf044ae6014c21148382cca3864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000156e86fc66a8b684327a4de77c31abaebbaf2ee5f0c4d5f9238c7d4683f08dc78d59fcdc25928d180a6f292bee23a523000000000000000000000000000000000f64634ec7de1fc93959639991df42e7dc621380f4433fd7efeff72ce75f6ac7738396a912f78ecfe70bfc4d0ac4239093064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f539000000000000000000000000000000000ae2c40a49f6539bb257fd759c2fcc9f7b09d00059c7a7fd41422ce39aa0792413894bc716d66dc79092223b63de6ad80000000000000000000000000000000017a82c6a853fe29f98129998708f0d4d2b09fb22b07474395050d87cfe4d3bbf94967e05861c20680dabf3f4367135a75e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000013b950aa9b7675322d7b39e81b13b14f2480155f74bdc5793425a02f7de41dc1ebefe4f07accd3719feecfe366e93c440000000000000000000000000000000003378e83277e4b02c3b517d3a8cfbf2d2a6585d028c723b2a263e6ba17faf14bb9aea301cbfdfb73f84709e2af99867693f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000004d87c87f8f05a0999c712756bcaa0572b70264166b16eea7fc4785a59cfca18d5b819f0e65e193dd7ec38d0756b84f20000000000000000000000000000000012f64e2dfa3f00ad8f7f68e08b24aae83a049390fbdbaf570a7973d8516dc90e9c5c9211130d5c6c09f5b29183e24201145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000851a636dfc668d1c5d5467774deaa78283a6f56cc514420fb2b6c58ec831add57b5203e31377a57adcfd9097a1cde2e0000000000000000000000000000000008828c34d4e712bdd5133e220167f3424491b9f47dfd95406bc833b3b030037c0ac0d2c84b06b4a2891c8181359af350ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000011021119ccb1cedf88be6f72d3999df899efc4dc28f828831be911582b61894aa37302f84ae9269b97b03a2e30d66c93000000000000000000000000000000000c373df4c0cc1d8a75cf2b9a99b5889811d3ed42850f55480d891b2f44769a371fa4894cb5bf78b7e995b4912cf47dad87bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000005bbd2831bb4eb8ace45ed719056b95dcf5bda8831bc1495f763ff5e82be9708a004a00ecd102d4fd084579d892e5da40000000000000000000000000000000004de171bf5fab4c89783ad1d0cc9fe697b827f023ea1660b0fa2cab108fbcdc80837d46f292b6062761dd865bd1f905f68cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000018b36452aa579eab36db9b0417c999fa334292bc7174bb88e4bb14025a20c86437d5cace5369b90640c81edbf2d60f2b0000000000000000000000000000000014278d1cc3fd07e947419a6a0d7f7bd5f9e13fbd63779ffadc150e3d5efdd1a3f6f6e5ba8516066b75e1925282d0e644a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c5000000000000000000000000000000001365922301de7c81b839e970775854881955f35ef7f718643a97e54746b9d9867ced3fb7525caf5b5bd0d382de02fedd00000000000000000000000000000000000d37c4e106e51c4cb65fef8460846eab04fae7e5ae1d1dbaa1e0bfb2eab7f2e27a9cd5c3cc942e38b021ef71827a0224c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b0000000000000000000000000000000003b9de0464ac24606ae840185d2ca6cc78773b674688a028161341b88907213e275d7dbcb8d8bca15b483922a09297170000000000000000000000000000000012ee2a578c09b7563508d0d94ce6ed75d277ebd89a7f1d6095f8992c0794b4de12e33ee24547c271e17b7a045eb3bf5b17ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a0000000000000000000000000000000005b7aa071b76f93c765f946b96a972c1d11a2c44244355e90cd77ff069b930b2e8171f7cb1ba29f7ca6e62d88cb83c1b0000000000000000000000000000000012cabb25e52f00f89f2758790f9a81d0e336ccd7bdff06a79552a346d1966f54a5157130e5aa8db175aa64a431e19e494615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000dadebc30ac3e033f433d8d012ffc70adc146f4d9574e5431360fb4a8ff0891c8a9f38a8754984a385d704086c320ca90000000000000000000000000000000000238439bc4e8c7dabe260c7b40d317014463c4728d79f521e7e321346747e9aa65bc6b32ee5920969c34421bb99bee9d38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f700000000000000000000000000000000029df69b4ad5cae9fd974da7f58e4c55e83c61eaf011b5f22e1308b56e2c31530c170b304d39eb3e8a3009b67b308c6700000000000000000000000000000000140451659b4d6eaf05db63be5a7b0341612747eea7536b958b0620bdfd7b9918e8bb76c05eb2a528bf4727e38605f99a364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c745900000000000000000000000000000000062493361a1a862e63eb8f20b0610a78d30ac8595e4c6c3487cf3add7cc38613870c2ecd0cb5a869110a99b76fb9055b000000000000000000000000000000000d8918e018ac5490c91cf2574e6a6962b69c17883caf2caa473de172b14961780fb237236b56a236ce8c674dc9001547031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f00000000000000000000000000000000016470ccd107b2afb9ca03a0efb958bbc165304871e683fd606d2e78f65e34885668c6ccb655d4fa98f5776280e63cb3000000000000000000000000000000000982eaaa34f9301fe0ba1915cc5632329715c506528860701f5e52d1d77b8fabc89706af2c4ab3b729251b9472cde96f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f,00000000000000000000000000000000112fdd661f948495ae2b9200d959ddc873c53c96ee4ec175f6748e62d76d61d6b15970d3a3a52ae9bda30a822ada25b8000000000000000000000000000000000f5b38208d69b5b842bc22ec9d91eb6b653acea5cb16569c61bfe0921f2d8ad613239e87d48c6a9b29ed6a5f43578987 +000000000000000000000000000000000a9494f10e6187fa12d88e350ab84ab5bbf999554924e781d6470e700c3da78e411b8627459b3359d7363b088bbeb0da0000000000000000000000000000000017edbf1108591996f28ae17beadfd6b52340236c2741bf8474dd7471c19c1f62a0f28e8d8692cf3e700ddd86a931dcab4aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b900000000000000000000000000000000052f9c6ecc29239c614936bf9ecdfec677afe80de019230180d0fe529a2e82b9e15d6e081b02475e2bf812cea3ba6c640000000000000000000000000000000003dd0afc91516b50d9027c0b132453fab92b165c08428fd5c2cb994646b6b1cd5b82b7c3f7924e4a5cf8b45575e8dfdc22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000da6e13230b2236b2bdf671bd5f3f8bb47bfc637d6e3f1796b555a95e51b86d04fd310f3d3198dee604baf48f69ce0950000000000000000000000000000000018d209b03f61056147d6734003daa776011b70a57e1ab17d3b92e2565b31a846d8fb7c3fc6fa1fff04552800b73affab895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f683200000000000000000000000000000000116c72b5bd9d30182463c592adb8f73c16d22bb4a22832b8d47b683da5f4b8179d4c80d361ce69f92a393027ab29c18900000000000000000000000000000000026dab8d729338903d46a219004fada41eb666a9a90d8ba115f53da9e89a7bc5d824d7f4071c8859df52b3ede7b7dfaf4c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000004e0627475a0d4da458475dbbebd6c36f4ce771bc2b2a8c6adfe9d372ffed05afbea207476af26974476c0cf51a9267900000000000000000000000000000000199ebe83e44a269752d92629810d0c5402f53a1bee03ccafe0b3299a9968ec45abdb5a74a6d90cb026cd9b28cfd2b89f6d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa000000000000000000000000000000000b4918f4bfad81349edcb45439e148af7af6664094412c9a51b887271cc3c46e34147c8a306a19f08922bda9c7146c61000000000000000000000000000000000afc3d1a7c4b6d899149801cb74a7e64a126631b3e758a73feda92a2867c53fd3efd9adf025ca6f6c762029c57706b0b4cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b1384000000000000000000000000000000000a093eeb354ddfc5ea3090b20312788923c5db9d78905dd31d5bf15cd83521f2f186fd284de0858270eea05d21801aae0000000000000000000000000000000011d047410dbf6df20f81971327b38996484e0862a9f71879ff63462e189471c1ba391496753456f0b5379a3b36380e1296111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c0000000000000000000000000000000003d8e8e3a442f911e23b353e9efe396b746360254c14216c752fad17d96d440988d5a25f044afd37f12d74c89c8cb2d700000000000000000000000000000000179ba95a3d3b5ddd3d181e2312385f4ad7232d9af0c28f375e2036157e4603c1a01aa6c9c91496bb28508e5885bc2e599d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d000000000000000000000000000000000f56dfaafea0ce3152458b7252fac14ea64483e1d4a00a44f95bf3932eda2f2c51f0239e6a7a503cfdbbdd88aef2f4880000000000000000000000000000000010e02e9be7c1b795ebaa84f83bd27eba4f12dd49b146db0d788e37835338d352445e82060dd595f616b4f6d2d03cf4c911ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d300000000000000000000000000000000010ccd262b0cda9ad39177d31be0725b83e935c690fa8e07bc7f24e26f8b03122173f4ba43fe8ac933a7fed79f4496c8000000000000000000000000000000000318da543dfb04005a3cf6d93d6bc4058b4b93c4cd84ef978e6a30dd85d60e5e359b4f518842e73d182567ec4fb236b8b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf4400000000000000000000000000000000003dbf6c0676cec0202e328bf408a8fcc38758db1adba3e8184cb3904ed204b7e18db2183f5a1833737ad8eb089afcafc0000000000000000000000000000000014d9add10a0c739dec7fd09c57b3e959f3b7551eab8423ec5bcab4b14e63b7a27f128758d63f8e43a22eeec7bcaddd41a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf2411691190000000000000000000000000000000014d0230f7d5c51e6fff6490c61972e2564bc31fea4a6d1f293424934f75629cb96f189c80ab32a79b2e988582d0283960000000000000000000000000000000011813cbbc0cae4cf6a8d5d58859f1c3b75ac53819129f92abe0ba9123a1a277b55231e1a24745d0d2ba6242ee758113c87fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c5894500000000000000000000000000000000006adb1e972755f04cc57170d19414e6930d0e6d42c09f587e490593a5c01ce6e827a6dd1e21570ba11c7e4277d532e0000000000000000000000000000000000ef599058025f40c9f77ef858aaf314faaf8d72277cd319a84a9d7038d81b76aa260df0516dd38633b22f9d3996e4761c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000008190fa5e3d23c0186ba502a5892b76cf8faf2c15c91ee39d51b269b6bf4bd3e7ea395787d989c1a14ad88f3702cd6d00000000000000000000000000000000118d2d1b28f9180155277b80f1a7937dc7fe6be3b00cbf6a7ddfd08cf653ed11a4ddaa44576e70b27cacb7646a100d03f29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b49899117000000000000000000000000000000000d28e7ef8433f8d5399ce3cb847f2633392bf44ae9fb2d402ed8e7e6a22de35c39e4f09ea0fe673ae3cb652f75ec80bb000000000000000000000000000000000ebf2ed9df06e2d5688d0ea812b7f9de78fe292584476b20bd62066977f5e221dbbd8f552547f06a3e821a53aeab83c1125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000e162f9ba960f452c269bd2f9f06e8bf1ffe737788d6364b1f75ea2788fda7e265dcaa907e45bc6ef7a31c4791b470e0000000000000000000000000000000008a778bcedb58f562c7b69ef3073c81866a395d6408829816be3172e1e825ca6b88f156ed2b2ac5a8784fac62b893896bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6,0000000000000000000000000000000003f6acb4e1f7856413fe9317bc7cffa45f2053ae62e3b5e2b82ad57b48cbeb4110f53dfcace08bbb1955c21b50fc636f00000000000000000000000000000000172cf1d257572562f9fc390f96f2c39dc5809765b0b94240a554f8bbcc44e3658f22e81d1e6c43742ef24882934cbbed +000000000000000000000000000000000013d3d80910d9f43a707ba389b03bda49b65081f65096bdef3942f0bde2122ea575abf810f400d47ced92c45dc73837000000000000000000000000000000000755b4f5a055c718f268cf3a74533fe8e8ebf37aff3045b58927ee6ee7a862c8c1cd61f00dfdaf6cccabf981fff16c7908c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000001530565bb621f7cd530c0eeb4cc41c2587ef8123c552aed339f80711c157e1595baa140434385d0977e9aed2629ea76b000000000000000000000000000000001806c5a90120fe65450e84ee0a56e0176e944a3fffdd2c83bf15d7dca875790d2f842eb31f640456a1221e44035ad33ca0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000143723a10965da7b47bed0d0b5bdb6bfef5b748f6e185ff2efb73c5756d41d77b8c217a6d92245ae36e0add4d743e7e9000000000000000000000000000000001274e8842cc812435a576b2ac19edb84f72d08cffa129d7f4e44be5cc88b3449ecaa719b4d76aaecf08ddd30f7b184ddc252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000379e08cc1f47014f7eede433abbe881818c0c3a9cb02bad8cc86242aeb9f9542aedb67313f494fd19971a0a15d4ee1a0000000000000000000000000000000004e83a0e52981faf6a787d0600ccc457ddf3bb81c76117265c1bd011e5b4f3237383e97dad3b019623521b3c94d67df36d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b0000000000000000000000000000000009ec14585b72733f621a58f35ab30580f131c93db491d4d704c8da2a7a0a1146e144575083bd963238434e2af48d3d57000000000000000000000000000000000ebd1a1c160ba7c8e3c20745bbde05f08d7f3189ecaa831d05c6a34562d6d3ccaa92472c67bdebeac8494658abf2c0405c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b197300000000000000000000000000000000084b7f967b141c94df69804a723169f69e05629c97a7a8c60b140787f3361ac87458372c91e04c08da2d01fa96056ef8000000000000000000000000000000000d731a1a900551ca569b8066af85176b934b94332679aa59924eb7d9a5fd776a55b4d7e5ef2413c53c244c848694b06411009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a0000000000000000000000000000000001847861de1064a4226435ca43c1cfbc5d4660fcac177654cf5d497ba9aa5a6322f1156adafba852633e111576698bd00000000000000000000000000000000005ba738972bf139d91f0a426c96fcbb3b77a01af0f2316f2427a20882b5f355772fd6d6016ed77c31c13f88b26c628763e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf000000000000000000000000000000000148907d2335e046c50fe213b717fedac86eb3920099526a62b4466749d435f5ce11a45032b60bd5d7b26799adc63f830000000000000000000000000000000004bdc2bab60cf6df6dfd25c16f04edd96d5021b97ef38cad02cc1fc7f12494098eb793d99d15b327185718f81ec0ea620444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000145ea0ffc3b24a623d74c27b84a390be062542795eb93a2f71f9358b44b76b93dfc0a2ae507f07a8a07edeed2410e5c10000000000000000000000000000000000d407c6c245316b5cc6b62efcd082829354d7e9e69ad739ae0ee55e6096ea08a48c59ded4595032093c32634576aa132035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc736000000000000000000000000000000000123f333f3554eac47c8daa1d4b362e42de1834ba9f55e4fee138eaf1a057036aa6ff9f50cddc78dabd3d5557b05b8bd1000000000000000000000000000000000116d786097bcac320327d7d56aa734d76d48a677e9c02ecc0bce550d75082c319f568d94b41e1c57c6075ee994e33304bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e826000000000000000000000000000000001012b1790e287a6328cbbcf80eaceb2c518a70e80cfe17143a41c4045e8c6c5317aafcb34f4f56494b401a8a9f21b5fa000000000000000000000000000000000613a88e513248538c1b767ba4d3667bca7aeee7974f691b7e4f012ea9b2b32603eddab0943229f53324c51838d18fe3369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c31aa6f315a1102ea973d13e858d079221087edf178d98fb05701ed0a159309fed05942626b29ade066f8cef465535000000000000000000000000000000000177a3468b7de9612a93b9f2bb3f07acf505f56c63f798b4dfc38a25d0fc133c862e90ec8b40dc94004cfdcc9da197ee7ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e12000000000000000000000000000000000092095e7a431ff3a8e51e26c24dd4a5fed6d4a4a169b5ef79e8822611da8aca5d7c27139a911d5473442db9ee1529bd000000000000000000000000000000000c59f5a649682e864a792ad50fad57b7cd14cbb19d1feadc3536515f01053fab26950f56bb78d5a51f4368e73c19062f2cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca3610000000000000000000000000000000003930511780f28217a125f524ddef656581a4ba2d461730f0837d1846d63258a02e659b25b882a3c3d077c880a64e3cd0000000000000000000000000000000019c682245c941c76605502785b1f79d37f65cf9ec61a4558092973bb2514de4e5852fc757c2fc7eac1b01d414248acdd60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff7900000000000000000000000000000000000edf518026cbf2dcca1d46340c24fa947261bcef36e3c8d026a09068a10a5afdb0964b54b70bb3b27e27c4d2e0bf9b0000000000000000000000000000000005cf718694ca47202be8c0afd56c88742e2b467d01e7b2330de778c434a57610fe7b8bd6071836a58f5d6b2876cff05a652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000013db13bf10b6d8b1ce5dccec98745dab635b8bc81d03601785185cccddfe2dfb3f3f9f6ed16d2c1a7a6bd63264b094d60000000000000000000000000000000001080522766b6cb5c90e6e0ae11ab4ded3db3ea3c7e69d00f29155283f7b25f762eb35bfeedf00caa83dcf04f22ee72976a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20,000000000000000000000000000000001975e01d64d38cb8f852b4177104388aba62c167da2a8530bc59f510a990ed57be0c6ddfc92622234a121ca407853dbb000000000000000000000000000000000de737b9097065462dda93321d5881430f50631f6f3deabca4c41cd491f1e441139bf8ceb598393ab2a3424b0acf290e +000000000000000000000000000000001624f6ef9638cdc5f0b16b47ac8c5465cb479333a4ee4caaf6d2b656464d8f84387f01bc1811924312e6cc1e29a590c300000000000000000000000000000000012b3bcce18f60c4b2159df93a2d536bdcadd675439371acce011ac5b542fe1bcf89161fc3b3644679a395aed31dab569f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f0000000000000000000000000000000005d49b4ea69c41ffa7727b98db08ab3fd3ca0c0261ef04b426ef29e724bd6158b3f3242cb915cf0992f2a631fd9b4421000000000000000000000000000000000f635c26698cf5dffbe25ff496f80c5de6b181f94a907204b79b548c1fee8c7dd426b49e9eb9eb0b17e34a26628c38e71deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f1080000000000000000000000000000000003a80767130cd3c3fff0610f215337bc1b4a88886778fc0dcb6bd3cf7bee48f4c23c974c8883e2cf32fb01a84f9e148f00000000000000000000000000000000173f518f3349c1f704fd200747158940ecc395b04b4c476f406cc27836df182c3f1b707aa05767ff1bc75de42dba2a824b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000011874da4371ee8bddb34bc92fee6bf51226878e4550aa33313a434b75243c1f2296c1d62d9f31f6ffe2735f4f26a8082000000000000000000000000000000000f82551ba2b803e35c7118f4294626c151c7137eb4b97aa5265ce383f7ebc5ff5fe381776eee724aebb963d2bcb3d9f6124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010501308d1a05e69700111431a0ca99aa41a991555b9a53df9c38413c67fa1b1836853bda93bbd8679e7724b3141a8d0000000000000000000000000000000000b033cfca384e480f73a4f8f79ceea706d7390e5b702305b79e30890e158ede03814d1a0dffcc3608fcb9926c5c65eb65a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000c7bf31a1f30f8e0de1a4a77b8b6c115d1a5d825b51875cba3db857a9cd2c589696ce2abe5a87acf8d6604c1f1f89ab70000000000000000000000000000000019ad7a6190a69fe1df07d55f8c792fc72cf2be11bbdd83c06325682bdfb5c31efef11fcb819d39f25bb1978570a250218c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab870000000000000000000000000000000012befada1cdf63d34ee2334ba2e42d7e69ffed71a39714e7ed89a86fd5cc1c65a01340c986abc37e7e3ac5a22a2bcc860000000000000000000000000000000006e5b16316867dc33a9770aa2283691f379581ff2b0b7986003174d4862d8b73bcc3f325c9a90097328f881b15f877c7f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff0000000000000000000000000000000008e763f110c9415b63baf27236f1c0975e7bebc04bdaf47ea0d3a2709a455ea48ffefb7551a73c9d599bc5c9fbbca78f000000000000000000000000000000001492e70f2831c87222f7d7a9d00842870b77aa68e87b8cdc9d8ba61f86adce6ea514bf5b8f9d66937b1b640c43b02fac22d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000001685898af1ad3bfd350980872e6438048f6cb37398ceab33d7bae1d621b5b2859e6a07b4e4db891af37e29881cf573ad000000000000000000000000000000001084663fadcf81b9818c999c26a84c6f9a3a1f71a0a2982b5c6d01c56c2974656c08e4ba7833d1ef8bcf9af53d2f0732e816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000013b077eb9130821bcecfe9b366c7a14f4487121095d325e74de44ea206078a6b1ac7d29a4e80f75c7714b6053cf2995a000000000000000000000000000000000b825b95b52382195416477f0bce73f06167db02bbcb91944e9e7534f804973bb363adca8b5ad80e77b70f4f1b9654d004f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea00000000000000000000000000000000014c48b3b2fb994920957b046643bfff19533dbe533df980dc60d9c852a3d07b8cf67454820a89ec9c7ea73a209f911ef0000000000000000000000000000000019b19e64d977d40b95050e4af365541b6c815534dc4abba7ea0af4b0a7e6bff0495fbb347250f5b5a48020ac20ea61cb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d20000000000000000000000000000000017fe39b732e6b815bde4078cba9f926e117349e3e49fcfb6308a0a09296fa27da4580d8fd18b0ecfd0ca68312cc0e5c10000000000000000000000000000000018a4eda1862c5c296de2eea0e720ba13f8a60defc65870f0112ab394e8160d6e1a0beff5db8c450d8770792b7efcccba05edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000e65750f3b9690f25b5bf80de0d76da21752a0daa8ce01b2bd8d172577f6c7d46c119ed20e73617ea163575705343c4c0000000000000000000000000000000019d0f934decb53a477b37d894d6e651a8a4f25b9375bac6b6d3483ee8d85f56b8374bacf74bb8550bd26b3d326962666f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b000000000000000000000000000000000907fe95f32e22ed75f94d96c191bcb19f88355bb84f91a8a535441da04dc211376435ccc60ad2089835b51e79f24b5900000000000000000000000000000000071e35d64ffa38024f4ccf7c4a713e22d8fb4b8450ba7b05ec5e759c2f8ea30e7d9e71ec2c90b8c667370131de785116059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b419000000000000000000000000000000000ccc90617f386ee2a76da43a745972066955c8e346d3de214834ea79423e7d95a008a6c119d640491d515b801034452f0000000000000000000000000000000002588711ccd23b65cf2f63b2d602b1d7dbf97cdbdb159e02e3bdf84fa65685e14d4832cde3662950a7fcfd11e68ad40a47b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface0000000000000000000000000000000003b5acf5f4e39fcb32a267034c5e905eb3df32f2f6f7150d94cd17bf16e3a9fff9dfdf75a966040a6af5a623787a40170000000000000000000000000000000018e4b8d163e5176bc9a45da14fabbac696ae6870717bf5f6c00b5c73dadefbe329d86a761935b18e81d65ab6c48e241567d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c,000000000000000000000000000000000905fd0b04e8db7657431b5f69f1d896e79ecee300cd67ea8fbedcf51b0389506da0669c28ac8158d5357a61fbc3976a0000000000000000000000000000000003235ff6d1acbceb35cd29c5fe524a452064a6e51d1480ce1e57c538b2ab6ec4f98c3bac676817e25e2d92e701ba881b +000000000000000000000000000000000688d6eaa2964e33cebae16623e228256937ce9a7721c4fbc85233ffb3edad5d6349d9c8a00c16faa0efd9c54827f46a0000000000000000000000000000000019fa249ce7be07208cdac9f9927163bb1b79b40b320623fc1a08a299d5500cacdc55386ce451173f683a9ce3f006c1e4aaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c00000000000000000000000000000000078eef7d7951f257b17c579fec05f3efe332534b2f56a953a701a8b92664b9a0b37959f7c3dbd77ac18a5e72d174b9f20000000000000000000000000000000017cb59169aee6caa1dbc3c47c29f977a44a81d33f1cd298d5df3e9469c8543d919b985e1b588a96a9268cef03876effbdb48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f200000000000000000000000000000000025bae0252e5d83a3b76f2a861ebb1312bd344e3eaaed5e7169de248137a929ab94156be11e9b16ff312180d856d93900000000000000000000000000000000013c207c57a4876f6bd6e8e87eed0021d5e6b2aa3b2a323572fc2ad521e807c366fe31ec285c8412f89328cdf09dcbc99ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f4000000000000000000000000000000000ea93e5fe055ed1ce77de5d298fafdc4201418489b64d10c447de3973c1b98c184c0cae1d95831742f3d50613c5cd8c40000000000000000000000000000000004f2f3d0a5caac826632ee95dd1aafe181976552abdcc7db737f5693f3d08d3c4a85365e05e369365a37ef1b3df5cbca36d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f460000000000000000000000000000000004c8b69c09f67ad17e8fb9fea4b7532c7c5bf3edb7669e26eea4f9c8f0bc10b0b1895acdee731da5999318d83095ef5900000000000000000000000000000000054f950a1ae65dfcd40eca15e5fbae984e7672a23ec030eea0cbc0424cc8073186b8442e0d71d6a4a77cee37c1108f941a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000b69b6b7b6cb1569ccbcea029dc71560d54b8bb88bd33af1c12a09d867fbeada2e58585385f1fe508a0dcdf8d2143f71000000000000000000000000000000000277561e6ac810ddf4c46288a065e5441ae0fe2d7ee79ebd6cea8712281a36f812c0bf49c21beb63a1f5cb670dd37d03ad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e9000000000000000000000000000000000c0f4206d4cd564be1efcbdf57f99ce43b97d3e170017fe352ed3ec60862f87730d4d9d9d56ea0aac4f586d2f1786df900000000000000000000000000000000073202e8c73d14469d15a392589db79f3897b72bdb2b788da9012c7aaa167a157f85f3431161d35f45bdfe0f2255b6378fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000191cae6012ca07ddf511ed586ef19e9f0d913d081cd752f033c9f74c334c6f5d075b4f6ec85467caea7836f51d0159af0000000000000000000000000000000016e65314e34e1c7ad577a36eff992abe6f26fc5349d12db12394bac648cbc1452cc366aff69e8cc4e2e5bc85db237a863023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e0000000000000000000000000000000000bf583ae5e3a7827610d91c0d2433c8d358fbc12c016c59be8454c039197971f90191737993bfd08aa96d7838b7ce6dc00000000000000000000000000000000046fc386c5b456bafe03fc84b4f98939f9c736ac74cac507ea036d2443066090118138547766f637537425f64be9691b8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000000f08b3868ea056ff8e82fb7e22a6522985e92df1df9db77f787bcb3ed701bf8c90badcfd94e9d3e3b3b68ec497b9fcc700000000000000000000000000000000002e6f5e9eb44fcc7aa96a43856a707f5a82cb4c14c99b21df09e666d4802d15fb50d535184b63ae246d4ad77b6c4851415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c000000000000000000000000000000000167e13cc54e9e9866bddff0c37e942ef8393a588ed3c2e90da12d0a8360edd6c3980bde808ff16588a57100d1a8898fd0000000000000000000000000000000014b21a7a106640b55cfeb19d3c23aabcf1c0be78fa554613e68404978b78e5d34b6b6378c2e87d0b8bf1cf3444d0db31ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a9740000000000000000000000000000000002e8053215ae6894e8df09394353fe98b38fe4b17b9f20c7b48c4baad91519587f63b863e4de79be71672e1fb00d337a000000000000000000000000000000000c2ef9251a148f1ba8cd75a60ee18ba6328e1c3a6780c790cba3bc91a2145f44cb8bda5257c03890d5c5674e4d09296d039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff3000000000000000000000000000000000aa7fdd550eabb1b734db00400304be9663c008d322d67fc771a85991bca6413ec07ab3adc3cb40d390fd41021434b97000000000000000000000000000000001994d9be11443f0a95a2ba4f7240a9dbaaffbc70256aebc0f10c322fc5b120feb2cd8492d02c60578f8becd7a8e589c92c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd05550000000000000000000000000000000012b3574c35288c63930be8024afcc91194b30d2b486edae832dcb34778886af5816f7478df166f0a7e4752d8c12423e30000000000000000000000000000000012cd382d17ea10ad3fbfb40fdf4f3814a19384e302542a0f5731920443e4498a1f8f4d89086764beff079583a672b93bab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000117294ca9961249be6570ea760bb1e562cbd587f78be482263e4228171d9ee3d970b234455912299933689096f4afbd000000000000000000000000000000000029f88a99c750a388eca5dc6939082280ddefbf7d23997cca3653aaaa03a3ee4677fa8291641ad1f46fee0f8f1268140297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000001006f64c279f074bf036897ded9deaf9b4ca380a9a7542490be675355c3979b2925be09ac4613fd6b7a4a8bb9e357f70000000000000000000000000000000001537e170e8dd88a92a6bfedcef69bb370f7bc1f32c36d203f5b6859be9b60fcb4d1e3948687ac7791d867e7c200967eea87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99,000000000000000000000000000000000603f6b2d8b806c5e399c686878eba299131204084e2c31c38640d757e8a6e5b318da785d54ec8335398610e9e3956280000000000000000000000000000000002abafc5839180e8aff2bbac4db043e8839ea25d8fcb7f6faba2a1c0a567863f256f820e846e49b3296a754650ca9b4e +000000000000000000000000000000000a2eba2e26da82458a494738fcc816405760f4991616d729415ee502d13951c319be796cf35d88a8e00e17fa3c58126900000000000000000000000000000000117f6b75a6e25a786e860df05505f8e107b23c6f4338b2f87ac8740554304046f7cbb43f2193da35350e5fb39077ff3f9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000000f6e3064df312fc97c4f30d3cab398f7921453b933d428a4162a37af5ea27c79d5b21d1d305a9609c994e61e56db226a0000000000000000000000000000000011edcb47b9d5339d08f24be87e52eabbdf701ab15f7799a5ae26cfca9d49e0e9107d9d1f09c711039d096a5745b89c9164be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a6769000000000000000000000000000000000d6bf9e905e907ed86f5d3a4cdf61c527ef43ea0befcf6bb7eb1bb790b3dbdb83e0b958836669827251da94db1d07c420000000000000000000000000000000007f85bbbc54af3eb9e1c7e4c4700b4c784b8d2e6b2ff6a981a534317766790942898b4eabbb8d6c893180a436faf88870c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da000000000000000000000000000000000b17bd374136dc1717cff915f7c898e049e892ced4ba57a16752a6dd875cf1cf9a2005dec3e3bc6f87b7a257d5ce7ca6000000000000000000000000000000000874999db06d15bd4b2f60e9b61d195747d12f38b75b74f3089d5b47735e9dcf79ebce22505399e16492c4a6e0f83abba2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a00000000000000000000000000000000179108aa8a7d8443f69b7c906f9a4869ff4c724aaac4fccb5f52cddec86e32180b3ab2f66ba76d57f69416b70334a0f80000000000000000000000000000000007f83a847f4c7e7b35fd091249120bc59719ede5b6db083b33f5ea6249f9e13457511db006f416e0fb9614b8d22d51e1e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c589000000000000000000000000000000000154b40b3bcd0ef04a5e1a550215c238adf07f92757c227e4d32e42893ee8e7e4fa9d7169005220d89b11253cffbdbd10000000000000000000000000000000018daff3cf04f648e59d00df4b86d8ea5dc74adbbc6fe4f080ea7a84dc6443d8923517a11f264f700e209af9bc52f759c76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000163cb54e83a9935be82161939360356f7f0cd0219f446fd243d05f6333c68a1aca8f5d2dfa2b54dbc07f81f756ed6bd7000000000000000000000000000000001667e7a040817e83896d62adfc4a9f3d329e87f7d598217c7d2195c5b0c3eb58047d4b9bb640e3959f7ad1242e10783f7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d000000000000000000000000000000000222ed79e925d64fb58bf0cf105a2087c538c9538070bd742f7acf5e00ab371766d286fbccb3e708bda2d227523a40cc00000000000000000000000000000000126a9569e9ba97e5c41cf11af3a601560d037f1594f2e352ac86c744542618e9d2b6def0c7d3bb6a3707b80cdcb60f15d35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f0000000000000000000000000000000003786245c244c9508ba94e994dd510a7485f4aed711c75a2f509cf01b784eb12ce2f3907156aa15675e36b4b2587e9770000000000000000000000000000000018de0e75256cfcfa2df959f1491d87dd5414a1b51b6ff02ed5034394ea636fd0bc5d3b3a3b84fa7156ca7f97aa65feea3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e86800000000000000000000000000000000026828a6409635184cb929a5b3fbb881ef013e8342cc9b5123ac82e7ce24fe7aa6a507ec3c017bba10126ad9bab5e63800000000000000000000000000000000132cf4a23eac460fb1a3db9aa43b542ae55d19f6bb2f408c399a570c1e479c4dd0462f9573c95c953bee07a51c543c4e10325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c1138200000000000000000000000000000000035220c800af6a330df6b6b6cbde47abef2e5fafedbb7a0feb84a317ca3cdb79eed934847694e85e2873ef97b31b6ba10000000000000000000000000000000011edd4c17352914beccd8c062aa7b95b913f35892c7cc5dd8f736a31a33d33a98d8f9b4be97ffe608531eb7c9643f32109545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb30000000000000000000000000000000012148b58f805c38bb862dd9847f12aad21d1ed760a022d2f619a0a077a0bd79fbbd6c066f0f6c58517ee9e912c60a37d0000000000000000000000000000000018dd847881616f7410f29d4e68854ded4e97b31d5112fd46437739ed62e6d78fab89b078581d052266b7c2ce403d3a79eef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c81000000000000000000000000000000000e36ce625adc496ac94b53552effd651a73ed0c69abedda36e88d408ca7bee73777fd87b4f55e2e8b567c2fddbcff3d50000000000000000000000000000000008a209510caa720f20cecdfc9b0bd71d3fd4015627d0227a027aeb9992ec8030056a5046feadaf149d2392fc98fd60bfc8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e8000000000000000000000000000000001373edf053517ee79eccbf02cce4b4b67d6efc53917b7cd548379c3f78b447ae5dc331285a28bc2aa5863befe2d26f4b000000000000000000000000000000000fce7f982bb8e937802fef7b3fac517054e6c9b288b03ad6497734d78d4b9074e22b1acef45938a08440948dd8b88683a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000001ac8ab3b3918836a5ba14e3d7c44eb8a0d909dbfaa2772cb9d7f8f517963662b5d4209e9a5d44ca0ed897412792792800000000000000000000000000000000169f8127198935f06d26ad8e4ca3ae5b95ad967aac69f7958fe9fb9c5b1f0e98e596fb73a0d8bf90174ca21a02a3e2c2dcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a000000000000000000000000000000000c1cfb4660400ad5d7ba2f394cefa878c6a8fc214823dab539c0aa6d08f36ff1bd706be273f25ec5f1abfb06bb57e8160000000000000000000000000000000012ff9bad1a1d71fc49e96950c74d388229d4e4c68f7fcfafa42329ae06d4dd3091b5b1c95f6498743393b6e3ee794e4ea6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce,000000000000000000000000000000000630b9d9da596e60b15251aa74de850ee74c7804414e3e4d7113cb3d5ad6b985e436aa67bed66c36f135050d66f91f75000000000000000000000000000000000ab084fa126c573ed007f983e01737453b3dcc99ead0a74cc0e8d7cdad89ce81489384b311b7ec4c34736e9520b37e1e +0000000000000000000000000000000015fec44912af2bcd34f1ad42ed24b6ce430f6d07b311d65ffd8b6d726ca23f5bc4b7437d158a36bd1790e806fd5ab448000000000000000000000000000000000c4a4de9940c7c26999773a396a8f9a6ff4b86f0525189426529d9cca037d504385dcbf1c89eefb5ae2cbbb394be42fc92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b7000000000000000000000000000000000fa1258cb0d8a37009e8c56228bbd11aa854a4695bfe96ce205efc1c9f32bff8afb64df0fb7863512ff8db6b091f146200000000000000000000000000000000188f128e662e8d28be612c8a17cfbf28b965340487df40bd3f0312187d027cd23b50e713e21f8595bc790ab8011919cfc193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000000c7b39bc2477597e37910b1888ba0afe5ed03e809618bca0e543add93519909b6cdd6281e2afa65a9b45627dc1c6334a000000000000000000000000000000001335cbe866b3139dbe22266c4ed5f9fdbc15a1b338a290a590c03811b6448244027c12d118e6f829dcd352a419bdd8283dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae600000000000000000000000000000000178ea2552d03f645fc3060a61b35af6e3e12095ec65b2e9972a5e346ac1019593298925a887e59a94af2adfac7a8361d0000000000000000000000000000000013996dc427ba51c4ec1f67b30c95659f35c8e71a225bf357f636fbfb428140f9b9e5602eda78bb38e87e3ab77495e505e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000013555f26c2e10b79f8f2a4c397dfda0d8839a35a7cc15673ee5da34578f3fc4d38bd0331a5c42665bf40fb2cf693f31e000000000000000000000000000000000bb16b5b1dacac465a751a68b99def392a69a293377a22194fa4d4d6662b912d3ad804cbe51a4ec4792229de57923ea14dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d7200000000000000000000000000000000120183e73d23355da316783eb47ca687ecd34d85e800aa65d2c95aa5f8eb730a33d3273307cc05d81fdafcee5138a080000000000000000000000000000000000171f5e63fd3c71200720cba782ab863ace945cf405a2f961baf39ffab2d3283c26347ba297d16c3f2567814c6f9914e795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a956800000000000000000000000000000000095ae1795306c8a8c48730987a842a05fcb263d1f9ea49d3f3c0ae70c7ff636fa4e7fa33a35637059c0b11b1b1adc6e000000000000000000000000000000000185e08447394763607d6efd8660118429469a1f6e7edd03a7a3e12ef99c2a15670d1f7ca664a8a14f52814db9810ea2b25b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f08380000000000000000000000000000000012b0afa7f55ff9131a9399cdf0fbf2da69dae7cd504a0160665f0cd74a02163b8ad7ab05cebf3195495a1637134cee450000000000000000000000000000000002a130747763c25b9b6c0436390da91f02c9d5b24178318717024390a841baadae6a9f933e7f87f7965fc96bb498ade5df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e0000000000000000000000000000000018911ed6adc5f48db7221656c622c6cb981b1ac1bffd64e30662035c0daf4bc5accbd53cdb1fe8eb60628262584de15a000000000000000000000000000000000b753d21d823d1050f109683c7c153514dd06663ed0ce118e388d18d36686e94588159e5afbeaa492d021a700caf2dfa959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000001107292ce4d57209e9c1e2c396688ccbe005699de4e77b1a221f9004585ae6cf8f901da6811ad85a88cd85cb819d040a0000000000000000000000000000000012cbe9c273a8a9c1404abe51af4a647f6c89e7e177efc04233586d70df6dad3aacc9ce2a9fbdcf2ee5c73396fe4e498d26ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d7000000000000000000000000000000000297e52ddc42a7da1025d43f46df11009ee035a9ac45e09a0902ba86fcfc5a4bb4c35ae8b0e0c9b86a8ed7e5ab751947000000000000000000000000000000000319c082c39ce4e59b952941dd7d14f3fec39a9eaccdf7bb41a2b935f876ebbb6778c90e1919c1e5804df91abd3bd9d5f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c2000000000000000000000000000000000ea5a9d96509cc5675e165e3a7c9f99a8c6b7be9c33fe5fba895a2d96a68e922271c90badf3c41b3ff52f359f5c6dae300000000000000000000000000000000106614bf5ae42409881f4889a82c6a3bc8000bcdec23b093ebf29b24cad128aaa7aa17566c4293f67af010e9b5950028b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb300000000000000000000000000000000145447f37207ac8d58c706af0b900dfc1f2638f840a0b44fa65245b5e671ffc6c008951ee17217e010ea6cd5e8477d4900000000000000000000000000000000187c607539f8d2b6afd15efa353e2fd1580cee48c469992785f02b3ea3396db5359e0d6743ff8d41648fd8680a4a8c2bad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c10000000000000000000000000000000012a022fc2dd9c201e9d86a0983fed4a71abd086068b8ab8c9586cf51230acafb084d559239d86a3713aef4b87a04c09b0000000000000000000000000000000017e02d69776c705bdeb9fe06d412a67601c6763a19c840f15f96de0fecf782e3a44118def54286cd52227361f0db3bf93c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e09d94291ce5e8310871aad89e0744e6b319b4fb1089048b0181cb9e885aec881fb7577fe0e80222793068deed473560000000000000000000000000000000017c8676e4b8216a98d9e9a05891ccb74e64d72a5ae76dba1b5ab2d1c4eb8291cdefe7753abc5fa59efc4a4834f815488ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff70000000000000000000000000000000008902b3f9b3ed6f0dba21e5d6bfc13fac8f003b3e11de4b883024c3eca0d2c4614604d598d31d9e328c7ee4a9d9be6100000000000000000000000000000000017a918bcd38986300bbc7a401e09b9ae20ccd382280b4e79294b6c8ae7bb1dbe2f72a582e0125381ef2b4fe24998e72f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af,0000000000000000000000000000000015e9fb1d1a586288f07f399b87c37a085df405bcf88505a7d2b0ae6609d4baef7ec358f70edf838d3bb7291c6e5a413c000000000000000000000000000000000cc7d7e2d372183766a842f5c14c1f2a528d502f1bc5dbf5dfc9d812c56503a0b7cf1e6f052e998aaf45cfe24a261551 +0000000000000000000000000000000002cdb1466c13290ff0c55c38ca6afe33efdcd09ddbdf7461d6bdb3e36fb5d8be851458620a0bf54932c4ddc1778c97bd0000000000000000000000000000000012755c81c142c5051ec64de7c89719cb59d9003fd8785ed5b36993123418e49cd3afab18b599deb72c969936633a956114d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d9800000000000000000000000000000000067b3a33aaf3fc4b885035e60ba7f3afc7ccfff469cde1a67f48fd8cdf4b15b7beb9e2fbb13daa9283598aaeaff5014d000000000000000000000000000000000bf43cb79d63db544b2db14ec18c11bb9114db93662e8e6e7858d3e4a99cc332890ce90775b6c190d5ed418571fb907d82cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa86260000000000000000000000000000000019329a66132ba7ceaf5c030fb4ae9a599895aab7df2a27fd92b55e3a52b99ac51107e798175f2af83991eb63147901d30000000000000000000000000000000005c71bf6552c314dda4bf9f2b4fd8aa368c9e88c0cbf4b1c2bef9137d608738636f40579a360bcaee1a3f12274687063ea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f9824900000000000000000000000000000000048a258134ed95f91070684d04b83634c2d4c16601ad259d41e7d27292897a4d4ac76eb73425583ab1718b91f151019e0000000000000000000000000000000013a0b600765fb760919bf273a7b88bba568350ef82fc382babafd40a7e006e6808a03160f3747878368d8f6b31c619b1e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d00000000000000000000000000000000069d94fe286a9d39b64756e79669add0f66db69ead7db5b5c2fa1a9e5338aaa9051457a3a744c3b08d3afec8b87d2e9b00000000000000000000000000000000105028835bbeff46cb7d9be4b21f07670dc5589603d0d695355591ef5f7ba28c04c8e6dc40f0bdda031bb54a5710b4c0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc0000000000000000000000000000000015e6aab154e33627f92560e3def26d936a8876c52490732c807749cc28e34cb98fe8f86addb30e129f8149c504d1dcaa0000000000000000000000000000000005f6040a129df2340f3c3fd0935c02cfe162fe1afb58dba7699e7e08851b3a3c3fba36745bbc769aaf01a4f9a401d038eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000db5640083674fc75c0b0d1b2d6eb2b03cafa2e63d7a65c894d9a76b196d92916ce85c708c6c451aad65e0b439033d9b000000000000000000000000000000000ac8d6b508ff6797668ded6ceba4680443516d601a155cff48a51297e321417bbffa6eee042255e9ec054d837bffe628acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a90000000000000000000000000000000011daaf23ab5fc0ad7abbe7d5f1dc26c8ce388491cc049f01f287eb9b133e52f33d40f8693921d330ae57853539ee30c20000000000000000000000000000000017594ae7ac7f6e4f02df862b6d4ff946ac1a47085b554ebaa720ad3291f576ba720dd455829600f930e3964a44e5c7f30cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d200000000000000000000000000000000174b5b9d4ef01fc9d0f05a03612210690d7d57ccb772aa53175f11b9623388de8019ff2ae1d564e7b30ee06bafc37a84000000000000000000000000000000000e4c03b8dc45b0567e9ddaa0a085d169799d2a595c03f2ac679fd858cd59341393e6a0f62dfac0e53598af4758843673b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a054400000000000000000000000000000000197f0ad6576bdddb48c58adb1c9b2115cd9b38368dacbea9220d6a86bb621dba93325b676071e38aed2338273c98c4100000000000000000000000000000000011514f08bb28c37f078a47b6a0d53b311d5975c8a3c8e2c24a25f34bfdcbea53bcfa14b7f23adeb20bf440c87a251a66825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a3000000000000000000000000000000000aa5f3a29c47fed2e4a87bb4c2a46a5a17102535aba9426235d42f00007e35d1c902b43c1068af279cc9a1b689a0dadb00000000000000000000000000000000056d9729f8faa8e12027b993e8dc41a340d61c64e4388c3166482ddecbef8d04085d6ae3764f0d9cfe76288929749235307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd00000000000000000000000000000000070e376dd57cc8e2146d49ff08c6c6ada6302c36c4eefc3003f0cc3d75040d73599c7e0c2fb9f7e24484c37262f0eb330000000000000000000000000000000016a272b79edcb7e7fa92400bd55fc937d6389f1f0d3d2168656815845d92ab1e7b555fd4ea311802a62cb6c94bdc5d58bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d000000000000000000000000000000000bba6375b28ead3d49197ec9d3662e34c70735ed0f987f05f439da164afcbe98f25d2ce7a5e1e32515eaa4cb7f5a1f98000000000000000000000000000000000b1ec74ff999ac5a7a3ff2c91e93e5f0edf5f296b063d80bca22fa64198a798fa6b6385d25cde65b789454bc2674231058aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000012fadbd9c50f2e8518dc15d95a59ccec0c9886488ed4601b3fddb2bddd77a4bc861f2862c9c4666622e42a5dda7138ad000000000000000000000000000000000b2aa31218a13b4ab0b00d1b76a9ac7bb3d7e6473a29f2f0d137ca63bf7f152954e52182d32d3de31df0e6ef0d102c9e6810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff000000000000000000000000000000000076846443079520c5b1600d5faa5a6d500998ae355c84b9393c79f83f1a2485b1809058bc53cf5f8a1a46bde6cf2e300000000000000000000000000000000012027dd1a4fbf6078b70c507fc2cdc0fefc9a0166694c796eb26e9838195e68fc76297e66e2a0e9e069274d110efb095c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d4700000000000000000000000000000000183c019c306c08401b4f2c1d852b29dc47b56bce8cddfdb66d4e3d5385e4bc75bb9806da1eab476ee02e25ca2b4d41c900000000000000000000000000000000066d56711b80dc8725e112e4e2af6c939977aa66c931c6febb21735d78f5afca4bbaddd77387e52dd5bc9c29cf26923613a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc,000000000000000000000000000000000f75ea9863e14f6151c452f7a4268b099f97d076b249d02d72caf5d52635bca3c686070d4a4bf997b753c283c82cec600000000000000000000000000000000014402b3e738bee3bda4e62c077e9d355ad8a71b0830ec0e67a4fe6dc59b1f3f7809ca7d07184f53c5afed32db327598d +00000000000000000000000000000000107072809eaa84dfeba5a95f94aecc2c985c9e5dbc49a741811fe4b2393ba7f6597ac99d8e80c0fbf715a164099e9d5100000000000000000000000000000000124d1694bad88200cde42f1e7721f3390df8dbe4745715a2f0b6f11cfc78996c6f342693acefe88b3d83736cac6e3e05dedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d200000000000000000000000000000000188a853e19d512149800bb0aabcec450561e5ad08b5159e0879422cca1f957ee15bad2b881979d7c8551eb19693bddf3000000000000000000000000000000000dc097932535d21656842615f08e7016f55752556da3be69027d0dea621ef46cc65e335873e041a3dee6c7e5b7589dd5db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d3a53b9865082b23226042f69ca71b99978fd6dc3c8553e33ddb12542d05b026345a23c2b24dbd934be2ba3cd585162000000000000000000000000000000000b0832950405431722c23cc78bf0b9f33c6e2dfecf10e6d503c8c96ca9732c7e7a29251fa5b5b161d14b7155a50846886e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca40000000000000000000000000000000014e19a8a203bd2e9e9601cf6feeac5699a3b2d2129b6e756b9b5a7af0cd9228083de8c9a2a0ebacd636ab1b662c8c0c7000000000000000000000000000000000faf049bd6532cdad26403b269d7dbdcab6c147ce0ddd6285ad9ae0e8ddab4b6706bbf038fddd7f63e6dc9a766928ec327a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000007e2e69d6c96b1841340c48e8ab070c67054b574bd5778a8e38a9873241baf8b85deb73695873fdd9e3387fb1fec3b6b000000000000000000000000000000000fd151202c399636a360cc014c90caabaf3b01d5a6114e078eb2473bc2fff94f1c24597e39a3d2298a2e9210726bb48e446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000000ad0e842dd19673bfb8534ee20591a9076268eb203940212f702131fc6a3e7b226a84324954eb4bcfa8a007669d2317a000000000000000000000000000000000693801615c5282a327ae034c3a1480de0e1c471a412f194178a59582509ac6fe8ea22c8ec8e98034348ac465527f4b35f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f000000000000000000000000000000000ac9f4f22670b52e0e85a37bcdd729b40c45fcbd6e8aa78626752d736771ede9c570991e347134f95385bd77e404e4700000000000000000000000000000000005964a351f406083b14726ced542fc6d95dcb8bccbd41aa3ca9cf0395d8d29143b897c66c78e2fe56eedf17d4d6f6c1f94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000018e270b6208be13c23cabf52e31a156209abcd7bab03694fcb7035b453bce8464fa1e090d59a1139fe451d8c699669c800000000000000000000000000000000158dcfe7736f4fc63071a70923d81db9f7d2a03512724dc41ca47a873132da66eb0eda58134312fdaa63ecba7ab529acb677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000000a614d7a53b7a06e71aea4014f9b951bc19747cd8822da50f7993c0821e05100dc5fc8d043b2cbe7cc4dcae9837679d0000000000000000000000000000000004e0495281282aeeea480fa47f53f8b521a7df4c5619d4e58f730fe346a6deb3d501ec8b55b581489f28b4d991ebd90cf5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000000edd664ad8b77d86bda4ba772f677d34c9341ce2b4d2af4b2680383bce0fd4468e936841dd57753d06c50a3357a47eea00000000000000000000000000000000063eacafb540655984104f60569720625e4499f048ec7849577caf240634ffc42612ca7ca92c17e3e50aa627059cddf2fa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000a9d9bea7d8a058cf254d2b7e10f6d2e8244cf131c6f87c4e25b5febcac352d02b1b45ba347e0b891c8b08e7b5dec82d0000000000000000000000000000000001d256cedcde615d01e15cf526c4a8bc8b565055567aa1de1847b524fa49b4b9f654f5b66cda0a78f414848aab42b05c93d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000001519b05b59250c72c9db7f425954694b29b36af21d9293a36d7bcd1ffb53d0ec55a3ceb7980580ce6f9fb6a0faa7bf3f0000000000000000000000000000000009e7d045b69e2dccad22dac427f5938974a6394c9fef84633fb5f90a0d09d437219f1b7ef7e7bb03eed106948eeb560d3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d060000000000000000000000000000000017cac7707469b98c6b4d24fecf6d818dce6c8b9eb44bb08d6e475e385c30fafc81551e74ee98cc854d38d77d15459e750000000000000000000000000000000019d5bea3e48fa7bd273233bd6325bbe38267e4950dca4fd9ad051f487e7933a366469107258d69f0603b2f9a8dea2e4f5143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000b993d9303ecc19122654d5cb10d488af5411c451b39b1e19e7a104477da50324472076c55c4557576a9e5d7755a381900000000000000000000000000000000172b34e576f0539e32c5025b3a8f25b5bf407f3f3dda863b194a9fd97d3a6facc00902c95fe076b91713bec162f61cbf0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000170d799ffc4c0abf6c582b41732308665d790900ef07a74183826e48c9f0fc500b09109b2b13b2b33cc17e6e639d2969000000000000000000000000000000001943fe62329fcb67a45b5155da7f950ee12fcfe0e8e9ee15868409ae44adaa5f03c330206d7d97fa733c9e93957755a0b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b00000000000000000000000000000000078681739039a022499219b298799027a341be64204a34a97a8115e5e10486420c18664825b764fd7bb931343c2558a60000000000000000000000000000000003313d3482f952c6f9cd4ec2f2b61f28ecf7d8cc7e60f17e9aac8e63ab25dd6bf2da2d67805debce0dad8fe37a36625298df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af,0000000000000000000000000000000010aaf24dce0b05179769031ab81db339cda85cf68963640b888d3aca310a6de690150166c0943d658e51524981a1f391000000000000000000000000000000000d1af37c2bdca5886d73567cb00d5a9859755267800d7239cf39c291ba83b1c67e6a3532a2d8e8590c1bf2d845001038 +0000000000000000000000000000000019401d9118a5c2b0c6ae40507cae6180083258eb6c45cb8bf2fd5d2703c95fb07c031c82d0568a395e18015fe0a48a2b000000000000000000000000000000000511b992882f75fe98131fd35276b7a1de527b94718549bb4f5c9980917b6301a86e45fb7c7e3ea99e54158e49c7e60ee49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c89000000000000000000000000000000000d886eedf2a2b33a50dae5ca6f41237c9425b0a4daf08bf4789a3ea8c7f598d53257916d9c03df0d63f12a1a804fe0990000000000000000000000000000000012cb777812e76378f04fdaf2cea12456aa9e11b4c3ab0f12e63fe7ab11c716562b07b3864cb9dabb7970c81bc1da324c79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f39530000000000000000000000000000000009f09107ccfc5c4ac9b7e0058d6a0c4d7dc4309134d5fb972de3156a554211d4a2fbe639bb8a93d86091137671ab8447000000000000000000000000000000000b7f9955092221c8a2f09c6a9ffe6483ec0f8a0f6c555ec1772c260fb62c4ada6dc7beb92e29620afd15466b5f025cbed2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b0000000000000000000000000000000009a22492a1b78342b919f7b5c8fcdddac408cdd3e8af4d6de5a4b1e510fa3b7e0e6887bcbe074fa839f2d0dc892db631000000000000000000000000000000000e5eac3a77c7a3e89e9324abcc0203046948f3d62e40156a5e1b1d9a274d408d6cb49e06b8cfcd21b596923f86c02c6be9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d30000000000000000000000000000000019914b405a24e72896b3d231582f0075fa7e59b0d0bc796d790754902238943ba634dce66131260efbb5dfc3925a1d54000000000000000000000000000000000352a5a986c500e41d2fa4f65e5a917061b3f9449c1e720caac187c6bfd4ce14f1b49ad414864a1510894530cfb4a768c818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000019567f8de70c4cbbb25335e69154ce48d4106c8c9d0027e17c67777dedf758203b0a8fa3863d4e7812311f6cde36a6640000000000000000000000000000000009947f7401d03fa8b0801b130b43f729d6a71c04edfaf7b9d3265f82b039131fa09f20f9b4565d21939ab7dc7dd3477e9ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c496450000000000000000000000000000000003c42ae9653d1d1f00d79f8b1a0c53d0f2d7f3ca52ca1960a621fc1bead7ab31cf6e5bf30c5cf7877c83b33b6b5b54d6000000000000000000000000000000001221117f45dea3fa1f832bb8280512841ad1798b76f1dd16dc320ea7c86473f6f8c98ce007ebc3ebc39e7a860be987fe96acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000004340b7dbe7c27014add4ecbdf310de758ea5dd1100508a96501ae3caf9955c877113971a61f66e3691d09f0a259d4ac0000000000000000000000000000000001d5f83065f6d178b4dbbe0f00f0a88edf0a90021601bddc2cc27fb0ccccce7e48c6283a1e641408a20de15219b5553e60bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf0000000000000000000000000000000017679efa923688425fa9cff1f8e89ae681245371017f574f4a655aa780bd11009579d7daa47249f503592bf0ab79e67b0000000000000000000000000000000018f57a1ee533981c8df24895ad174228330ea361448ed63e522637df44cc1b888e969ee94d7b44bd532b655123f8f5d8537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa9830215000000000000000000000000000000000bf47631b34b2694ff7fc5d1e25de2195e606daafec34fc2c8ec86c0a325214d874002422810a81cff654eda187076eb000000000000000000000000000000000931c54d05eb43195c3ff6b396e324b5878c3fd507637c316c62b3b6e2d3d84cff9f33cd1046f1939187979330d3fc431a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a200000000000000000000000000000000138adb70a3dce09176914deb0be17821cd0212c6ab554f7e200804dcade06c6cb5f7b084a1d6ac0ef8eeabc7cabe7717000000000000000000000000000000000a4422c569aced58938abb7bdbdefdb27cb06677c1066d17f98a59f847928d1bf2343acf8b5d1717aa38cf81959ac1acc7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000fd9ff095adf9e3f666d3141717ac4a96deb5b4f92dcee35be1d305031d06d51ecabf863a41cfd8dcda0fc94ecf79982000000000000000000000000000000000fb55855aab9e557046ed53421cd3627b519859e26338328d7da249fdfa6a07fa533f748eb5dd564f9922ad911121b2784f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d000000000000000000000000000000001054ff028d2e2875330e3d0ffc52e2a83ee2ad2adf024ee294f695113d9d645f0be2a3d3c70f758f43f2deeb542aae810000000000000000000000000000000009a5e96cd08d3ee4e740e2f7b94a4e390ab5f6f572c4a1b2d927a7ef2365557ab9be65b8e2388fb571a3765892a96445c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea000000000000000000000000000000000bb1de70113edd86e5304248fa2f857f1620dc8a6bb28680f537e04029aee158e2ead4e0eaa373b812f6ca988dc40e7f0000000000000000000000000000000012118b670c9df77af087ad01e3b766d4a2b7c2b2a319cd733ed6c02ec36d9002036964fc442db992bf730c57a7d0a407a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000001968aed17e572c0d99e4e9262f239771976dcd9d7df19c20bfa94aefe1d4f3a3117bbfa4a6e329bc6b9552731446dd10000000000000000000000000000000004e64ce59b928e8cac2f744bef119018de8395b712013b0c69855fbf2bdc6a750a947b1a81c9df959c78367ed0e1575d95a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000148cd980512e0aa153adbdef262f098b1ece801ee4024b5561e261d39b495165851781d519d75f83dc5f298d40b4e9e0000000000000000000000000000000001dd43f37950976e50071226b6aa47c229085807ce9634e6583f5a2d47eb8547d4de0669b16a2771791c9ccdb4289cd9319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40,00000000000000000000000000000000059c7ca50efe2d0a64b55a560a08976b437c350860f9599fd30370d1cbbeacae147144c216cb3e63afb2ddcf34282a5f0000000000000000000000000000000018f42ef2fb8eb6cc8c31600a3be37ece06ee567ce0c8b8c7a54c910041b8c25b2d94a94722904de079737f06e817f380 +00000000000000000000000000000000172bbfff135f33357b0dd0e8545da99c4ee74d6414724c2aa66ffc85f3a9d0e35ac80850436745a12ca6f1c4ae5c0ecb00000000000000000000000000000000152dac882023cce1a3e1fd4d8d5aedcdf6acb2ca9628a94ce92a4a551b1b7268589b52b2c90af6e4be9631eebc2ef8a62a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000000c40d04b3002c21b041ea8b8ce967056435fadb479fe1fe20c373b2e2c5b568b7a38d031424bc835bdbd85af8ed1d0380000000000000000000000000000000005e7357194364947c8dc32fd74757a3f3014e914dc25f42b2dd86230ca4f86981476e6f10b1559694bc17d014cd243d7f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b000000000000000000000000000000001474002c92db026ff5bce69eddf1d8ff8e6d2ab9427bb82377911597fafa4d60256836c094cd513a52a3a09797afbf5300000000000000000000000000000000176a7b311a333c2d4f6eec66e8c889ecd7becca75fb35a38bcccae52f10ff69630393fb7d87c3b6d97cd648be099c56507f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e40000000000000000000000000000000009ecbdc4836c6c0cb4ccd014f9e582112bce0d0ab047115f38ed5dd51c54de5a43321e85c9b3e9af5fae0caaf2493fcf00000000000000000000000000000000034425e05f0adb1577f7b1bf9b9b50a76bc894f5ff0e9a8d190412eeeaf80d0bb96f21478fe8adea327f69c9137f57094e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb6050000000000000000000000000000000000ec3d4fe1b5e1c26de1558d7dc51eba3b6c37ec037de184e8a6f481ae20b830c92221593e1bbe4ee76a85cb10b33e18b000000000000000000000000000000000e51f811e16f00626d934e69024b55dc29fa4ea363916cd8f44f928fda6e3ca4947eb15de24ce1952c39e9ac52d2739d27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f2289000000000000000000000000000000001205dfd803ff3688c2023913aecb10c138be4d03756e2f05a63627973f511c2635571469d4f630758627f7977b418729000000000000000000000000000000001186b9c0d2b2073b495ef9c233c275922bdbf4691e8be085051c09e245242526b13b7051782a80726b381a72b5ef9d5ec2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc000000000000000000000000000000000413f6ee9bb25469af4298dde67f0a4a26d2f528848ac6646764703922c78d65e046204f891ac94b0b4c425110fe986e0000000000000000000000000000000011860881aa871fa3a6693b23fd7b1da0bcaaf044058ea0700b786f12f1074c615577e572e33faf8b3562bc285632696d911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000000010e8fe8fd7863c2807a4bd717fc4646a0e4f99598b9c6c2cf0547d039d58290a367e4ad851c7a67e8dd546d5e328200000000000000000000000000000000063ea10e84e4f5824ad7b9b68398c9154ab25ddc4043a4990d80e09dd94a890dbabf9c3d93b13c4f40bd7b1ff32b14b2fd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b0000000000000000000000000000000011cbc725705b809ad69c5ebb55ade0039989728e7103b684feb35c8142b100175235c2b395e37a20aa40845ebe2dabcf00000000000000000000000000000000057b5b5a5cf5f5bce985295f8a50252967aa54e934e87855097eb083a59863aba19ffcec4354a5a831b747175ba10e878118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c00000000000000000000000000000000041043cea626d6ab553b95c6e09de597454a3a3d1b8a75fc9ecb3afe15bdd8b5e73b8012ead8777df8957701fc9c9022000000000000000000000000000000000185da96dd1d54bb7ca5d7dc2fbe4cbd8ac95f06fe85a7a26e5e0e6353f6a6daf73b74117ee62be4f3fb268fb4c86275c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce773000000000000000000000000000000000a06b91559964aa8e8628946bfb720047915ddf08d24fa34f7b241e16bb163ef67f1e84fd205485d17725a8386a7016a000000000000000000000000000000000ec787cf5134bbd832d2a7dc1ed87b8c824552d92fdb30a790e1c73b22c753540a9747eecaf14dbf867d9667b7b852c7982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b00000000000000000000000000000000053bfa3bd311c1780afa1862de6ae8a475b8eb9c61fcee2b63dbb6556022d703bc7eb204fb038056c654dfb940e7039400000000000000000000000000000000074ab5797d3c39804dfd5359b69a4bdd2b738670d13662eb2c112eefbc0f90da85dd1a4b6e0613785fc66b100d129202000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000cd50ddae4f053bf5b7b3237701bdee2f5167e09d824d260e89ea498fb3b593e5053b781c159302b0433ead35f072c850000000000000000000000000000000001abe8539a4215a3b7b78c79c306dcef7334c83f571f4d6836e1c1839a65c8cfa9a0811395e3c4bea26b22ac2175757e773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb20000000000000000000000000000000001e6e0709869922c36e073fdf1404a973e0467cab3a04a806361e743d67468f0d66de28f6c0c7b8cf92954330485db0500000000000000000000000000000000084e96298cca174344b7b86052426f9316a15b4031b9e42677253fd9355b1c99ed9ca3eb3949005078ba228d4167f8b0759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e0000000000000000000000000000000002c5e399eab947a52660807752ca662212cf3a201c1127dab3586cae88f8ab6dd23deb0312387178e0e9526bc8fc7b8d000000000000000000000000000000000ad86b21dbf58098fc4f758d7ec9204bb16cbbe680b58fa42821456d4fa508e42b53c8988dc0d9a4d6f6a782a5fb90b6cce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f052530293800000000000000000000000000000000181f41dfee6effe70a28e4c53bb6cec52f232caee076f680fd63d73cae24b44709fc63ee3782a36278edcceeb7b32415000000000000000000000000000000000088d9011a9db9294bb4451e9981e84efa595462e26e5dbe14e9c84a8c5ddeca94f49857cf3b8a70e6a4047ad76d234585e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f,000000000000000000000000000000001170d9938910ce68e56f9e84be6f6ad284a244adbf26daf8d42f6411c9794b707454ec9300442fcb7a0f08eb91edf86800000000000000000000000000000000043679c7a917148d141a785e65b0a067e35a016c24bf7639ba374e21a817819ad67d317ee70852bd13a1fc3a373a38d2 +0000000000000000000000000000000008d9cb39df5b28781d33d996039da8c94cd810bb85aa5868008b4267ad2a8670924d4b3ad7898b33689aab2211bb9bdc00000000000000000000000000000000007a8a6f888722e4717acbfc42ef1907206db31603c403e0a8c1ac0af9b37e63124d4645a506265487e5f9eda09c8baf85431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c000000000000000000000000000000000db6af04eccb3ceedc11378406a26613aebbbc2201a9ea2089848c7af3b34e46a3421d5704242c4b333f72180f6baa0200000000000000000000000000000000105f40c8b702f0989a9e20f72ff6a4f7310d81787e87638c33a61985f02116e106218d64976d50bcc61cf5bcbbff7c9eb6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa000000000000000000000000000000000a77e39abdc9d64d72ea4b321e3310a145feaa5d342bc1a5b16c0143dd01caeda4f18909acccb3cb5b43ad999a94f91b0000000000000000000000000000000016fc4a4f6b488fd1f45a158d941d7aeb5d431821589ee845c64eb198ff10931d586f8a0678237be2a394f5976d895bc854dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000000d0ddfc05bc9f89eed488752d64698bf00633c83cc37931d95a599d6be6e4c5d611a4151839133e86f74bb91aed1703b000000000000000000000000000000000be3dbea501c822730ab0176f64903931aa46b0179c59556ee7e1ba54605ed8da2eafed7eb2254a7ddc34e553a9b6d59eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a0000000000000000000000000000000004d6f288744016f15b21da736283af2ed1f45df12067a3a70391f66fff3ce3953a51169eba6288cabd84ffe7f597c9fc000000000000000000000000000000000f6556a63def531a940269b073ea98be79558d832123dd681bb4446d4c11e2fed59a2f97904797abb07ba53e0d48e923b273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab850000000000000000000000000000000013e692a13e79c734f3758780fbdabff86fe5936bf6c60f2f155ec4d1c49cdefb97dc02c1f1e4280c5ebb055914d93f9d00000000000000000000000000000000060898a9365ae49697e5ac23e320261eda04d818c5f1153f647844b1910bb3430d3c06df9a64af8ff9dd25c18cbfa79d1342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f000000000000000000000000000000000a2a4d92ad63dade4d666ea949dd64d5886eaa3c7ce466677356ce9f65520591c1aab590b48e9fe1eaa0f0f3e306cefc0000000000000000000000000000000002a2bfc836409b33bbe078a5f89c5142411bde621e9117ddf9f81f37bd546c3e2ba94975ab4652fa0858d5a2361592715b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee8239812500000000000000000000000000000000075d3ebb18437feb21f94ad5e2ce96cbaea2f6d68885483ed54ee67f2dbcf8cfa39f405afb46e45d08cb804a7aee3b8e000000000000000000000000000000000d42851366ed4694730b7c58450c3f9ebd365f15fa4dfa3fd226d180aaa921a0d897278506ede76b85decddc9580a365249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d17000000000000000000000000000000001722e05d33728260ebf5e4b48104cb2c89b4bc3073767e56fda373bc0e29261c9a5c53e5768b453b116494c1109cba2000000000000000000000000000000000030e4da8620007236b89103b215e54751ba2f2dce19b0304997f450791880ad34f3e43cc4e6852aa599fd65ef72dd9a5c014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e895000000000000000000000000000000000c4e7e44e8e0387bd99311343d2ff3a080ddad557c8639aad64c4f6e47d64f48b91f9de2e33b4b9c182a87efce5d4e0e000000000000000000000000000000000e7cb49fd7aca3daef3c0329c950c832e1d007f21a4f950f367eb37b5d7433f5d6f1ab1c206232b2ee32137b56b53967ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000014026b8dae20a1913ecb45359e9ceb317137244e16a036ac760b47363f2d389ef6cb12cd5f5fb9e8e31ccd39bf114f8b000000000000000000000000000000000f07f9e76789dd937b85e02a9c346f81e87637bd03bd5f98a9b18ad6d109100b540aaadf1fec048530bcfa35dbb5b8ae8b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000a0249c354052094cae5a3d77313360a8956839af614184696b5b7fbd2af6555c6ae14a150220f01d624484b9096eaa700000000000000000000000000000000043098df38ab37f42175cc9f9fa9ecbde75bb344776ed078632b3d8bbfbf04103adde27ef0d361177bb3814cbb8bc54994ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000011e90effb7ae193b47afffe6fcaa0a28c358222cbb087ce479b7fe88d25386c5a9c9527899d7633eaaed9d982d3ed4e100000000000000000000000000000000174877f80e5e9daf2cc219545ce67b904319f75c0284e41552662512727c1e05b364364c4c8835c1c9c6fe028ae45895b1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000170e754e54f64090c4c7520bfed82665b44728904092fe3a4fb2fd2d3667ccd4ecb796e5ed9fc4dafd315c0b6dc22b86000000000000000000000000000000001081e62ee7c502159f7a8e28c5ee45fb7fc5b301f3a081899bce10096c74d1bf7834d12cb7fb1301b986e9c6f7501d53e670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af85000000000000000000000000000000001434584d8d1cb34eb29fd1c95871f218f4dc46f8b2ddabafdc7049e88f54fa4b80c88960a76411e365aa65cbf77f01ce000000000000000000000000000000000e4e2e1318c5907a07a7ff154b07e959d681a69c066585ba046b8889d417d01c503b32a924500944d43e68d7da8da35d54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000945a9d0603a3bd0278fce30f0cf97274319a760291fea5aee143c364cc0bc60e59dcd1093aca1a3ef64696ec47845e1000000000000000000000000000000000a77cc690d55763a94aa48c210610833427ed3176b6dca184598755f539359bc7302f8dc2cc941d447d9b5b68fa716b70563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d,000000000000000000000000000000000ac708f97c9e9fef4482af7b95c59c2ce6b8826f6b046b5a29ee2224ad66648a8d1f9959ff434c77394199fbe75780db000000000000000000000000000000000983fb55837062305d7fbcfe8c76cce1c431067a97392923720a6c4f963839d7d2661e2a8dacad1f151f505748239798 +000000000000000000000000000000000ae003001e3173dbd17f4d6598fcdaba9966f1e22a06ce747f7d2a06b2bd37579d093242a4940bd816ced07ec1917365000000000000000000000000000000000b27db470845f285c792da64e870b818a7598fb820313e075ec72e78f59f3903cb0860b749bfc67540a8bc80e844a8de5b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e9900000000000000000000000000000000018a33b2c2f1ea187672612b51c8dfdd9e86674df58ff4f77ff3f71628e7aafbb80ad22f34ab4203c42bd39a4f73c3d6d0000000000000000000000000000000017c3a68d8782a479ba9aa829e3f261a3e1b832595fe3922d800349bdc2bf58e0c1b523eb0924bf0996e38aa83267f570a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000015347adf6539116167ee71557b78d8fe13373512ca7d8d365179e25ae8ed2c6a65e1f643cb0ed677a2f44eab809d5b640000000000000000000000000000000002360dbbe0b7f8e97f6aec4b20a7e6525d83056975a4228901b4f19259c9ff2d2ee00da9bb9085232fdf843e5d305561677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb6071910000000000000000000000000000000005b62380515d49aa1427800077a11a8f01ff00fe7df53a13a9266910e4038167ab747bbd0705fc25ae2cb0e2451c893c0000000000000000000000000000000008de7bcad1c67d7f1fb5cfb9d20ac2134006618ce0d22f4120f5396bf8164c0effb0e3ebba7959e9dde757973080a9cc53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac00000000000000000000000000000000185bee837e3212323dc40fd471ed9a1a58f2aebfcf7f07ab761d40bc1ed77b385a134c99385d07e75c5f8c51d6496482000000000000000000000000000000000d7d42e4e18040da671799f981d404085fed490182d397685498e80967cb9c080a766d5c8822152d78920fb388b979f534c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d50000000000000000000000000000000012961da3be1ecc774fc9df2dcd87c337ee50a99df7c4821fe08da7327276a24d754be95b6e916d5c63926b6e44b74310000000000000000000000000000000000e44d11949fe33bc3a0ddfcc74c5b0fa79cebfc0d4a00a574ad7659c7a5e72c728ae4ee031af57e9135a3eabd93686edbebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000f03c013d5554584c2030ea02cb451ae508fe6dcba72bf7c49cb47a25d3d65eabb2fe043b9ea90e03571aa7b64be8b11000000000000000000000000000000001479789662864eabf677d2a541e48e5ce70f35a2cd6c0a476d4179d02955a51123e75c650888e514aecc85d67781c8c18b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c959600000000000000000000000000000000139bf8fb623dd156a3fcc46eca51e61155cf58e2dfe8edfe717effdd4418c833db7fde2031ef27edb4a70f9d60d67440000000000000000000000000000000000c352a16159eeca4dc9a86601973c02e39f2a11c8a0955ad52236d7e46dbc405147258ea8558505bef0f09ba92527c76d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca0000000000000000000000000000000005b8c4c2782a2a2a3abe4f99e60db6ff4179399aef4b9e305fe037e1a14a4c03ff59be1e91f55e5bf316356bbaf876af000000000000000000000000000000000eae605cef3beee4a176a0589f2676b3e212edcd7ac5834ece3066bbbb587bdb6bbe46663acfd9d8aba2251a238004106330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b8000000000000000000000000000000001038258f67b0097ec51adee244cc15d63c4d3bf1b3b3e64ef8ae6ac15a7c4195fe97bfe8c5a42981a2463ed1b39032de000000000000000000000000000000000a6f27fc1f2dca48f6e26456de5d9fb840e4ed3fd9ff12372e51130d7c439f4ceb4fa929da2dfa3ca271d34e9aa0985ec2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe374000000000000000000000000000000000373fbfebce5c599172ab017e8f4f9813b0e6aef3031faf61c336aa7d6b64c8986827a27605b476bfc1057a0388f864d00000000000000000000000000000000079ec2c41547d98277c60dc46a61ddda51c9df65a8ad2d0a64d483eb245986de36eea2509cf7949c5fb05a77f9cf3bacc9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000d50821953bbbdb494e48c59c940c5f2ac2b902f4c2ba2b2ad50960a51ed7eb1a9d592bb903a03b0b90d8817d10848ba000000000000000000000000000000000bf0898bd20e08205aa218e529db578d5118ae411159ed372eb8968cd773ebb1619f92107d2948020bb3c721ea63159dc2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a0000000000000000000000000000000006bab55b7648be3eaec947694311289f17258876d74a7d92f22b7807d007fe142a71210684593b1aabf74579eb1b1c17000000000000000000000000000000001016b28dadfe9b65d86a1f843f7ff4b774eab74431b68b079527c2387ee6cac69e95ca564346fc54237edd3d2d31f6ed9fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000c0d5ae44a0863ef3d6d32f1d8f32f2c5b89112652e2e3d6ce620479882fafd73cd3627f9f11315020c8fc9341c7fb4800000000000000000000000000000000197067de9d61733dc0367d91f55a57ae268d5e7babe7882c1fbcf03cc38de7a2dc41acfa16bac0ae63418fc349b9471cd4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000c3964c79741fe8093ccf2f3d118b33897a18d920ca242ae153118bc17bf0102fd19a9e4000698b256930a2f415305180000000000000000000000000000000003ce4a6877879ee56299ed27f634571126d9f8ca8ccb1e67100064e7efb435cacb1ada74d7c7529b495957ce7a5dfe709644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000018e6260c0cd6cf806ee82a047c51a85e0d7023883cfb05993ee81220e0871b122c12e65bb99b20787322d93b82089e98000000000000000000000000000000000d5b66fc46b7fb60fe8efb6659bbe948c6776d7780633f007123c5c49f5fbe7e3defc0f3d896333d0ca01244f2b6effe0df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d,000000000000000000000000000000000c7aa8aaa848c068104b88d0757551612285327ba5fbe35ccb4eacb1bc96a341786e4548f5f86f6ded789b712a4be74b0000000000000000000000000000000015c9aefc358834f21ec85092cd69df37e0036ea6ddf23a3ca052a669446b183e5340ec7ce894ff6575f5e6531947c84a +0000000000000000000000000000000004acd4cb6bcfed3219c3aee9368feeb58d77a7ec81d19bea11402015f4bd0ee2d7afd86fa7ae9dd320910ca28eb6d98f0000000000000000000000000000000009fe1b0094c0c2ae80a3c5accfed5d212ce39f867aa2150b781c193a0053aecb04d06e005fbfa0a24595e5968d024be18a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a8330000000000000000000000000000000018cf9bf39549c35e94211b4e2d0a0157d73e1ce8a17cd724eb33c38281dac07e12eec61b27b440b220c4f21915a73a52000000000000000000000000000000000fca6d956989db84dcfe58b0310fc21b5bdc82a32838c8d9cae912d683dd9c67f68e15b3fbf9d7b430ba239c8904fdd2bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000001897956bc232fd5a9b0ed1b533bebef8ddd9e97002513eec71d67ce1086ba8473f2c013af7d8ac548290453d9f71bd5a000000000000000000000000000000000796da5c8ac165d416c8fa36d84e11bcaa80c1bbfe18efde4b4b2c71d6d00fa24f3d51eac312cad9e854f094dcb6ec7458b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe4000000000000000000000000000000000302240769257e92899da03fcc4abe1ad3944b74c3046e790e4e950f2958426b5fdc691401a1c8a531f42185d382fe5b000000000000000000000000000000000053750b58b6d2fbacae94e22b397261e541eb4abf4715b3f528dbfc3388122918b1b4b506f2fef89ea936efdef0105b3b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe000000000000000000000000000000000168a635a14f61734372f4bdd2fd564d77afa8588e1828d88c4c90bb50f57473b2c20585dc0e93726b84e73c61f29ef1000000000000000000000000000000000e6e92355e59304ad35b1dbfbb98db803d5fadabdef4fb1b2a54080ec9a33a7147ebb4d5219acabd949337bebbffa793b049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc000000000000000000000000000000001797bf2ac9b490cd43a346fdc64bfb22301a0a0e371bb4df8ec02342b4fcc99af43b4735665c6b1386fa04a3dc5406e3000000000000000000000000000000000fcc20f4aec04b7896ddfd86f58c2e1e9dc6f863ec3b477572c073c0f4fb07ee8dc0d5a843321446445b6e7846fbc5d556197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d9703300000000000000000000000000000000013ddb8ff149222a5a0a997c0b89aeee36a6ff2540de3cba8bfe6a2a64fb505f13ad956a3882082ab85bfbe72f3a3a6b600000000000000000000000000000000102c1a1085f60cd5326966a2dda0872290e1658002ff3ed95c47cc0345565076bdecdeab7082bcfb439cf7f3e445faaf721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000189515e637d404ce6db58d24774609cf946074aa22066d808dc022824a26b381bf09148005c61156a976154b025d71c90000000000000000000000000000000009102e313c4517cdd3d07a66e0013eeafc996c21fbf5f0f3e7d232ad5adb781cce1657bd5750193cfc0357ff55bd012a461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e0000000000000000000000000000000002e166e475ff083faad64667b683e546b2358f945b8656f9c2f3f6e87a40dc3fc087dd94874bec1c4bd5929b7c96024a00000000000000000000000000000000022bb4ba4be638d8c14a16c94522c41cd3b3ad917daa454f820b8fa35e5a48c676266feece6986e8fe920b2a5e43e4b3569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d0000000000000000000000000000000004947bd8ea8cc3b116fb7320c573fff0f107913c18cfdba2e7e9a4c8715e334a431156f384548508df8950d681163aee0000000000000000000000000000000001e9e7494c295248184503344b8ac7bfcff41a4561de03d78691ac47980f14aa47c1eaa3cca80103f0f2ba14a2842aea2061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000004789b01538cfc54cad0e99538e874d13eaa7f07199af29d460927c3e622c74e0bb4185afa12c53446f56033348c332f00000000000000000000000000000000154291a8bdefbc91445ef1fe123f326b8aad652c8c54502920d4dfa912c2f42d784fbc5a16d08468d2d6ee56e7e8eaa24129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000029048f227fe8d1b7247a82cfd3e1b4b60cdce6b52de42c4b96641bf8fc5ba9b077e33bd4c4fce9a51b63a6a2451b427000000000000000000000000000000000c83518e1b7700d68966d592cb2e3295a2db5226eb6fef972c8a84721d1e49a30e4a8ee3494ed4bbcd2a6877e1ba597d366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e83467290000000000000000000000000000000003e96431aae4330d3d204093b7af21343ace4f1960de951eeaebea51e778b1fee43ecddc46667d096edbc5ff4735586400000000000000000000000000000000183a282f4b0513be661b1b38eb5f02b51aadc591745e0bd5d2d4e5545739e26470a9ec20d78ec284268d9c54c8e4f7b6d997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f5000000000000000000000000000000000904c85edd36dfa18ddb4e1809607708142f3c0861570f2bc8fff14c462675661f2111c10a01557fb21f7f38957bdd840000000000000000000000000000000012a3a37f34ebb23d4c9268ec9e1d53aed4747aaace497695e6ea8fdbdedd58031cb479003e8bec0d14aa1d062fa30f2ce881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a337100000000000000000000000000000000148532bffbbf8bb1688f6448854214b4273b9d5adf132aa9142c1605d1882879678b6cc70638713b9438532d427f447c0000000000000000000000000000000010971ee30d83719e10e91aad3f1f201fe35ba1a057531b1905bca3a8391a3786cd077ee0f104305eafb3c94f4546da9edcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e65000000000000000000000000000000000f73574aa5a06ea569de88e48fcb96e822039af296684933c1b417dde95e08d2ac9c6ad4d525b0734e24807ee99ba88a000000000000000000000000000000000523deae09e75121a6d89b45161f69f0733a9e43d88d8527a03cca8cc126aeb7a680cfaf291554403723e20440b79437334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638,000000000000000000000000000000000db91871e4cd84b3b58216b6c2e77a9521f3080182e78d3f66fe33f313013f06aec2dc5a6d483f35fadebde873bff9490000000000000000000000000000000003b9685de062b09b9e277ad5cd664d28af59064448af2b1b2b2357df6fc88e3ee7e0ac837100e0b7593944e8db43ab0f diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_add.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_add.csv new file mode 100644 index 00000000000..b068d49f761 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_add.csv @@ -0,0 +1,101 @@ +input,result +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,000000000000000000000000000000000a9b880c2c13da05bdeda62ea8f61e5fc2bf0b7aa5cc31eaf512bef7c5073d9e9927084b512e818dbf05eab697ba0661000000000000000000000000000000000b963b527aa3ec36813b108f2294115f732c878ac28551b5490615b436406773b5bb6a3f002be0e54db0bcebe40cb2e2000000000000000000000000000000000bd6e9060b42e36b57d88bc95b8b993da2d9d5acd95b73bad0509c2324212bcf7a94a46901932c0750535d00008a34f7000000000000000000000000000000000a374afd32bc3bb20c22a8864ce0dafe298bda17260b9d1d598a80830400c3fd4e8a8f677630eae5d4aa0a76a434e0ba +0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633,000000000000000000000000000000001963e94d1501b6038de347037236c18a0a0c8cec677e48fc514e9fc9753a7d8dcf0acc4b3b64572cb571aebbe0b696640000000000000000000000000000000000d9739acc3a60f6dffb26f9b5f1fd114a21f2983deea192663c53e012b9f8e1cabd4942ad039badbd4745ddc0a26a91000000000000000000000000000000000b4206dcdb80d62195febb6773acab25fa2c09a2e4be9416ca019faeb72f1fad1dfdc51e8cea39b371a045b18947d40a00000000000000000000000000000000100758b888fa27e9258ddd5d83409e8aeac576874bc399b33b8bc50d77fce5358cb091d42f9a1b1ed09be3f200959989 +0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f,00000000000000000000000000000000079e4fc2190d3441fa76c2d925d23b81e353e09e9138fdde51234195e564a32c98aa0d240f051298bf966d17adc2d6fb000000000000000000000000000000000aa327776fa7e15000dd548fcdc3a1cc6f9d0ab33046dd4240a3002962131b738ffed579945a348c795cfcb33682cf3b00000000000000000000000000000000179232ec56602d1ff79861cbfa2edece34b296541483aa65fe0cb493f520b7722cfffbe04294dd054770a38bf75d927b000000000000000000000000000000001826b88a6b411330757bb304a380487a02f7cf421115b84b3f468d11a83dbf304ce7a5661f4f01299d3c7865305a0006 +000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c,000000000000000000000000000000000f69e3616e7122bf78230461bb1f4b194988adc6149372691d8794d0086fba0870a2255a2c79cc3426e7ba4d032fc2ab00000000000000000000000000000000174752301e05dcd62f7a3ae3357344e64d1c94835b2b742ac24449ee2728d693a0df10c3beaeb45d1b4af4ac2bdbb8b200000000000000000000000000000000051a761a3ceb275ec28a2a269b5ded1d9fd11a617c958e73c07de3a92ac480aa82c7d2a1852d291804e734526277f5740000000000000000000000000000000009bec9045ea89d5d16588e3373cc977f6d975d0e2213b171403a9b2ca460b3b2e1106b474185516d4200655b17a179a1 +000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca,000000000000000000000000000000000c079610e6f8770d65352f911863b6cb4fcb25cacc4a42f75e34e29e977c93244a6241cf3d5bd1040ce7d8987996f87e0000000000000000000000000000000010d08d8f6fa8ee7042c0891ea0c3b9b59a79da52cf3a91627c79d456212e3f6f39e1f69aa0053bbdb4076a3f7d05e5dc00000000000000000000000000000000069047218b0ac1e07650ac8f4a1b9235f68408f543517c4ae3c0ec47c79b468713c704ff3680edc8abd1bbed7a5fa75d00000000000000000000000000000000137737706162e02cfa75ce2154d57c9a3520818cc04626654824769ad92ff7977942f3881a28284ea47c14f353772d0b +0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e200000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d,0000000000000000000000000000000004dd5dfe38fa70625216ecfec60ea8d38602552726f0fdfb8f392362ce845fe0fda76894d0e456796e08462bb941579f00000000000000000000000000000000195a85cd0685f4053ee539de7e04fccd2380819b291f89cbcd63d5a0015b3214500284a7c6568a71f52bbdbc38be410a00000000000000000000000000000000107c211bad49c7dd8555e30f2500c67e7175eb98a8494f3d5309c65a93cce89572b7b5489428eaf3f0a5c1be323c5352000000000000000000000000000000000c11f978150ac35722679cf79443b3706d288c968116ddedc1f1d0fca8cd746e3c92dc006330be14886c53c41feebbf9 +00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4,000000000000000000000000000000000fb33caed4de22cf341bb3e04d41c0198b064c1d371a24f5cf59595ab4a1edfd379916a40cc405d35f0603b2f8fb987400000000000000000000000000000000131ad6172c20b3a1cc2542db037de1324086fd9cd140ae97987980f260023d91b24504181af6fcbcfa242f48e99559320000000000000000000000000000000004a0404c00789459395f5344544041785d10f2fe74d4bf484966f5e9b6b4c4c8cb113a811a4fa82a1cdf8e3242bb418900000000000000000000000000000000086ba6a914f3f07bdc6750fcf6baf76124a17964bf9eb9a12982e8a28ca04360da3544b69436d5663e4e94bf7189529b +000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091,0000000000000000000000000000000019c8a1a206c0006a3033377abba4c31c55710a094d8c9dcef7560818e90411861ce7d189e2763f8fe69bf75e719e4efe000000000000000000000000000000000cccc6bba8691c210aa0a67d26584a359fab94041d853160abd9669893c0d398c805cc37fa3c33bc5ee5ff915b985c45000000000000000000000000000000000e353c1993c36763acec2a75495560e743d099b565f3de195e011afcacff3d60502801f47695da7dd589af81e772eb7800000000000000000000000000000000100c6123cf08eab6c59d78b414fa504ed10c204851289b0598b40ac31971fa12cfda4ef7cd2d64f9797d4d2b193e0bd2 +0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2,0000000000000000000000000000000009fc3870f88288c680b43d63d3bb5305b99fe461e59c07be981b8819fbee0d1fdfae0c037e830fbbabc40cedac7919720000000000000000000000000000000018bdd4903da4d14fa28af4c2cddcb708238cf68673ce77a04a3926c4aaf17d39a831c5401e84dd042d6adf595a1763710000000000000000000000000000000002c398f0e8ad9752f4aded980bc5de2d91118db06818d815c11e818ead47e7065823737db8e304bae32969cab065d1ff00000000000000000000000000000000180642a633c3aa402e5c0b18fcb6fe8c115575b863abda59b5d91997ab01014faefc975d0aee994f98cf37ce79eb95aa +000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb815960000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90,00000000000000000000000000000000199600699a6108599c638df8f965d73b5de4ca74598df281ec95c539de2c7eff9767569692d8e0ad120fcbb3d9335b95000000000000000000000000000000000c42b11e2585ba93521b3c968e9dee07e4f5168c11087d8d750795555a105df70c969bfa79b1ab4e5fc8d81657235d08000000000000000000000000000000001370daa4699daa99e9940fe04f69150e6f752798cbc0e66c91c3bd46149d935c1815f32d7f14b510e16d475044eda9cc0000000000000000000000000000000016c7a00be10de5732795cc3ee2951e58cb9d42f9b05d02fbff1b83fab5d3ad830cb8178092b76172108d7a53afe8c539 +00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e97795505419600000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53,000000000000000000000000000000000669cc8a3acae17f99f805afb9012a38851a9e8d4fd9895a9946c29fc859849c24d7ab7b6278c449cfbc5f1d7ea1fdbd0000000000000000000000000000000007a9095be808d0ebc99bce94e851d2a7cd3e1977b923064ab5bbed2347cf18f3343e60120fa051d12fe27da3146cb423000000000000000000000000000000000f1e7f75887651f67457f6dc064d7c11934035d15fe4dc40bab970160ed1b1aa230a3fb84dc1da08770d847c0216347a000000000000000000000000000000000efbc62ade1678cd70eb38c644038bf19e52b0859f65747068d9f3124762d951e4a6ff05f34b6d14919774f8409adff5 +000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a90000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773,000000000000000000000000000000000e25a38d0ce2aabd2538c95ed463f226e3f29ce7f10e1be27af2d3db741926d557178c4b125af8789b40480d8beec0890000000000000000000000000000000002a94b7c57fe2783d055a537004a3b67e41f5374da0813094f5944fbabf4d27eb576dc8b21ccc15f8339df14ff8785220000000000000000000000000000000008b9efd8abfa4fd71a8eafdba9df38360ef0b0a117c0052528d1c24df5032635eebc7b201439f5de858514666c68cd270000000000000000000000000000000012a2fde51f6f4a98435c325dc3b1ae846bc33a5ffb3b13fbe3fde2f74dec0aa815fa8e42392b3dbf798cf547fdb4db0d +000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b381731734598360000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e,00000000000000000000000000000000067265782d58b04a2ef3dd419cee506e076e49d1119e28db1df7f0e22cba9bbdabc560084cda50bc8db3915fa9c489a30000000000000000000000000000000012448a61fb2f6fd8e355111b671f0e888304284b72d5688091f2ed00edf7ccb7e5bd8a733a910d6964dde07d393798470000000000000000000000000000000005f687356ff6c634eb46613be8e98540107e706714434faff54510234d4aff42ef7752e154aed63fa8ff905ec0af628f00000000000000000000000000000000180dca84a37c964b30f5cd11a090e54acea102f1b884319f8d1252a37bda005512ffc39dec8e33af0dde0d37993f846f +000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792,0000000000000000000000000000000012a29d35c9af52f172787c90c5a3e77ed29d66feabf5d7bdd6bfc14dd9a05d402976b84d44647628c908d1816f4e7100000000000000000000000000000000000caf3c372e36de557ecd7eba02e6a79b1b4cff30343119df7a23662c8512095e051ae2dc27e577635c74a260be2b084c0000000000000000000000000000000002ceca293a58bc9beb4ee9a0679eab037f5cf7b326d65c0efeefdbf384ad8e4bc08a3a75a02e6b9cba8963e65d6e76ef0000000000000000000000000000000004631773a6590bc89b49a75bbbe2e732f9466ba259ef7a04ae69b6aa5d5a2621c1918eb213101f6f7eeee4656a7b1472 +0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5,0000000000000000000000000000000017f155ed9911ec56d71d63d57556de071ebe89be36e6bc9943ec068a70dd5a6f045dfb9fde5c1e29d52c9fc17579452e000000000000000000000000000000000a60d62ea549edf4b11f62f2321f39d41bf11f3c4f858dc7db85b1dab1b7644e27eeb1d022d6082f59c65155068d2c390000000000000000000000000000000009d309145fad15860e556ec4b4aecb415865954247c2034d5bc96026e4d6f7612af6e2db99f4e462acee2b303134b91b000000000000000000000000000000000114ed157e3d020c5397cba7e10cb864aabb47461f166a6724614e689274ae74c505fb6ebfe3e88da0d6c272a15a0527 +00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db,00000000000000000000000000000000118e0c81f9157395578f0fb83b179721de2af3326d13189cb8f43911d8c3268a11fd9702f09f14c115bbdc43d5fbc08b0000000000000000000000000000000016a548df8c87f432c31e4e32c3e5b4d48d6f29fbe391d1181174be9dddee450e7e96bffe8c9f23692ccc080116592944000000000000000000000000000000000eef72a5c698c58f1d2ae9415da256b54d7b1ac37a1d1b88727c0afcfd854a41973c6cb10ecbc3a90050fe3d8d3ce8780000000000000000000000000000000019b16ca8f955dfd21830a3f7fafcc97d7de977bafe1983892988aaedd430d22674d97897d24c1643e99bfa6256df4bf7 +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca800000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,000000000000000000000000000000000f2bf3f69276d390c9fc2c15e9f5f5d0b3cf9a6eb028c44811b481f376ab60e17d33a04b78348e46eaa94332c5f16ff8000000000000000000000000000000000bedd0437fb3f4baef87e56f33c77fcdff6a5512571cf11fd9605697abd8763315f1fe4bccf04acc6e971d6aeefd9c1500000000000000000000000000000000067c3ff69733baae2fb4ab77cddb7563047c428b40a257a375f8cf8c9d230a6619f7932b86e0836fff0c1c60d2c4dfd900000000000000000000000000000000057526faed8d62aa10e89add5a338320c748ca1f96ba5ceb579efec69d17475571fc4ce6fce3a93398ea88340f0e969d +00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91be0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9,0000000000000000000000000000000004fc19f8fe47e6acd37567016704b07f906e8741fcb196f697e1fc24b0204292693ff424bf1c5e407f5bcba5a3b1ab85000000000000000000000000000000001816f992c3c461fa6d2014ced382a35b0d70e61927d72b4d661434efff3dafe2f4b6cc91bb1a5dbf809f10f3ed7f36de000000000000000000000000000000000dadf7f7223ccedbeffef31c97df7e01f99299da71b589c8828b65715012aa343d7e041dacc57b34a6b5f84523a7938100000000000000000000000000000000167f7e73e22df81bd2a7a6f14e940a401bf414e5d18b3aa610b2a82ca8f46aecb5721d0092b27f8968b2302c37957268 +00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7,00000000000000000000000000000000041a5783c748247f05457d30d16f93431e9046a236d5025cc07a27b9f2abaaa556e2df65cf0f0015107253fe94d8b4dd000000000000000000000000000000000193638bf69c7508c4b12808a62e89883c34f97ded6e1b5dcc3f28191e5c7fd901a72a85ae386acccc9865f8144b1bd500000000000000000000000000000000180e8184ab583da58b77b8a4d108a366dff3e3b336ebc5c9153fa815188edc95e7067ef25f7d79526c295d634bc98f5100000000000000000000000000000000125b147100f6df0cede8e22151b3423b1dd364899fdee103c71a44388ff002a367627a2342e15833644bcde61f2ef6b6 +0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780,00000000000000000000000000000000095fda8adf3981f4468fb82aa0ccf80e55138c922c6422cd8e67f53ee63e7a390bc345469e9211a1f8d810cf4ba27d0a0000000000000000000000000000000015c19b6af21f75e8e53fcefbae1c8d7f97853a8aae5fa62e606cfc92ae71890702ef9dc5609d3ca8fefd415fbd820c04000000000000000000000000000000000007b7e908766d34c5d99cb7cc76d5d5ea83c29ae1d9b83b163741bc9962e293926b1e251b546ce0c1268def728da78100000000000000000000000000000000084fbd6253211f7d66d52b7f14360729d54b2f94c52f2b76e521dc3961c40b4f19944923f64c6425a44eb158a9727a4f +0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e170000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e,00000000000000000000000000000000121e7f2eb906d0b31b8ce5cc46638428b6ee57a1ee70e4ec3c2bc044230b9b86875abe0862145b442c0e34308efc690f00000000000000000000000000000000139120d0a10b82737561d0b3fda01b6df69d9beb7dbabf3ddda036f9b4c317f3ac1eaf400013fe5ad664bea44a73b336000000000000000000000000000000000a923184b381027d8cb3f82708802b204566b2b8bb6a72767aa396324d8a26b4e0f0cb92fd1914d77a4e9af2f1ec31e3000000000000000000000000000000000409732f2225cb5e5c002bef17512519eb1a18bf6c3d7f834d0c7ac8a38433c88b550b3f443d259313eb1133620ebf0c +0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b710000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67,0000000000000000000000000000000006a200642d5cece5eaacacb36000b4b897e8d8c661c8282f90495002aa515c7638183cf1e80a0b35e953adb92b6bb845000000000000000000000000000000000e88d4cda34e98df4d727fda79b67961b5b8efb1b125ef2a8eafc481a2cb2fa1530e59a091f31c25cc49d38f545491ff00000000000000000000000000000000082f38c1a1c35981f537547dc3b59331ab8c5e8dd261df58fe6f0c44ef1e65d0cdc1980e1a62f6248f38d0afe91e5627000000000000000000000000000000000eda1002e202e9ee4df5354cb87760d4df32eba1eafdad27cb0636879370a8f93be0bf2a30f15f2fbcd7e52c1bdf6b05 +0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4,000000000000000000000000000000001341cf3316152ae8d57ea2194224f04756690133d2e02d077dc271aa577278e346e0ff66e8a49ff8c983fd34546e1f6f0000000000000000000000000000000016c9093da650643f4b4061e1c6e55da6ebaf9f234bef8325aeecad3863a0a2f53e1cdb2d54aa8b075ce6e6632fb4cd660000000000000000000000000000000011eaf3dee010bf2a16c5fbb1f7aa559cd4d831f087d9dfad4e157a6d2b6495e370d9791cbaaae19339a65726ebfc3b910000000000000000000000000000000008476d793305204be414819fce2ca70754a532682876277bc0586514f2096ba9998ae848c722ead6722d5af9395ff77f +000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa097000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa,0000000000000000000000000000000009792d98ab9b90c2467ad0d070ea44f382ec7ad5290a59d889313c5a55d7b8e837333ad7ecfd97221d405cd6c549dc8e0000000000000000000000000000000002b92dd07b61faec23f48b8a7893dae29509fefd688a978bc2e870d4cd6f963d708a0611b4aa65f5644fbc6ba4c5e66b0000000000000000000000000000000011e46a283946a8e033afbf7c14ce3162a05867809d7de94a090c8cc2cdca8bb79add21f6e2fa8d7f39ea6d26cd37ea850000000000000000000000000000000000fddb7cdf1f1126e7a6780e4892601121b289a386ebce0caf96cd392ddc57c47e3f9284889fd8a18fb330d6c40bdf67 +00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e,00000000000000000000000000000000054dedc002c5f2da8c6e0a0146bfe5c83200b276b074e6d6f2c397e1208f152d3ea3e8f0da7da62cfd2a028d4c94fe5b0000000000000000000000000000000012ff307f86e266e7a212484a169d3e81df98217c6f715176913b0d383cbe4e790212da7feca0cea66df09d92544fae010000000000000000000000000000000009c211438dcf8ccb664b535e73eff304b92aa2f568aeaeb8e10ec142f92b211bb8147b250dad77d508cfe353667b6f150000000000000000000000000000000009d1734f4ecc88fd56f412f9243c387b9da659faa3fe7295580a6b7519b1980bd074339fa9b0bef44dcdd0cf0c4a629b +000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f,000000000000000000000000000000000896a38ce734c550c178786092292e737d44fa5f503d6d3b66c75e6bb70b59d1db9e8baa1ea3e256e2dfd8a942311e75000000000000000000000000000000001231db96a35229a4c7507b0ec193491446a0b43115c27d18b3715fcd4aea14d4e5c99db5934e73bb0b86f1bb91ee96fa0000000000000000000000000000000000d6f95d5637b29ea889c028dacdcb484d8ccdb243da4d5ff49e5ad82f234d414dc1484e9ed6cba1b5940eaabd3066860000000000000000000000000000000007de052fbb76902e06e1783fa8afcbb54a5069b4c5e9cee78d43da2cf76f24843a740a9eec6fe9b8f9bc4ac9baea77a5 +0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b8592390800000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f,00000000000000000000000000000000156914a9137e52abd4579599dea4c0f857eed0457ee1d80635d3a6ccf0c766ba8ab1b6f989711fbdf125c4ff06b597ea000000000000000000000000000000000c60184e8ab32019ce20d2d137130f657c8964406fe4abb26da232c9c5dbfab243837d700c88d6b9ea4b8f0a2f514281000000000000000000000000000000000dc3e6e3acb898552791431859943d0a83fb4ccd62e4ab2a971370a93a99a9dfcdbe4c42535aa063354e0f2cd48308c300000000000000000000000000000000025be02da875d4990d1f0be626ce634c4856ea91f88f636bc27e313e73897c9c13a1e3ae70c1227dfd4fba97f521d6af +000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a300000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352,0000000000000000000000000000000010124c1c1c10868b570d2969ebc3bf5cd6bfab13ddc93f0fd2b8a1742eb8e04d31063bb81c52b92e253128d4cb4413a60000000000000000000000000000000013f89997cd2ddae00cbf24cb66a92146c553c6fae41cdfaef14d49078729f239ad2661937dd0d4d6ffd7076b03e0aa84000000000000000000000000000000000ba2ecf990cd846c95b35ab60d4f97f5814c8189190df9d521b3dae462f2d44db006a0daecf6b82c1459006bf82ef7c90000000000000000000000000000000016dc129b83cca5b3c699628d081306c5fa61faf9dda5e92894931714037628fb829c595bf64d4a7fa295f136ae244601 +0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e055086016000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6,000000000000000000000000000000000a66f36f2437db57473bd8b7670994f1cfeb8b43c0ceae358e63a5e4e52b737fce6b3d24cc4de593bcd44c63f2c5935900000000000000000000000000000000070b7ad970f03a38c8a31452cf11422159cd3331d746031781a5861e26f54efbaba63dcb1db8bab997eada9c3dac39cc000000000000000000000000000000000ba4a9d7350adca1ae64e722df11baeea77c5fb75c5b52c8c46b9d863a70bfed1ec47888e907213f4ed4dcaedd37f20f0000000000000000000000000000000008a64244f1870a1dbcc4bd4d5c9eb5cd5225713dc73aa22bc46b1cea36c88a66f85251a8a9ba7279c88bd5dd37a06f7b +000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3ff000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703,00000000000000000000000000000000079f89f2defd1f97efe0ba1db28523abc88cdf66efd39918a600a07c5ed5b72ab9d3354a172735e7749b5f6814a48f4f0000000000000000000000000000000009e361b8609be8057e5b3c99eaa1727fdac17edc59239af17f55d72c8b8daa89726f4ae240c742ec4b02fbd89d45c46400000000000000000000000000000000121b475a2ab50357ce80fe01fc461195029de20f61474b0773d80434253adfc268a775e1a0e3b7df5e85d1ff8c5008960000000000000000000000000000000019a76aef4e04136b1ad0d03586a3d8608ac4573715f18d5fd6907d03e5fec7c5659e15c19fd87f242da972b651dff5fa +00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b200000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb,000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c6700000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02 +000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,0000000000000000000000000000000013f8cdab447ef9be450b87f941c96d4e93d5efd811d80c6a910965728f7dc496dec132f3fbeee5d1e84ed7c24ca9c2a8000000000000000000000000000000001537d5caa13ddfac93f0f86729c743d9a68175a78c730528b581fb54b1f4d020473b3b766e3882a485ce5d02ab381c33000000000000000000000000000000000b370903684ede24f3df80e3834ed414a765cdbad98f20c49bef8663a82a468d3911d6bbcdc021e22c252e83a857e55800000000000000000000000000000000100cc8d05f071904753776c6092a38db84c5de751bf93216131a0f9a50bf78a722344a14b3be2a9207568d1f669d208d +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f860000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce,0000000000000000000000000000000003c5498b8c2d4765a270254dc927c6edf02acf0759540ddad951ea8c097bddb949ea0bf19942accd615bef21e8572dff0000000000000000000000000000000004c17bb648909bdddab4dd86560cb6b341e96f58c515ce471281f226181bded16b358b56d72e363f9ec491b8a9dcd92c000000000000000000000000000000001828973958204f8ab8cd13f5af5f3529f368a149bfe931a8002b61a61895457fbcb0cc6874631bb55799c884b998d8b9000000000000000000000000000000000f61460bf61bbf3ce38917850bfd3cece1e3955ce29d200c6f8aa89076c70919c02668678edc0bcf94efc9e9ff6a650e +0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac5,0000000000000000000000000000000002c6104b3494fdef86d53f87bea68d313188c0908b935fb3b9f636ccd401c6e9cbd33bfcdd437e1a0150d0e4b9c3a881000000000000000000000000000000000bdc88396f807d1ba8d4d6e284d008b5e40445ce32c23a0178824fdbb6db3c5aede7687eaa2f12249125cded57052ad2000000000000000000000000000000000c7004365c1d3027997b55bd258dfc61ae07a762666fba2a14aa2ca116673fc03a6f694c069f53cd915fef6d37513101000000000000000000000000000000000ec17688d8f53e2c92502091c859cef4fe9a57ae984cb1e72686bf1f0656b10246293cae4b96214a38dc76cf2709bd59 +00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c99739,000000000000000000000000000000000a44e6a48ea0a95667f607ee66290cb0094c964baed779bd6656941db28e30a7e9effe49a617be9ab376af4f535cc28f000000000000000000000000000000001933b87310bf5fa60b1abcd13bb7ac3f2ec0a278f6a0a70c953a2905ac1d3bc5a70cf1da885af45d1c7680bb4f7ff74c000000000000000000000000000000000597ce9f1bf7efacdcb0250427d0341e142226aaea060983175ea149912c5c4f3019fe87be6d87d186a8f562fc3059eb00000000000000000000000000000000198b5a891722a237a5e23e3004798c8d3f069af3267152508e283b4549fc5e8388330343f80e606eba30af51c99c7020 +000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe20000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0,00000000000000000000000000000000047c2ccda315b9c013e87bc9168b3b8dd6d463403f1cefd824fa9f93a99f4c4f98fac5f97e4237f76b1ec91042f99bd600000000000000000000000000000000036861fd0a69cbc851741475905441b51af12c5b2aaee6ce9a27a01a43db810be9c7d6fa401406e98e327703404b83a5000000000000000000000000000000000310cbdf53f6cf8d87e2d178869bee4359a8dd666986d869761a79963680a33ea3ecefd40a1e558acae5ded2ca04447300000000000000000000000000000000108bbb28c73ed7e76a51a78e4d15a2c88c25e05c7127ae89d4347cda00be231b5e70e0b0562caddd4a7083efa4516722 +0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a84486000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c,00000000000000000000000000000000137d23ed3fa0d7e5928af8d1f4bdfdef08e0b4c0f3bf6f51ed28960ce9805eb8fb254233bb18cbfecbadba95e112fdb80000000000000000000000000000000018615147d7a8cce1dfed6de25cf2fb52f54a243bed4913e20e66673f47ecddad9c5e4ff9653f522180de4b90ddb3ad17000000000000000000000000000000001521f12116b13f785b5211aaf438aa6668bbfa318cf0ed6d91aae963f6f00d32cc5f25d3a02bd902ccc25f847ee2db830000000000000000000000000000000014263b23396f4facdacf13c79864157823db724350bc640abf8fb6d62663cec1069eef9db56817660510e2417b51c616 +000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e370000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d05712,000000000000000000000000000000000038f9df6c14f84b8ef8045010c8973e5c2f8d2e37268f6a674298de7b15cae82361ebbfaa00ea1cb2653c5d00886b45000000000000000000000000000000001376f7e2d5621aa9d6f7ce45ed11de7e0e1095ebeea976f78eb83189c6852ee199840c14059c233bc3d40efbeeb5eb36000000000000000000000000000000000c7b0e53adf4f0fc5172f903e3fc479539348241edc3e277f30ae6b4fc419aadcfb73a8f8a09a1ae1dd885a6250de0040000000000000000000000000000000007a00b57ecc8b056436ecacd7e0fd346b906b15042e9a700f54f8c3b1d251c566e0c55bd34f7a9e30f1566b7f2ab16dd +000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4,0000000000000000000000000000000012662e19e41bfacc0c792f5183596bc7f1986f9bea72c626e187d72111b6ef3f36f5afeeb640cfda99b7044c0d0b846900000000000000000000000000000000050ba08e1b9fe95dc67e6ee1ce60664b291c80fdb59729cdea75dfd18f22fb88f837b439fd119c46c996787d3008194b0000000000000000000000000000000004ea0f488fece967675abdd3c42f8fec25b547cfc45d42fba14bbc55ad7e1a75296a679113d0671cef0aec0c2165f4a0000000000000000000000000000000000f617f51800b09150a7560505079c785ab45cea4705992fc0325edaf4ceb30e1f0bec35a31898db5f810685e55634076 +0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb300000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc,0000000000000000000000000000000019c774e968049bde2188e844c3413203bfe2c4355edc8cbc2cf6f977c34c0a42a206194e6eecba3c97b24558048f3aa700000000000000000000000000000000081ccf6f111575a946341759b9faa13f3608998fbf4ea3b547804737e30fc7e33495caaf2aa328b19bd48315c5c7f9e2000000000000000000000000000000000a4098536041cfb808176c7cd8e980eda613a2b390e8d63d607caaac26db02fccad6d87412b90cb4b3e186bf9ccd31be000000000000000000000000000000000d3c784c6587b9f786c06099a62aa639f40535b512ac2440912f04dfcd1cb5851b7378f381fcdf02d4e58312eb7e442f +0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05500000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6,0000000000000000000000000000000016fc7c743c5ba747640a6494fb3c30caad5a1e9719a1994d0ca73bd1645fec118a2887acc8876d105102241c10274cd300000000000000000000000000000000058a42a0095a7388fba7ce71dbef4ecfd2018c3fcdde14afd2be26588de4689d8de757e1e3ff22645fb8c17aa60265850000000000000000000000000000000010bb622f649e346834b95e82f93ae83c71c0a65df7842c4ba88df7f6eccb0217ca9377167a6d14777e0474c24821f8d70000000000000000000000000000000010c180c685ea3d0146eb82c007fec3efd129880f18f838f1cd2f80181f5a4884d6b5cc8247430fb0c1701a57f9d1d485 +000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d03,0000000000000000000000000000000019419b635c3742cecffee02ee7e2b1f18ee9ff15e647ca0abc4398ddc421ae7e0444e3c1ec377def9e832d8e64fd40e2000000000000000000000000000000000d9b4abfdaf3b4c7bf00fa07579befa10a3418d8fa0f3a9c31e59ae48b0de50fc8e6d583aaa4d0fe6048bdd1a9c60eb60000000000000000000000000000000003c96d57034ec97c4abef1c2c81f4d4b0f4b6eb1e9dc5464bcab28572555b9b874df80325941501c3766fd7e06bfe7360000000000000000000000000000000002dbb3d72385b562ddcb9a80400ab3770f00d22b880cce2fce1641042b9da669b22b2fbc97617648c25ab644e661e2fe +0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbc00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d,000000000000000000000000000000000d32b00154a5fe75c576c098419744ac36b911ee800f94bd598ff9b6adcaa39c836bc158c5d6af72c9e715a242d0fe710000000000000000000000000000000006e057c13885d6c05f5d92061fdc4d532f10d31d472c371e71367fef7c5fdd3741e665321d1119b895660fba3770431b000000000000000000000000000000000bfe695c3364e15479741e974f838649e789a76d073e552aaa60981fbc6d185eb7b297fd59e51535965214a02f5cd67e0000000000000000000000000000000014f0a27412248e3163e5f82fed02a25d953b336b0201692f08a3e8e9a9d223b736c70c1a39826a0888fb02a314e223fd +000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6ea0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47,000000000000000000000000000000001566022247ce012b7de92c8495876b4de91c36448f4f7e00f6e154185d38a735e701dda989ae9e37d332a5e60af5d06b00000000000000000000000000000000065aa42560df7990df2098827a55ceaabf3ec592c53d2f20e5dddc1481ee64381accbc8e58601428d33589b3af78a4b70000000000000000000000000000000002d9b0cf8bfd1adf76bca80ca351a4340f02434090518807e07ed76440497042f13a0cd7a9c30086872d6f145808fb290000000000000000000000000000000015daaa131431e3e78a6221091640811fcf88c835ac975a041a7ab50bc1d06b80e6a3c9ae77d2390fd14cc9bb009b47cc +000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710be000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf,000000000000000000000000000000001290bff629c93d992ad2cc709317c48980b0e56a32fe239258c7aec75e4523e0bc0b81319e100d10568a44847869a8d000000000000000000000000000000000055d9098e08eabdf2b883df35efebec9f6afb16d651ebaca1067e2129146268664ec51c8a4f28f13a250f3e9883053780000000000000000000000000000000002424dab6f0d18ea8bdded2a72bcf87c13307d27d53e8ec35e91eeab97fcf3398135fd436c530c609fd47a3508472bad000000000000000000000000000000000b25d0db1e28b98d4f9d3c77c0b71489c51186105d93be7fc2cf8c72b8abd8959340114635e705e698b0f257855ea4bc +00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be100000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2,000000000000000000000000000000000cb2998b4e634bc83b5585b0683b7b561f260eefb826719bdc3c95e8ae51f8f7b442d75d69e0f9228dacde2ce80ef4e60000000000000000000000000000000014d30d1c02122143868ea01b454a4f33432d875f8ba66e6bb1e02fc161bb5f9298e673339a9183a15759f8b94b519cad000000000000000000000000000000001068bf3c768e8c9e9058805050394ea820b5f60bea6d271f8e1fb665d3b7931ab0cc03dff4cbd24577b2c254a956e8200000000000000000000000000000000008b7f4148bd1f4926d2a84497b60a48701057ea08855bb9a2f838d2464e66360a59d058d9072f1416023cc72045af558 +0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da900000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb,000000000000000000000000000000000a7843a1d67360b8a6976aeda2e4e98f1ea229a4d84b947dcf5ed8215173d5cf783920a7714f5b048778df30f01a0bed00000000000000000000000000000000035663ceafda9e5bfe934cff725b36b258f12afe749f907a560a06da4abf8380853f8de31adf14d62cdb310d8740e29b000000000000000000000000000000000f210d576aa5d4cdf5aefd8e55be099c422debc217ddf0151b8801f7d16456c97d1e134b40e6d71d296ee2518e50af9d000000000000000000000000000000000219efb35c68540c6bb0ef224e68dae6f7d48425c2908440072f5f63eec3c8e750b559c73e33464d0b5cdabb50fc4d3d +000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee,000000000000000000000000000000000ce704e650605f747cbc0bc76e82de8569ba7b3d897eac2bf5f79aba17ef4c989731e959c0bc0b7988000a9b0aef39430000000000000000000000000000000003cd3f3d978d6c85d98812ea0e3d21149bf4151ad1bef966ced124ad62dc7cde55f16e8d08bb1ad54d3a23bb73795d8f0000000000000000000000000000000019d37a20fcf6244c2898b271535e3b8f279eaac5d8fb1ba142096da383488eba28a21d038d7a9d3f9e8a008d6d3ee1d20000000000000000000000000000000001ba9c1720a4ef07ec752efa1ddb629505b3586af415c916fb0ed2953cd8943d9343268f438db860f0bced3e690a66b0 +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c70000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb9,00000000000000000000000000000000160d8b4bef36fc3d09af09dcc8357067c22e421f3811deea66faec42a2f00fa4aceca8725cf99062613126a9fd7bf7210000000000000000000000000000000004e8691a42c8f3ce0e7c0470446689e9d2b3cf57d55fad7387d624857f977cb9c6864c87bb4b6a2c17538478ac5fb5960000000000000000000000000000000015e20f6baef033efbd38081d5a10eeb3c67d89ebe5cd652110b778313c9e86cffb45231616d5b67e9ec8b7be15980aa9000000000000000000000000000000000af75dc221050256015fecc2bd8113b42afc9c624e5d28d7ff8312af499e34a603d66a4304f263729b440b6266538316 +000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c6,0000000000000000000000000000000013edd8f016f6af49e9bc461ca14c438a32eaa3d1270a5acec99a666aba3f0a7e7eccea81720971cf4432bfa94cd18392000000000000000000000000000000000dbea5617e44c82da828844a5a4a1426d43422fd0158204a99f53cf9821f82f0bb0130a2123297a6941f695e172d9c5e0000000000000000000000000000000005f65a445e9f2d57dff2b210209f9faeb1c8b446454de4724d990aab20bd68362dd7ceb5b95de361c129855abba83f7e000000000000000000000000000000001219ecae79d62d3039e642369353993b1ece049331f06be256f06b01a1c3b0c617221c8d8f0bf4b6a0abe1191a3ee8e2 +000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307,00000000000000000000000000000000158da32df45fe3e9102010bfd7faf3fde936bb8e52f68262ef479ee825a0d7169ff753aa042883a5403103a9bdafd2be000000000000000000000000000000001800a5776a47f52d2af08144364a6cd7442a0e2fc214a2d8d285a29bb7bd3a0293e89f0a1856223a527100d0abf12899000000000000000000000000000000000a6079d18ff3367c47fa61a57a967b782f3529bee93f452ecebd4f5c404b3e1769c100da9b8aee4258b5191ae1dad9a90000000000000000000000000000000011d3188a927e8f13aecf7f8637be6ddbbce309393a94fef77923c286244f8531d3e137e031d8c1af829891425afd53a3 +000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52d000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc26,0000000000000000000000000000000019294d87be784f0f8fa29de80d45a697bcb694b32f3f6d7641d4b08d8a7ebdad0ef78ba5ccafd6b7f240e1cbde019c51000000000000000000000000000000000645f7851644e1e7e255d0b3dca769b987ec3ff2c9eda42cab65dc39be2f9858c31f307d59f6a2caf9dd932d873d2b08000000000000000000000000000000000e8e93f39ce05a11d40f3b52262980c79ecc52939dd02b94df3e5034a57061d040b0c8894189f4626f37bee485712dd00000000000000000000000000000000001e0b7c9c3d7456b2c0ad842083e9ce2a00da91cb1aaba371ff4b9370f0f2c08f4b53b8e5a3030c99b2957cbe5f9e967 +0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d761800000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb34150,00000000000000000000000000000000040f355021ba50c9a3b2b4267668ac8d76dd88991be984ab5bab9c96faed6dcc6e8eac78ed29cd6f7d687dd55cc5d5b70000000000000000000000000000000017853cf0a39332e3c7d75b08b2940d693ac7cfdac46719787c22b55a2ab1036d6f95b68075f1c585942843aa486f17bf0000000000000000000000000000000008696feb333417a7262e8976d1546b6d0a9d5970095485b18efcdee8993b16f42e6dbfdd08d30c45fe4af6a5e203de07000000000000000000000000000000000ec26926720243124ca505c0e04923f3cf5eeca2abfdaf4388960b87c6c1713fc54cdd1c825e2ea359cc67b3bebfa2f9 +00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e69000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b1,000000000000000000000000000000000f3dd56c416db1c06fd27e18fb852c9e1662fed42005e253230a7a8f7c3e0b8ce637666e1d20952c219cd2068d6865f1000000000000000000000000000000000aff045afcbefcdcb5255805a86e8af3de881e5482188c487d15ad1b799cf551c1d48c7665028b05ceb2e82e15ea4ae5000000000000000000000000000000000e0e6ed04926aed1f8c6a4e13227bf2a99d9d6d349a9c86214373be693db702a0011b4423defdb7d842bcb6f722c70b100000000000000000000000000000000148b1af285c65b12eef498f1c9e57a673e7a3803088c56e32aaae13dad3977dda8d3e27809094f8d8ed607239610a1a6 +00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df10000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e,000000000000000000000000000000001220b3da7e7d03823458bcdcee82db56957e5aec335e9b543ebb0f3cf4fe3cf6ecacb6198c886b9abbdaa42f528b4963000000000000000000000000000000000138233b166547e9e9ee9d11048e2d2579b2b111af5cab372d36159c4c45e28d836d733a1265e8833da64f461c0a32cd00000000000000000000000000000000005f860a0c72034f1a928501d9f549e5c2a9dc72670272fbf35a0b301025c0fc751d55ef6fc2c5bf7ff42df7693f3dca0000000000000000000000000000000012c73105adf97bc0dfec1f56153c57c6fdb9d68341f4397b72f5b6c667873ff7ed5cc841451b391e33290cec256395c7 +0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0f0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e6,00000000000000000000000000000000014933a0923416428b5fe5be7120bf399ab62ca091b07d03da3fd2ff080b9c411c3cda3bfef40c8450ae31c412dc5feb000000000000000000000000000000000214229a73780d4f260364649e9eb2ed751ad3f687a832a3738ca2cc81a3acf12757651e88c4bcd79239bc0b0c40e5a6000000000000000000000000000000000548f20fa375e578084e085ee71df5f8ddaec1db03a1415938d9521b5d9c914b5295835fc07263cdbf49d7802551156a00000000000000000000000000000000063ecd9efe55229a76fc848728e940183c23bf47363cb34c5a49837e6df8a5f0dc29d7108cd10ea08e82ccf017d246d1 +00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a330000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c32,0000000000000000000000000000000008a71a08d2c4e2ba3d8774dcb42d3e96c7f72d36fb3b880a4049b078d8257a7a9a51b0b34c093568baf4aa6de70e709d000000000000000000000000000000000daf83b5ad4b91b557982fc4b9b7dbed2998aa39fc4658ba671f5f27b3888dfec7602949cf626c9e6ef21171acb185600000000000000000000000000000000013a7ffca291d9ba8790ca0462c54c147aa22e03a2413b756f27583155932aee65060924e46db321b3fd6f22ff7f54041000000000000000000000000000000000289d7de10285285279aee024e52476fa6fca85550f7af183a161e395d72e1339b629c64127f96bc85858d80e73dcbe1 +000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a6000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95,000000000000000000000000000000000a4ed8d613cfe4f5dbda1d0c6812d0edee45ffc2667323c3828f8ce4ab55c119e92a82f2c3d06afe3adaa4aaccc18f8d000000000000000000000000000000000fe10c5e185f3f8ba81c93754132d76e05eb3543d8aaa8a2d0c98833ce5fa9e2b84420d6e3412e005cf89d11f5400a510000000000000000000000000000000004ac5f8cc614e3833b3b6dd9eee9ac29501002ba9054554314a4c516bfc8cec870995e811f7892811346574f3c58b2ec000000000000000000000000000000000a6bed54d8ed4ccb09211ae7773c604edc6ce51a05c9acc94e8167026906d387af681fb33a40e72e85cb076e072db7d9 +00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f254000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150,0000000000000000000000000000000004d145ad2575313a922667b897052063139eef8c61dd375eb055c4a5c52cfbed35391a85df915e1eea50d000b9b6bb5700000000000000000000000000000000071cc73c16a234e99faba9b04fafaca1a943f2bdbb68dcae0a1742acfca1f90c5f69464aba42be6c18be31f79ce30791000000000000000000000000000000000bf725a2f4d7d33c66fefeefce13fb5649a68a93fb7086c943a7bd5663b5788a5ceaad7fd2a219ade832dfb3c0022a5a000000000000000000000000000000000fef4a2610610afef43da2161b86b25a8f6e30ed90053d57f5ee0a10effcdd2af769d32ef6843804b2b6590f95eccb4c +0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c5600000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b,00000000000000000000000000000000151ec7c35a67b878420e198ee7bf359d0668ab61ba1a0bc2e5e57b1b7b18838a015464f9910b659fb7d1e10af2801d86000000000000000000000000000000000511536f34067fe931c6e829e22443eb838f0c938eeef6f839eb322d72e2011dd1c33c504dd044e3cd721065d7075b520000000000000000000000000000000010c486f846242024f9bf40d805c8e33ecf1b44cfaa04455d5584db7ebc32c0d29e8742c61886d4ebae93f22c518ea87300000000000000000000000000000000072e184c836a853fd1153eabb1b645bd35ef72eefde4a52db169acdf2d8d68499398599cb4002994c6f4936de1da75ef +000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87310000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421,000000000000000000000000000000000642f215b772d17a3aa45ee3aee607321c02b4f7a7df3884259a25ce78c73e9536d46333fa388e506fdc79c708bfd9de00000000000000000000000000000000145864ce36521fdb641761be541a27bbd3f4797b923a870148bef1d5b4b0d463c0a7c8ef07954dad464510d836105e05000000000000000000000000000000000ca038e667fe68111b583dfaa95f88d3b9e46c0798abccd1476071435067e6c0e2fa81d25db6e1175e60efa1705538b9000000000000000000000000000000000cf1cb1b155e4ea47077c42a1a99c3f11f8b27516a808b5e73498ee12363652bb46eab7e55de93513cc2d6272f26a537 +00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab71960000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d400629,00000000000000000000000000000000128c909854a20ccf9e8e396b617b36f233909a5f6c3524c93cc659d22afe0e7058a438a5ee4345bed914288c64802e29000000000000000000000000000000000239fc43718cd27855ee5450cc9be5be5d9bca8188c22601242a1bb4269ca0fe62ad5e12b2c65558cd3dfc89ea31205f000000000000000000000000000000000a0aec9527febbd35bf041a901b0b35e5e0d48a2d6d733bb557d0767798369a7ccf2f1c278710eb764f721821f9aeea300000000000000000000000000000000194931bad52daa16a648ccf1ba9a4768e5e2900fee4f9bf46ae07d1aa605aabbfe96684f5d2233c0b254cb4ad5517775 +0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f20000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d,00000000000000000000000000000000189ee5ac642bfd0b612058f96e63acb1feb6b4dce125bf0ea1e56e846775af1a8b0864d4ece6bd96c3b5dbb04e2f6c33000000000000000000000000000000000073d57ab79314e38267ee8015de3156f2c1d5dfcb6655a150b9ab4a3bc9eeddf7b37b3681c49611e02abb012770b3f5000000000000000000000000000000000cfa1363275c7bc5bbb9bb7c03e7bb7f6d6d365e39fccbe62cfe0bb93280527c9ea99079fdf9871abed035b62079856b0000000000000000000000000000000010048e4e96f26710d254110650de36460be2a8302badfc2da8b26147da498e4620e79b4329033fc3f3a9c99b1e12aad4 +000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e,0000000000000000000000000000000005889133be5f447013d779f2b9b0033667c5af87e1c8a16d239ca3ed238920004d87e00119ded46658026c26988ee63a000000000000000000000000000000000d4ed8fd88f7e1394f2b5a65588bf1c461a292acafdb77703c2790ef249f2de695524293c826252c94967a3ea4a3a28500000000000000000000000000000000001b5ff0aa278c7e87a89d4748aef13b516c49b7dc9f7cd5e0448dc6fd860a7a8af7183a198eebe6c7dd549fef806db00000000000000000000000000000000003c9e40ed44427cc3cf886ca2db341ae31f015c542b857f6702d25cb5036e3e6abeb8d4bf9a0e203281ab85ad89ce0da +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f,00000000000000000000000000000000093b692a68536b16913ef38c3bba7b19ba94a6af1c36a2e54b8ac1754a29c29882107cde142deb95365af00f2d1f537e000000000000000000000000000000001035e70852f38f860a1a04f33081e84f3ed17d83ad894a6800e7b8b9259067b755fe7e08d4c1b297c6d53064ab8209590000000000000000000000000000000013d38db0d8575131865bd7acb6cbe994812bdd8bc7f51b810bc382a6eb379d442c47be20a2c8e751fb08ccce8fea68690000000000000000000000000000000000bd114951193e3bd58cd0025e0b0c807ea073b1c1f7bb04a2a00771b6442e70ea20e1124572ef5b74d2bd87c93c82f5 +0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d,0000000000000000000000000000000006db1eef1f614613ada8383e63d631484015224902ca38f58ee384a70af0a0575b0e7063675d2dd997ed8a140e2598470000000000000000000000000000000010d7b833f050f18ff4e3a8d0df227a9494dad9cbde88f68802b23e87387622a5333dfb7bcdcbfe2d4d137cb532ef4a150000000000000000000000000000000000c9c40ba972ee0be2823625a23345fe352d701cc8bf9a153d5a55c205ef1b7e5544d0a7f65aaa24bde8d77cb4c31ab3000000000000000000000000000000000402f170c4c3ebb9b1e7d64765b66ba9b8d45b2ea9fe9517626f38e00a11d180e1f8872bf80f6322bdf3a8dd90732ae9 +0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad300000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0,0000000000000000000000000000000002dccab673b26be02d2c645c82a2c73290f0eb053e07d4f81d4d315d9483e57c58b65cfabeb0172934b9fbb52ad519210000000000000000000000000000000011c34a27c850fe319fe89399e7680064caf6dcbad171c3a23c45b9883ee06ccc3482b2b81e5777759ff81b16bcc1b0f500000000000000000000000000000000119adca3e2b052c045124f021fceb03c979e6eec0a270c7f4ab13674e461839a4d3a10fd48da4e9ae750a238a2649ace000000000000000000000000000000000fb5210677e1096cb5448bcda16646d6dd29ff8a0765c5aa51d83fc952a5ab8063aa96e97f33abf701cb8688c989c363 +000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0800000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508,00000000000000000000000000000000056489b2248ba672501069ab6742016cc8ab2af50a119239bbd3c0a4b9b56e014402b78bf62b2b37bf4645c3bd3d95b800000000000000000000000000000000046956432001feaba6d230da27a72e8db5c8eb3d52f00616f87b55c951217095f337a302562cda789e5714c4391ac27000000000000000000000000000000000172c2a583c9563fe02d43b2b767c4ee4e3990fbabe4ac536d64cfcf059f0e38672876289bc86915b6344eb398fbc4ddb0000000000000000000000000000000008915b0edade80caee9b386e4a560ff4b9dce33946ee992649466315786e139e3ce241ebbdfa7ee28fad7e6214e65666 +000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e1090000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f,0000000000000000000000000000000005b81843ef3f98c6a6686f1fbd26f77248497ec3d41aff4be5968d13ba86f86309b0ec4792d74220ad8ef147bdee9aa90000000000000000000000000000000019825376b243f3e374b6e9e7e51e0c969bc72b39cde1dfa09187a3c7c5c2c752ee16fa5a4c8fcf94464287419b3a3845000000000000000000000000000000001308cc0c77219034a9fc3018f1d668a41e6959476aaaa5461ec73d7155c6a68fb08e1fdf8140e18270cd338c266a83f4000000000000000000000000000000000fee2a6e245e3bb570c3b605f7ad805bcd68e9a1f2bb2282f92e2a2e83b69e275b21b923f33a65defa8c4224934aa588 +00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b,00000000000000000000000000000000166414455bcd0e8e40397f4cafa9628d1a092beaef62d35211cf49779ba98df5c1d692f650c1fcf0893a9d4ae1926b1c0000000000000000000000000000000003dd898d0725ee899b913042da8566a1379aeb4dd5f0222ac784205b4e74f32858ae490f981801b166a01fb96266dbeb0000000000000000000000000000000019f0fe4f12b113b337361b977aff7cc7dce50bf37c2609b9f311ce340d30225de178999b73345ef49625518e52aa4d7800000000000000000000000000000000090bc07c6270901d706a8d28d512b07fd0e03013d94d4e43eafbee59677998bfb7c2a58aa93571fb49c35518b6331bca +0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b22000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d,0000000000000000000000000000000019ce0f31d9ebaed0ea1d12d4e232bd3ad48373fa465af44f1c8015102b624d2f8330d1323fb2fec524e83de0f6699ad7000000000000000000000000000000000915d65fef96562ea3b76f3152aa1b8e445ef50fa66dc487ad0c04cfd7a33b5ee48aed919eb81fe83b1f4dca59b4990d000000000000000000000000000000000e4731ec887261f29475523f7dfc5d21cbbc1b883439701a33cd58bd24f5d447267707c2b60ea38b04510be7dd10d72b00000000000000000000000000000000146a679d7a81aac5952645b2635f24b96393529ab9571ecc1078c4c20a77e59acc4591b9f45df00428250c5e31b1a8e9 +000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f,0000000000000000000000000000000016790155e57f7103d9e325a1f3a64c0b8a1875365eaa0c01c515538b64bd8265e8392e755a2f7314c37ec09026f13d290000000000000000000000000000000007bfe690fc4ab166b29de35e341e8faec4bc3c2d4ea2d42c9f4166c0d748b92b743ba646c86ff9e570612c75bcd522a9000000000000000000000000000000000c11b9ccf990162b772099fdb4266716b11dcf46c5abd12d03caf222c571e2a9e28cfb47e11db05162967ad4b430930e0000000000000000000000000000000000bafe02785607bae144d9ef5391fef02b9f2fd5dcd436e2506bd40866d8726eb83c223e09c00f3b8895181c6710912f +0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a00000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd,000000000000000000000000000000000965966a8a463de1f3bc49d9873668e87f54d95612231458dc8b885681cee8e2835482b4bfc476153c41b206f427cbb400000000000000000000000000000000183639fa14dd74c33e8696496a3ee269160f88e5daca4fdc468724d9b6af8e7d0706867cdb1bcc608029b89b94c531a800000000000000000000000000000000026257fc32efaf241c7712b0a7e9f881763d8fa0711a452d9b71ea25e973bffd88433cba768f1e5b3ea15bdae9cb9428000000000000000000000000000000001527afbb6594dc0f472673606fb8f4797fc855bde4d308ac1acdaa26f19a70f80f2d2bbf3498b53b887b79fd6273231d +000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db100000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3,000000000000000000000000000000000018123e82a5572e6b6c62d5db07448838df9db7f7d15dac1adba1fd924892c8bb3c417354e838f706564a9ac282c2ac0000000000000000000000000000000016613fc38997d39b2761aed3485de4d7c273e8392e434185605e968ed942b9d4712cd0d538ed5ed1317870d0cafcae27000000000000000000000000000000000354365566b6e43f8b7f4b94a6343146f35ba3abf61a204e9c976b1ad1a90d4d493494c957def69ff270371c1c8d953100000000000000000000000000000000066adbadf1b69dd16cf19349c82e362be4a3768551599b81a4853ca524a24326e6c9dcc38b5a60ed6fdeb3cc4e7973bc +0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d,0000000000000000000000000000000018ba8af47c5cfa552374cb1b25ada1ac785381f2da0501f86c9e7b11cd4417e64095a5c4bdc2480ee10d215ae2296063000000000000000000000000000000000a2e09eff98280f6a9863d8b8faf8871b44650496eac1aaf90fc2b256f88e937101407d722c95fa76846776d4e6bf0dd0000000000000000000000000000000003824f5bf25fa4aec5a9e044703e5564122bec11da155c01ba8ab8344265516c1063983235863d826f68bac455327c65000000000000000000000000000000000ea72f8c6768736800b141b477610e37477d926acaffaa1951a5bfebb042c94c065e984a8812430153d529dbf07ce2bc +0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296fe00000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6,0000000000000000000000000000000009f1339cff0b58b00a871add058929ffebdc58cd1bd8a9c2c965c63e1843945b28138008cca8bf7b7cc9afb69a11767100000000000000000000000000000000011f65b337710a4043e1fa58bb41d80d505e2aee434b6978129c80fa1b124db89e61617e89bc0e596507566f4a484e9f0000000000000000000000000000000017560f768496ed583b3522c4a013f8b96073197e5b53e9041db6dc935a266111e21d8c54fa33b7bda944a573f6e1f07d000000000000000000000000000000000168a0742af91f42058e6501e122b6fc50dc966c2f5981372704694544aaa68fba2b6483752fa2464526d5072f84d8dd +0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb519000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be,0000000000000000000000000000000005daf8338637bddeba63c788d78faa622e014efb84d3ac1d655d15af06317fe31d1782b2990354bd507632844cc87f2700000000000000000000000000000000185550250e2d9eec798e8b8c483dc37e2a917b304a6036e8ee518a0738d6bf946d99f6b7ee352b1a259aa894d53a8e1300000000000000000000000000000000105a4865d66ed4bc4f51dc52ffcf284615593d573b6beac490c3ee8e08ab83a529c8dd062d762d1d70b9b3290b6e8bd50000000000000000000000000000000014f598e5d0e40090f29aec1ecaccbebbf2a2d6889bbb9439798924db41b70c0cacdcf1e8ff6906f61943e9a8a1ae4fb5 +000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c,0000000000000000000000000000000006b63929ce97554659ae731d60d11abe858383e39a67007877f68233cba8179777c0dfe511fc730448da3f1c4347f85c0000000000000000000000000000000016d4df414c287b0871c69f9745a9ae68ea3a1ff41ecd17d87623338bb8750bf12be52caa81537bacee06cebb86f894890000000000000000000000000000000007ad72c98e2428b90bead3616f1b31b26e978cd3f9b6b759ad53056098c18932c48ba78d3da112d7a738d7a9ba21d84e0000000000000000000000000000000010dfcfc53d0458296686fd7e0555593e0378d2cb176d456abebfd8322012bc9b408bb180d4237679985457e689131705 +000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94600000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3,0000000000000000000000000000000009b166f124b5b85875834b5b0c088ab79a2dcf262240b284f57722e78b6eb56a192cd32544c1bb93ef492fe6d7a6216b00000000000000000000000000000000189b9792982b51b13cc3fc1691e0569b6c8d998168d3a3376e63ca60de4b30a84ce8d04fb265bdcf73f158d8e316bdda0000000000000000000000000000000005b99948b635750040b5b59568f0e8bacbfd512db2ae52c5032cd23eac18ad58d83b8f78cd26ae979ce2abeae8e1f3c3000000000000000000000000000000000d0b6561a49c358101b30f714563bfefc72e0febea857b1ce78cfeb9508b0108c2089c9b35cd694bc8c0ea8afc8d047e +0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88,000000000000000000000000000000000bbb59d3e6b0b4d86ffc89bbfcf543a5b8ff922f1999a1e06c501a734b19dabd54632132c865c53e5287f69f06942a58000000000000000000000000000000000a3bb94431530879a7fb46b317d4f3d65b5a790739b396c78521a20e1cfad9c44248c9576be11c70970a49a1914ceffd00000000000000000000000000000000198df068ac5d3cfb9bd6896ab64495f4b9933a72872679ac3a46764478f043e9fddf17a7ef85fb72a8dc1a722804198400000000000000000000000000000000155c1a9db0c90634a6d214e996b13252bd4db3a4ab84ca7456ac3e7899e6fa096904a90f1150026307a1cac8de00c6df +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d70000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff148,0000000000000000000000000000000010684ea0303f0e76b60eb96c470e1f0466f1f2b073bbedc1a0c0df1d2f6c66d77cb90ef9bfa4fef6a6a9eff8f5c66f9b0000000000000000000000000000000010e7ced79bbf01ae9f65d26894c73a905514296f19561ab4d00c0cde31737d01e7b4e8b8e6050054a7a17e8acb74d49d00000000000000000000000000000000174f771a98e262825ff2db7571f5f5475007d2f73a2c265f24e2929671bd173596b8b163abd46b868a644dd464dcc7cc0000000000000000000000000000000001cbffc9bb3195672ea2d998b169f853d3d4b4e147379329b1bbe69ce76d08ad78f87fdd876af227a050c31884fda084 +000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d527500000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f,000000000000000000000000000000000fa306f630d06c801e0203525c75fd6065bd12bcb3c4d45c7e02b597f85a53fae1e65a969feedca75068433547e4632d0000000000000000000000000000000004b1bdbc29f19f6484ea4648c70eaa47cf5bb07bbc255bb72dcf68a7b661de433dafb682d51321369cd3372288b2b9c400000000000000000000000000000000136671654b24e1ff2e8223ba747ded51f5c826b6e2c0f02e2865fc35d15045f41952835800406f60f966d1f241914726000000000000000000000000000000001007b5e8ed7f0d25091dd959d89732e9df02561a829ce013f5ad1adb8d6d828a8ce87b52d39fda1b5dc2b581ca420e22 +00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae452300000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf,000000000000000000000000000000000fb74d9ad4de11df81c48d10b9a14fde8353ac47dc902b4420be4c086332be480552e26fc42b7c0f30e34f740bf9a4e6000000000000000000000000000000000612a7e23bbb525f91084b122dd4cfce4074c9e6eedaa7cddb58a14e0b1eccc2f08296baea3eb3e003e576fab7c557ea0000000000000000000000000000000016dea145df47a2c5262893c273c6158ee14d44c3740981c161624a6e9ebb982a52c1eab6160c3849f2bf3821d953f4c3000000000000000000000000000000000e920661772b8b737f1a663badead0e89aec4cbb86e6dece5d4db8a673e75b844bfe81662dff671658cb8386c16a7f3c +000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627c0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77,0000000000000000000000000000000015930559743b21acaf390b557fb960d3021f3cde80630d8867a063d445f860c8a01037057de1929be16d879416b12a6c000000000000000000000000000000000c6074c54c83f717700f61c5b6bfc641502121b59b196a1f8c5f2945e5db1bca0d7a94fdae96bfeeb6204c8c3f4d048a000000000000000000000000000000000b3a78454479c0990e4c65e4f831606c7eeeaef0faa86596350c9e43e84ae959a0f32c8d03d1f631d9b2ecd046efcda6000000000000000000000000000000000aff797d7572f20b06bac75bcf8cef879df11599ba7f8b86eaa28692d1239cff22841b66e28662309e81a6a599e79ddb +000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ad0000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee41,000000000000000000000000000000000351bad2f1fd9adc84280515c2d9e538b69dd63ac93514987ecace75d6bc4585199b742eae0d357d587924333721a1d90000000000000000000000000000000003e495b544aaf19a6415d5558170b8686968dc922367c5c8c212fa1f2785535fe0e71498b98b9a39c8b1f2384956170a000000000000000000000000000000000c7040f34872eea5f98ddc78737dd01fdafe75081cf66ad5c7c900674fa90257105b4f4fc59103dd5b92727a072ae462000000000000000000000000000000001312bdd27ef038d4a89b12c86281975bb34b435d42642fe0732709baf55e9a0ecc0ede8a4775a33e880aa2e1fa7b7ed3 +0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa800000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f,000000000000000000000000000000000d521781f60198341d116fa5cd9e2b5c2fe51f91f6c8318f351df007c96086f6c3baa5cd2b9b4f442305695dd9b01ac70000000000000000000000000000000013454fc15b1d182bc98d75947547b3bbebef6d5e2d38ed7c67d76eee8da89ea2be19280af4760282fa7576412d5f2107000000000000000000000000000000000d866015c84de74c24dde252542d0d3823f435203c71cda140af235d88f3f4b736e9d75ec32c09ab73bf74083e76866e00000000000000000000000000000000147dfb5f53a9cc61b6788c911dd8649c09cfffbbba368c1872a31cfe3bd6d6427d7b00163d39f8e0b81fc4c40dc60b87 +00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bf000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c,00000000000000000000000000000000059fffdf2d79b4a297f6912e3035cf0b07db9372f3485150e00d60bbe2e7d86f45b5c2ef062dd92c7e8b1e2be5e9bd140000000000000000000000000000000016acdc57e7231b020268373ddc8b8a7318ead02a8c7181165ab045208409373eaf57ace9a6db1fdedcaa477c7a0ff6f40000000000000000000000000000000012fe630f7de8ef5a129b99faff2de080849bf3b59aae1af042c29b1cc49c8825a4f28c4ccffedc6d568f306416b5bb90000000000000000000000000000000000d86ab3e49ffdc7c2485ecbd00256af83e7f3f064d212ea91245d86ca75e3c7f28b42fa9496a5ccc0514cffc60c9fb83 +0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e84000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb1,0000000000000000000000000000000012ba9a8fcb69d15eff147f663a5d7927b6f3f79330eb9ee625e0100b146597554debfcf97a3afb51387a73554522ed0e000000000000000000000000000000000a63a990d6454d4db6d58642eb3489f79e517fbbcabc06f2eaa00c4b6f9a07aae97991f169d90af3461b7a62db276e00000000000000000000000000000000000a95203a1628a6ae2551df832f7ab94ffcdbf985e4c9744e244214c8e8b8079af05a9321d1e49b7240c2bdeeb7b783280000000000000000000000000000000001ec747203be73526d3f943e0af814dbede34020144bf247eef9a6ac2cfc83ef63f18a73d3baae18bfd8d5e83d0519de +000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbe0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc,000000000000000000000000000000000eefda9046a950c232c6244a79c33e7135d0896bc57839a4f971030220e3ca8196cd0ad75269f3cb5586a384dcd17f9f00000000000000000000000000000000195ce623693996f5ce9e45b4e285adb969e6771e6b0701fb5c95715523c8cb93aa641583821a3b360ad6f4ea1aedcc9f000000000000000000000000000000001553a4d0f965d26fbaba56294591935bed63c84abfedbb9d5c61f3d43484ea71600935fe3c8b6b137d7a9074d907e86c000000000000000000000000000000001673c42c88e4acf8ca38680694b80458f988403a4bd667468506452303000d13649c4f610b738a94ff88b65053731c08 +0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba800000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed,0000000000000000000000000000000007145ce58cbe48405392edda6022ba8942df055ab582ac402e7c9a0a951cc6a38cd147903f042273e736f30849996cd10000000000000000000000000000000011b457ba464ce818a34a11afc3c0007908091fb528836691e6eccaa9a23ea90cdc746769c4b7ec73efb1f2878413c3b70000000000000000000000000000000019ca519fa6a91cb7e83704daa9b92da9bb70b003f9e9bfe9f323430bfec9b19b01005aa9fcd19d5b1ac59dbdab0c0d84000000000000000000000000000000000ae356f5e5de0d7662bab8d947662bf87d792a3438ed477cf6ed4b27c935b1dd76a5aac446d4dc36db544d4aea40b505 +000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591c0000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c5,00000000000000000000000000000000135c42c10ef97279e3d152b18cbb8dac11ca8c805dd1d80818851424f592e7522589ec7df6748b5c72d0808399e629cc00000000000000000000000000000000083ddf3843434937e05ba9e101096371fd8fb34f226bcd517716200003ab9855f7aea94980c57a6b933494cc57afc562000000000000000000000000000000000be9215d936a49538442189c9a0bd3be07d4b0b1d14aa45afcdebc1fde17d33b66f7dc36da1ea5411549577f5a1967ff00000000000000000000000000000000176a4a4962c4af75a712e5093ec2cd5cb5c0433aa0657809dffbc0bc02b1ce303ac084f39a5721d482d41412d391317c +000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff200000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de,000000000000000000000000000000000bcd916c5888735aa593466e6ab908a05af528f34a7901fb60feb1f51737c73612436c192dfdecf927019724ab2a9b7900000000000000000000000000000000187d4ccf6c22381d0c40c9d7820ff8efe6298c6dad0caa25402412661737cb482dba2719c3a50ec08cd022230952dfc600000000000000000000000000000000164510d4f2cf1e14e039561f1baf82bea678d0065e378d5bb7443fa782e6ab2a3bf7e4ea125d6415a8277c60f5346468000000000000000000000000000000000281f2e28b73eca4db9966456b75de9ae3830c74ac928fc4c36b4aeaaffd47ee587d948f68056df2826ca2775415a53a +000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5400000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157,000000000000000000000000000000000cceccfefe04f94e0b67b29b5df8007930665006cb5a59504c3656b8c0bfb52324cdf50fa2722ce15b0ded0efa7fc85f000000000000000000000000000000000cdf34c330c0125f524f0711197639f8aca3e7c435f8c5ea30b78e9622c4bb72a7e584980cb4c3c6ecdd0689daf36b6a0000000000000000000000000000000004b1505d7fb65f6c06ef23aef85b16f3d991218187c5782fb635ba805da463cec9cfdd670c53d680c603adb827a4460a000000000000000000000000000000001104af6bef6482ae64b3b6b39664ec06c39bc18fa91b7b4e5bfcd444c827bab30ef548b28ef5487582d88fbc6d7983cd +00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a70000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0,000000000000000000000000000000000e1ef3003fe3181f690224cbc7008856e1251430ce3cff56a1965c89a892604398f5101d1bec7ff1590b0cc3d23b854600000000000000000000000000000000185b4d4b5fd8313c31542bd1bac034046ddc705b41a034a00570181503a6ea4c2d808bba0478900064270fadf3d655920000000000000000000000000000000005bed63ab9898b89f92027c04ba256569e6285c851753e12760129c98899bcbab34b62172906a1ea4cb056d4d0a5717c000000000000000000000000000000000961129a3e212c7412018d7407d7ad16412feba8c138f4f6ba69daa1a25c6b23f3466bfde6f5f0d09ab67248a2abdc68 +00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa2000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc01204,0000000000000000000000000000000001504c47ab0c410b32d5f1fe3d3996dbf1b21c5ef5aa3a2862a9d561b419f818f0b32b8e931c65fffc393ce7beec70ee000000000000000000000000000000000217e9fddd2551a171a13183ae3aba6bc5ce99e8f3587b92a7cffc738b478d8293b8c71989cabf9a55c5f5077249345d0000000000000000000000000000000003874de865d93650a95af4e153fe557c45bfdc4837bd6e209b8f05ad12b8fdee6432675cd92fd739b7e98e56e7ef16b60000000000000000000000000000000011303c0c7ec1f434cdf07c110da5f0bcd85935c3a0ce9fdf5546ca61edbc2d478562dbd9aa45a5f8d96e033feac2fdd6 +0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99d000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6,00000000000000000000000000000000101ed22b16502de0d83303134a97db17ce956faedf47256a9ac86004bcd3ed112a71328a58f98a85977a7f22eb1352c3000000000000000000000000000000000e841a88d10493f301af54c5fe07a31ef90de106a6c87d5631b6967fd017f561a56176a5f3544dbb34b9f94040ebd2770000000000000000000000000000000001bde3c0076f26973651cedd3da97c7eda24451bda856026d1e22d3b65c66a3fcbfbf506b4b664b5fc06fca2d712d8a8000000000000000000000000000000000ce553ee3b7d5389798cdc5af8569aaf477b5b74ca1138454dc61badcf3ecf5e0ee8457e374b5735d0b8408b04fdbcdd +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d00000000000000000000000000000000030dfbb89bbe5c14a7a55e68edc4fc38eaee9fb539a6b2f941264c7dc295da5712b0af0f2bbcdb74f785dc9ba038b0aa00000000000000000000000000000000132b4e02fda605a69251a4a6289c47536f9735dd90908ed1fb619b3ab808b3a1f1ca3fcc8f4b35c9864ae311c15747f80000000000000000000000000000000005858ece0bb09e55e012450551025ad2a6d93a15d29619433742851a62d987e7f8bfa6c6faed76493a27060ef5f51805000000000000000000000000000000000dd6b393e6d1b8d546e3f5ce69bc1737399e6ababc628f25734030e10d82b5e9370edfb5da15566d80e23d2fbf8aad5f,00000000000000000000000000000000182f90f5d3ce3f5ff2d91430376144583247def83b3e83524094d57c0f1be98b1c4946964deccc25fc303d6450edfbac000000000000000000000000000000001844806f711735c5ca18ca48e559a9e327b87b91d22a5ef161da7874668130e21a9499728fbc2c88366bdb59f8ced0cf000000000000000000000000000000000815e7cff14b4ceaf26d1cda5c267f432fad294b6baa239b65d886ffb039321f9e24330ae738a35298c6d1ec1ce1c95f000000000000000000000000000000001188a4a2f0920ddeccde1a47a0636aa7c404fd77fb9c828e4fdb5406df80ee6c258c2d4a89dae5e2a2b05210df9100d7 +00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e740000000000000000000000000000000017032b16be8656cf23bfe0abc8c9e6aade223fa9bea6fe25f95a025da79cea6adf38536eae3859b25ad1af1756b639cd0000000000000000000000000000000010975ed27cefbb43bafad0fd14c87ada8e84525e1d199fdf1e77caa0b718214b33e547a42a040ee3bfd51621a20d22fd00000000000000000000000000000000133d29aa41f92de37523d281eebfe91103f017e5fb390f6bad9a2a4419fa4702bfa04847edbca1da96eb1ad563a92c8a00000000000000000000000000000000014af850de7e800ebee4be1a33c7e3b30aa94106db7defa148568ca3c8d82edc97ab5769ac40162d3728687cdac201a5,000000000000000000000000000000000cf42f2ccff2e0cdda7e5f1d7652680650b4afa523c8f9a554ec18b905c837a189fff73982cbccf903ea492ea902b87f000000000000000000000000000000000d38219770f669557cdb623f2476b5f3f7478422b016123bf86a17bf75848548d1a1ce96a292637b8d52481321d80fbe00000000000000000000000000000000170d8722b824e3291b570ba8e4f9279c1dccdefb95cb5b7a94d27ad8a93513737f12d18ef3153c4e12b530bc457af34100000000000000000000000000000000021aee9e5f578328caee3177a4e08303c3b5533e288dcb75f94992db3520a6da16f4201e60367240b29c48d175942cef +00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d55300000000000000000000000000000000185aefe71f24281e5b03dd41e6d6d45fbc8975beb175118de7568bff0a9ccf917e9df97dc26bca16e8da06b0e9a8e7bb000000000000000000000000000000000015b326d401b827fdf556e4a24a3dd6c8036b1c849751b5ae3c3728cad88f931b06e3a345523a723481193f7afeb67800000000000000000000000000000000054ca16b4c87293002c31e64ad303e8f040e11de8b45c5fb9aca9dbec59b29dfda8532a8ef5ae6a92ac8ea90ee4303e0000000000000000000000000000000000b65a233a7731366cf24c801724265215a8626b1290d86c60bf1e74b021b0b44d7d6552f936fac7b5e60cf1feaa1d82f,0000000000000000000000000000000010d1b2f595166929347e06c1debefead06334f554dc31f320cb844abdb1810b5f7c4b933ff8072dc03d303f4a6d0d09b0000000000000000000000000000000013ab41dfca0a7cb0c58c2c19e02f675a94d9e73312cfe2999dbac34e6a80bff9472506b48690f24ad3171ad495f445420000000000000000000000000000000015bfd0db53fd4da538caa3aee7a90a669cb84460365696ee79b190d09a6d4c3f08965de7fff4efeae435db52b97d213b000000000000000000000000000000000182ffc4304b911b47b092ab678edd63ed5f5e8a9069daf9247f3bf9c0dd149cc9992728a13b0a236fc9b37714b35882 +0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db400000000000000000000000000000000085dd8bfc00ba517dc8d7ddb49d711d35bd36f9fe3843689019e779624a032d2f023533b8184b73042d1a1953d2885e50000000000000000000000000000000009ba8d5d36e6efe02097a3206bbed68529f0cb9875ab81deafd886d9243bfec8b403d2abe713a2ec929b93305dd2da220000000000000000000000000000000007f8f90ebb2771136a92023901ca85e87fb7c8b1a40f88ae564a124bdd0ff0bc27ea98612a817e2c871fb4bcea3bb06600000000000000000000000000000000152de417d02f1d14e5899201db8fd5db8ecb40ea8d415dcdedce8ac70c28d851db68e9aef94506a50ec28145547a2d68,0000000000000000000000000000000017555399f979745302f08210de5311a6401b6b181100b3bc6b6d450f0f62079d2f02d7badcb164f50dfc46a975cbd6720000000000000000000000000000000014aea86c06e4c1fbf0711a8cfced2544c7624abc7ae7906cd992bdf575a702540c45c2117e221446ba09960cbc9048ac0000000000000000000000000000000002fac56960c4989a84e02ce36e8970c2e847ee45579d31ca77f042bf96505af574af822da084ae64b22ff876610ba9a5000000000000000000000000000000000a481cfea2aef8975c80a297ce5a185dacd25649d41f8466d3c63d786e3c264a8e4ccab5ef6b80ab1260e86ab6d5b3f3 diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_mul.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_mul.csv new file mode 100644 index 00000000000..da081c22e90 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_mul.csv @@ -0,0 +1,101 @@ +input,result +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,0000000000000000000000000000000006334ba1e361fd94bbd98f44b75ae9ec00ecb4d3467b5528870b1a1fa9a7d04449f12af90bd4c7a1e3f29e717d6d19d3000000000000000000000000000000000bf4cc1626393956915845ea7ca43d30a59c7196fbe309f2d5ee6de7e40c191d29821dd6aae46abecf634b904de8f7490000000000000000000000000000000014aeb09e252cc74610ab956057d4ac5af95cbea8a6baba9e5062643dc037d6841044cb38b22d7dfb978fe0b58f94cc3a0000000000000000000000000000000000fdcd73452fc1ced1c06e6271410a48dea05afbe889a692905e1baab8d72418c62531aab8b74842b51016f0a9cbb93d +0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d,0000000000000000000000000000000010e70bef8eb893377e7ff92168d7acef11c9efab990fbded728b173b94e1d99e471a8357f16625d353287086543551850000000000000000000000000000000014043c1f00221c439e5febd12724a9224bccf0389914461644daf329208e869b1bf149880dccebccd440b1748d15e944000000000000000000000000000000000f7dee1e7d122e410b29a9eb011ee700c2f230cf8f611e196ec66e153c1fc331175532a8f9b060b573bddaa705430c2e000000000000000000000000000000000e1f659470eab7c0741bc8777ac9fc8dcd11a6f1b30ffb4265e96b879e795a4dbf851d1149429dcab95464e89f334627 +0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1,00000000000000000000000000000000119a5147fe9ddca7123f721b5662c1a44b0964c37a214cdf3a4fd34166e3b25210344e65220c38ec84d0e3b5ccc7e46d000000000000000000000000000000001642dad5dacf4295b871fe9b2787f0861f158807b2b6c01c2dce12ab053c9472bd3cb98de5dc33f40053ff45ce5c9af40000000000000000000000000000000005bb5761602b6639f2ecaf79f2d1f853fbdf75f4b3852b90808b858993a83f8a0da8a2ce7072aa91e3b6b3ffd0b3d1e20000000000000000000000000000000000a75143b9551d4ae41fb8bd71fdba7826b994c65904d9189a5ac5130a59cbb9d8dee0e016735565148fc49823d3969e +000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a,0000000000000000000000000000000017ebc9446f8c8e17dfeddab9188d0c808565da29c0bdbbc4138a44ca3196c4564853be28286b66660cda36832d6940010000000000000000000000000000000007f29a9583b4ae83d3913dcd72590a3f20f39eb5a6d36663c1ef433058e76550085b9c01bf797d98d0eef45cc22ff8c50000000000000000000000000000000016eeaeb123b12d1913ff1e50f974228c79f2b995609d2e3835c8e1d68773b0cd484df57b86111cdb75de1e19eaf062e500000000000000000000000000000000002f5688c1286aed42309896bd65d1826dc64dda615238fa9043669806968b8e0e1e3e77ef192b7df540aaf0ed282a9a +000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89,00000000000000000000000000000000042d0c1941ae0ed5e8787437ad5e2753bba02185317848e8ec2e425ac954e0efb1bca534725adfe87e8507851ee337af0000000000000000000000000000000002db55ae8126cbe86327aab880381a81205e33a351d172c883b9cc184799866a8db5a6b4321496e05d3ef62d00416d9a0000000000000000000000000000000012c45444403dd62d7be3e7658dd85909204751dd7d085f6edd38c0aa9185d3c32407d8c95bba371b380f788d0dc48e0900000000000000000000000000000000111421c6dd0db595ab731adfb4bc76c84a61197cb023b6f17e7176c443f20a4b6f8cd0a00cfa61e831ed20b3c6a84d98 +0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944,000000000000000000000000000000000ccdb2a0b670f199a9b61198e6a2ce2117075733e6a1568c53ca493dc3674c6ae85be2491d2ed983f52e2c7040824afc0000000000000000000000000000000004f52450d7e041c561c00200d5b142b32f2df2e2156e4f6c15d6c00e185e135037a1ed6be15e2ed920daa00e2f9bc8da000000000000000000000000000000000f39c38c18f03ce6baf1d016cf32d7387269940280f2e8d21db4da33dbd2d24ebb93ae3dff9f79b015eee25813d677c700000000000000000000000000000000189df61f7f1025fa6fdd0a4708ff1d53db7d414019c4828de2520af3d36776062350061c2261e46e746a6475fdeccb2b +00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1,000000000000000000000000000000001388a59c57ec8ca5e68b99631abdafca1b71352ac35003a55bbc415b48b8171857adda31123ec86a6ed9e1060d56aa67000000000000000000000000000000001471913b1ab5bcf9336665d3d44232b4e58da70285b7b8eb1dfd7c54442afb28c339f56e6389f89b84db0879e1ee058300000000000000000000000000000000022101b4de40b7180ea17bb36bad0a668a8def3e7361a96fbfabcfc4cdbe6f607ee4ee80d0eb2418b848ad056520092900000000000000000000000000000000103cda694792af5a51e04b6422600a0ea6f50808ca54423cd4f59dfba633daa5afea49c85b900f52e182610efb62fe7d +000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c,000000000000000000000000000000000cf5cb957a752ce9187940f63b13080790348814debf84b91e74fd6e822c2735941d61d50d492439475bb3ea7aa849ec00000000000000000000000000000000012e546ff33dee9875510a68301f46d89e6175f5cd9a6e179fb8599a580e9478fb8d92038982551dd29041d8185c7474000000000000000000000000000000000d52fb57bf2996dbbacdbcb4088df38e77e25598b91bcd5e41eaa27b1398eac150586b142f068d5b498e0ce458d3e8950000000000000000000000000000000012295e1d1039abe7a5fea51a04a34e9e8d44a0f24b8c032680703c119d54274d3bc2e548854021ab027b693e43964314 +0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108,0000000000000000000000000000000008e4c57309339400ac9b6b5df16972c272d47cf69ba7baf89afa4f4e72703999c5885253cc35686f6c8d277399da2a390000000000000000000000000000000018ad4e1f105f16b0dbb4eb089c51e709c25e407e54b64346224b1abbe15d62fabb231e36a69eb05a9ba7860f772634200000000000000000000000000000000019994d20a7ecc0f234ccb6b1793fa7d1ece64b3e157c579fb05a8c6cfcdd6f5456ac1f4c1beadb69206988ab543bb8bb000000000000000000000000000000000d435e74bed382442ab83ec90dffb91336137932524bfcf9753fa5ddfe038d0b98a045c8ec9deb53172e5662d3fd67e6 +000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672,000000000000000000000000000000001425890b6c46c5a07a79127de4ddbb751227dca4481ab7c2f601bf22b8f6a149767c73bfbf57ee399c0f2d0b12852a0a0000000000000000000000000000000012cce15f53fdfffb5f71de3567b0c0adea65b9321c85677c574787f7048c1bb5e2dc985b65fbc48115aa129e6000fe4100000000000000000000000000000000041398497f975289fb9fc6ffe671a19fdcd3753c82ffd3b2084574107bf7fadc8de462507f4484c32df39967c3751a480000000000000000000000000000000007514a7f246006e714d4a8cbb4e89d81b951b5c41a05bcf35f61283e888074fb3686fb6ecc1a66e491ea1e1ce0738102 +00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea,000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0 +000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76,00000000000000000000000000000000048ea2c854a0df7b10a2147db6eabcb16eba340644f737fc99663d1ef26d8ed688c2baaa7d7699c5f540d7605eb48485000000000000000000000000000000000c959efb835d48d3e7a8ce643764f27c365f6248a88e39092e3a6498f04ed851c55b796dacd62ae73d7edf23aa45fefc00000000000000000000000000000000114337b8caa68cea6f22a25c0ce3b247cadae24c63fb02c6a98a728b54f97b12b1473c8e23f55338326b9575a637bb2e00000000000000000000000000000000033167b0668ec650581815cefab61d13661f4cbc6e01711af0aefb699e1979b551d0031c603ee5f6dd4f716ea7aa4a6e +000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c,00000000000000000000000000000000142f6b71471f3665ee6269cf598fc3587a62523f9753eec48a2461a2e313e376828cf6d1a9ffc9e64353c8a668718736000000000000000000000000000000000153647cc4a5aeb8ea52f845c415651e167ace9f331c1d73eccbbe20a4014f9e1158c281495206de4b841839438a595500000000000000000000000000000000151d07c3f83217e63b332a6c47e91ef2418e9c658353f8b644f23266f5fbc727562f0935b4d892db947cfbd0757ed61500000000000000000000000000000000035bce4bd2d8261e21476c325cb68e581f20513eb5e0e6a0ddbfd4ac4674bc323590b6f52d0cd50010c13642e7e03daa +000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a,0000000000000000000000000000000014e83f87e7f66d8ed880ca46a76a5d3bbbacf2dafe1ee055f04af568738f4c6ddf2a93e1810b616da6f64f25c35a7b5a0000000000000000000000000000000003d14447254b61168d36f92710f95f7100cc8f278b0bc9528da763a18a5386b3f5b83b96f4dc426e4b0fbe755bc986790000000000000000000000000000000017f1a79ed64abfe5e960fda02cf3330e6ef5612c1b8639386959f86c970adb797bf077a468273d37996a65685f75ac30000000000000000000000000000000000d973499a7bf7132541c0976bf2e9bb26a2b6cfa5bda720352fa7a180a6b8fe95befcc13de5a2efe58be934cf7d8e664 +0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054,0000000000000000000000000000000018bb69dd6db0beb468242265c382de5ac342d465b5f72d4e5a24c67a48272d9a1f3af28e0bd3712e16a854c5d91c616b00000000000000000000000000000000072fbcc86b7dee9c2dc177dbabdbbbddb630c98ac3bf3737fd22f99e2b2b690175d9c5aa4b577f78c545dc6a5d2d03c900000000000000000000000000000000161c4218143ab1f0387f19bccdcd08f9caeb2d1331ca890741799ff1b40533076b6a96a910714176c770b25d2c17715300000000000000000000000000000000063098cd9d1eeb899724b40a2d10ac951ba0277db09aad639957f58541dd391fffadc5d97833bb9666b054e12debfa92 +00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,000000000000000000000000000000000e43672f1bc25e7e0e64a3fd26cb246bdbd6fb5c9084afdc87c888634916e6a6cc9a351cc67a6ac77ab8e132ed6cbee3000000000000000000000000000000000dee9612527c8ee9c574a4c51f5d3504ccf1d5781b59c78ea15294332c6acfdcc7bc68853e70f1f72524c930e4c3d2eb0000000000000000000000000000000017eba629eb14a0636926275f1c2109318ce8818d8171c69fd371751b6de47bda5b00a0b0e3765d05bab7b8dea9add90900000000000000000000000000000000052f0a4cd9b91695e1e58ead1da1480fef08cecef63896aa51ab16da373b99b3b91767a374645ac5932d9c7fd21d4636 +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf,0000000000000000000000000000000019b7ea673dad96c8352870136ea262c9ed105550cb403eb1e64ad598b2145fe1b95e5d61f1b5a6ebec47568c67b68086000000000000000000000000000000000f06ff9bcf2ba284e705b12ef2311f1a9b867ed742ee0737567b5c878547b18394b82c2bb97e16586515728245692cef0000000000000000000000000000000019dfd2d8fc4f2c989c7e1016e147f336174c84d380bab992bf1adbffe96d93d4d2d1d1dacdba3adfaf283b184478229800000000000000000000000000000000068d230422006004cd88ab0dd46a84af3905c7a1d329446cc23c1c5adb401a86a9fa76aaf577f77c2678cd8de8685ed4 +00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da,0000000000000000000000000000000015ffdd83355978ebfc386e13987effac0137ec628fff1667ede29cfcbd05e31cf8323959dd0247c20cf28978dc242c790000000000000000000000000000000016b1f810da2ae3c2ffbb6b83c47ef03eb0f298ff4c304ab0dd7b97207949d62858458d789c86c0cd474c34fa720ad3b70000000000000000000000000000000002a2e1463d5e795e6a25998a848b079363efc7d0337c3803385f4f17f11726b04108adfd87a811d709cbb6750c969526000000000000000000000000000000000289a3f472799c06a84bb1f377a36bad910220e1017884545159fe1b2505e8e7473882fcf324ba0d9125495bcbbc7226 +00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833,000000000000000000000000000000000b02ddcfbf391a2d6953261c786945093b09377352473a86cfac6456a811233809434b566b9301eea3105eb86922efcc0000000000000000000000000000000015430deba91113b841303120f0738012d77207e9408474998df5e68d0d61f1a64afb947ff93116ae766ca5325046e263000000000000000000000000000000000ab7094055919f6f707b458cda552f25104d95e4ec8d020ea4c17ac1d7efef5c4c3a769120718f1d5171eb8630a3018200000000000000000000000000000000161e7209f8c98e511a698fbf01735798cb632ae1afe00870654ffa0ba93a549edf4b97d60f03974ab0964cd39298401f +0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f,0000000000000000000000000000000006cb218607a1f66ce361c89fd20edc3f00421611adc9aa52ec35d45e023174962c863f740ac36c984c2b466cfc4827a900000000000000000000000000000000152b22d46e9660da8b1be4c5b14da613731e750ff7eebaf879f7074bf3c33e1528a2c8479e0178707e3855b49f85f045000000000000000000000000000000000c928cf78cee2c8b9da8215d33d189c5636df1e8e9bdaf143aba7ed40f29490ca2328b4a20cfc56f62e4ce49d9e77f14000000000000000000000000000000001574b7a9c3931933160ad4eb17400b6297210db47bca034bc1b5d17a0cb8c41834636b9123e625e5eb0b01738cd6b9af +0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc,0000000000000000000000000000000003e17452a80996203fdc4037db072c452f9eb2dae689c77c88b299d7ba266d111ab2b9c4b24149968d72cd143a34fc4e0000000000000000000000000000000014a057d7a50c9b0f34712ff8008770080bfa671650fef43c82726257da180dfb9672b266d4c54d65fdc677d917e6c5b80000000000000000000000000000000013b452c980bfc4a484637b578be100753aee9dda9487d5ee5c017c689dda838fc673804369328192d780d60a9a3de0f700000000000000000000000000000000103aa86d1807de242a6d4fa4a49be6c91cd757df5808501acfca44940733c6a524b851ac962b99a9be41bfc8d6254478 +0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52,0000000000000000000000000000000007c616472f9ac60f749979c6f870b587425d514395ed07558ed287fccabc77f0c90872f3885d0780bcdfffedd124eb3d0000000000000000000000000000000019531e9c25e84a2a968a85d9f1ab61a372ebc59ba5bb7a2bbb3c0d6e4c9d04061b28fdc719735e97ccd5f7243a58cdc70000000000000000000000000000000007772d3cff12bbee916a6569edce0c6dbc2bd8a794919a4dd7bc37024c8273245210511b8f6da551fe626b7b840833f300000000000000000000000000000000186a3e858a83a7ea1bfdaac65c2df1076059aaa193961559792373886c68acd2f9fca61b166a0ee55084a6ea122ec3e8 +0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1,0000000000000000000000000000000008adebaa95d10b9fc0f1a1f0d52dd6741517d2ba23e3f9e7a9221039684ae226ea602dbb50df0efd44b2b5bf7495c0b50000000000000000000000000000000008e276e78ead2473602d37cb9f2f589f9c60514a1fc5c215acf487bf57c935467d29945d3d671b41a8e47c9495dbf5c9000000000000000000000000000000000fab06240cb8cbe9afcc4ebebde50c2881e4bc4d4f2ed09a1065e3620e6344fb3c5f3019250ca4edaeae4902abb7400d0000000000000000000000000000000003fa6c48ead374be1dd45c8417ca8234c15ddefc5039151e6cd7fb27f866e134cef2f59ac9b2ec1b26896eaec9213549 +000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9,000000000000000000000000000000001412bdb48546014adf3c4eac4dbe79ba700f90c8030b063828fb01be5977bd73107533a4e8030c8d9cbdde9bcf10649a00000000000000000000000000000000126d3e1006abfeddd810cb1e12c898cf5f543e414438e600ce4c94cd8dbd1e17c0f3b9831add397feda74362eeace6fb0000000000000000000000000000000005b3159638afa34f219513cbcbc51567b16fd5598b85e6ae0d232021133cec25a6269250df2ab7b5ace726e9e2fbf0b0000000000000000000000000000000000c35bfdd1c10e903da6d41e9afbe65b0cd66addd7893fde41dfda8e543a93938cdeab52cc9bbdbe61f93d651bd1c923d +00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b,000000000000000000000000000000000bcc781f144bc148687875789fd8c54dd820170984b6f8ae75855f7e45619c1d2ff85c330b7743e447b5fc831dce9277000000000000000000000000000000001409aaf3c94c9a6b5123c82a7f311af7c2f60e9b197d49fb5b010f84faff972151b383a83c106de43454f8097005f6c800000000000000000000000000000000064a91226da8b9cb587030f1f4afb0d422a51e4d55212f26c621abc06fc0c57a473a9be75518a5f4f9a7f8d4aaba69830000000000000000000000000000000002cf239343bb77865ceabfcc1fe34cc9be4a1ebc3a70f16f8b7cb84eed5843524f95673b01466d6cbb0d8d9dc00793e6 +000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6,0000000000000000000000000000000006bbdabfe104b62d22e78bc8f3446a86cd5f10c4c5a54501140768b55a7e6940b9952c9a90a14d8fdc7c04600195cd6500000000000000000000000000000000172e718c926cd393bf303984518432693c304a2758174dabba303ff4c0289b5bf5376b61e8821abab322d53e88f71d480000000000000000000000000000000000a2f84fbdb5b05107a0a340e81b56ddf6d03c23848448f841dc44f07cbf8a575289cf6d53986f581fddb0f2d07e38d70000000000000000000000000000000005cbc10f143a9a1fe23f670a4c47d385f5c7069d8c46580322d6939122b2d39d185d6a8c2e51e88a1d40fd2e82d08b8f +0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36,0000000000000000000000000000000011769e191fe258ffd1922295a9fe877ad5a52fde6e343730f8f5ec6cdcd584f8ed1dbe0f55b5dd81f5f78b7437f02abd000000000000000000000000000000001253689089e9192d10a45342214425de36740c120e49f596d24658941ce2b2ecfb50e879be0125e3d159088f88e234f10000000000000000000000000000000017b642d1b5a953f47fff8f0649263f16f41a0ec0397d5a81571174aeb85431c352e2bf6bafa6894d2e6cdb5eafff16d40000000000000000000000000000000017b3438d0ddbd2ace1e63802013b5bac00d31889dcb2d9653a6f6412d157aad2fc45267322a62129087380bec65ec169 +000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc,00000000000000000000000000000000089a07bf63b8029e0506393828d8593b94b73c750815552f9a3c74ef7470b5810bc27212ba02ca6fdcd97e1e28a52a1e00000000000000000000000000000000051a93291d4b912f0a594d45c0264a9073663a9ec75e6ee81e13e79383d96e9330bab845fd1e5163e5b28c41c4a854c40000000000000000000000000000000016610bf2b2006207046e489294a132937edbdf95caf508f0df3bf8502e641aab9c44903cde75cff3c1f86873e06cc58c0000000000000000000000000000000005d33669fd8a6256dc55f513bb93cce8bae62a593eb8903cb7d7902a7727efb8fb4bb2e5058441c30b99f146ff5394c3 +0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792,0000000000000000000000000000000005aa23543088a9a833d773a71275e73fc3081e13c907b8a04a330df7d6c06618fe69e644e0ee55869e364d3561e40f300000000000000000000000000000000010eef9238d2c520f32243f07161f3e35b15fc949b9401baa1a9c5df7d50b2cb3bdd237747735b235862bb57322fd9d090000000000000000000000000000000012dcc16496c95e39ecfd8f0514b5ab2569d89826d957478cdecd4e827095034e974039b37e767a0f25bf057ed715aeb00000000000000000000000000000000000d0593865fd2172ebf1b94c7511ab7d433a276bf833515146adb6d79b6e09d7c18f4c7f4d3241c14d01a4ad0f31580f +000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c,0000000000000000000000000000000015785bae0c27680cca2097ab52306207a61ba9903723f574091ef5e57c2e871e076d7f46e6e39f65a01e183e7bd822f000000000000000000000000000000000071110a384248664db46f21d87b455a3ad3c43782c68304ce17f52cc8579fb2e3378995d6eb3b8c97665e5fb7de665fd0000000000000000000000000000000019153a01c2b3c5d481474a71e5c67f27fae3232a0c8f1655ddd4da6b4c79870bfb0b6beb4af8c54aaf7e9251ad41d639000000000000000000000000000000000c58375439a93e0763467c6a11dada3e579ec53a968c9b9c1a446cf3224ea0c89c9ec218a8b78de91fc12f087e722f94 +00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa,0000000000000000000000000000000004c7495c03fc3fb4d0fd4e0e660d6424de9e060eac72eee3608ba95bac294a3a62d246f42dcf3b575ee1cf8e20a9106100000000000000000000000000000000091140aee42a9dc875f87f3ba29beff95138790140f8bb522c6c15281b3545995f9c13b0b73ae691317e674295db6526000000000000000000000000000000000a945a215b2861427e0fbbfc6fea04e79edeaa1eb87df5db8e5e017cf98fde7b8d5a04a1b2129a4aadd2e3924ecc0bb2000000000000000000000000000000000a43f8d3d92a03b7bd4c8a34ce31729ea0b8e6b051c30241dca2db31a02b6e537071a914d8f0876f944dfdb613540c6d +000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,000000000000000000000000000000001821e14e70e12c7caf2a1ab651eb81dd61c4e1eec9a02fe4124abb865a7029e066f03b62e6ecfcf0fbae5151272b524f00000000000000000000000000000000044ac4a7399d6a67e7ee8cde3f5fe20b0a745462c870926f0ce8554061eba5bd62a8a08c798d8bfe30fba5567d47c7ec00000000000000000000000000000000178b8f061ad9282b3b2057f20c115c91df994ac40aacd05b7669e934bc7d650a0cd88f9fe17d7b766e34bed587ead58200000000000000000000000000000000188311eea279ddcf75f8dd82643ca3efd560ddbe6c8f2696cf7da03e65cc90d97b9f9ce99e29269644d8b881e624cca6 +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd,0000000000000000000000000000000012496dd3c1278b55bde81f6944c4bdb71869f5e5e21db7b1425ea32fa1dbc8c301e7f5e68cd7629c91650265d1361e690000000000000000000000000000000004a1251591efdbdbeda21eb89165ca61a2e090a73426451b6933d939161364c4064a67a90f859a7713fb6a9c5321d5a200000000000000000000000000000000163bcd07d030fd6ab8a8e0bf39b136dcb34f03925c3fdadf55e94a90bfde0ecde5c51d2f4d06954aa6a96c913f2ab4610000000000000000000000000000000016dc065a852ef9e038d93cc583b4a71db9b96a7e7a819dc530598f1ae256368438f52e4b709f15f56279b9c7f9db8785 +0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45,000000000000000000000000000000000a2397fb3a3891d1703eb2112357c5fb8acb070ba9f3a39050be6f05b49b8d2488e94adfbf849c8b4a42e287077e9fff000000000000000000000000000000000cf2c02a97addbc1584091e411f9a07135f1fcf989dfc8ae29155ac90b214ce20dc11a1fc75dfb697694891d934abf0f0000000000000000000000000000000018fd4af647bf0456aff9ef80969613829f8eb837205df552aadca46bc3bf9838e0ff2515d3fe869f80d78e2357091d8b0000000000000000000000000000000003c5671ea4723498359f29d49ebe974099da3dd59d21065a721f7a4f14dc7fb1de3a67a707bfa4bad7058312632c6113 +00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b,0000000000000000000000000000000000676bd7ce63d8b58cc1e5399ced9b495baba4cef9503c44760f92d6d9e092d6d5308fa88144491eda6c571a8c308786000000000000000000000000000000000605cebb4c20bc9dff0258f75a825f55f23a32cd0804dce56bf3cf2f19a3504f0345e0f1b839d4d5920aab19b363ae19000000000000000000000000000000001512f95f60a6dc79dd9261c321328ab8e22ff314e7582d8de83aa3bf280805cba8ba6d359a620fa6f0564396a45ca9760000000000000000000000000000000005837474ba78e0700c77141d70af1d8fb95a97cbadc95996faa93c2e81b7c8877d08d5287f83219a24bc0080e630e39a +000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca,0000000000000000000000000000000010b2a9b32e431c11ceb474942bbbd6915a3cff64a74d67570fadeb7447c5abcf1bb35c822d4441565322ebf75e61f64c000000000000000000000000000000000b75a0212232af0a59440482a1f953cc29bcd35272ef407925eccd70c1dc4705dc1e97d2da604996d3c52155d05d77500000000000000000000000000000000018751bc59f5907cbd7f1d503bc5aa266f4109fd3133a1c4c2e58e4a17250a40053b4489da4825b4c368b0f4947baa6240000000000000000000000000000000019b41fa1af9488596b09c587fc33e044d51674eb6087c647d5a762d85e38a587eb5482687d9346a1a701bd3a8bd36a61 +0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc,00000000000000000000000000000000054836eb7ef9edbe914bc16d1498e0bc3c978bbed2518802c2f8e1c0b59fee482cce0ae8e805c33861d4cd595f6b8bf40000000000000000000000000000000007dda36d55aa7a890aeaecf2528a390c98d9ecfc8a5c78c2a6def30de55b90ae408ab770cf9a9a4663ba601c4f5765a00000000000000000000000000000000007ff7b24c8ed9fca572069e72b1e93978cea87a0fac7ba60f54aa573d881f21b73012b010e9c0fc9324aa7697bae0c4a0000000000000000000000000000000002d9773bf294efe64021e755e4dd2936a5060bbea5688b6369ffa3b94eadcc58cc3986c74ff365301be1e6c785939b69 +000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447,000000000000000000000000000000000902c1082ff09bf93b91c9ef5e447bd6832fec9297cdb065f11fc5ee626e6e8834cb5d74775c586609a0394e6114e8820000000000000000000000000000000018e414a40c27430b98246fef556e74dd3dd7adc601e3c05b79f8c29169780a173be9a725df3318d71b6e82abf97930bd000000000000000000000000000000000f924fa88f43c86ec98b34379b9a649c7564ef0dc596c95df19522fd50fb3a37cae031e891a7a7aa6a5e6a9062c3726a0000000000000000000000000000000006bd3340412f64d02d0cb3ac44d1f31cdb1906e56dbfb66d86b60a74cd26c1e241963fcd8bba4109c428db0bb083e81f +000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828,0000000000000000000000000000000001415fbd8afeeb5796460a9095f14a8f3f6fe0374d4cc4160f030710a6d4d3a92febcf4dad770de3a3ba1a2efbd858210000000000000000000000000000000015792220c7e53262b56224d230a8a4b32019c77548704ec16da5ce167854305e6cdb9924c248f222d6fe95a8383af7890000000000000000000000000000000001694329d8e0f41256b703a8bb6548f1d9e0749a55c124c9b60361b4cb1daee24fcf272327ba598022a92815764fc8570000000000000000000000000000000003350658842c5b6fc5561a14df27d950a00c5bcc13d6d9d014bfd6dc95ec1a030594625f41d439b90b05275a0ffefdb1 +0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a,00000000000000000000000000000000054c6cb26c8b0a9a4700e0b95348e6fb1190c577eba03a44e84fe7744c543321d02c4d8f55c03f984b44ffbd899ac53a000000000000000000000000000000000e7ab8da5d573cb88a78f6a6ad2b307bf867777f79a643b6ec89d9cb208711c85d7d2cf8f8ac69a8b322000fc7866024000000000000000000000000000000000fbc5926b9dcd9e4d1ca1a2b43dab5c98aa20b37aff0868c54441de44eb014e5283010642717fafaa95000f4313e14840000000000000000000000000000000003671ee05bc20bead72f2306203dad55cf20b13d3bb2cca079bf4391411b85ed4df55e1426645d73b6935889d4450c58 +0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb,0000000000000000000000000000000013fdd394635f42a926a2324b8cb870b5995772ef4e25ebc1da41dc5bf724f747da8d95a28dd703b5ed65ada5555c8b5b00000000000000000000000000000000118fd550962d1de8f1e60c312643ec7cd306f0bbcc932739270595537c8d290ca7e20b962fcde570bd2ed7ea43009fe70000000000000000000000000000000018b25fef4b75fc7649a489d078311dfb6da9909f472de7bd9bee9c3ee353f345c83119269ab797fabdbede41e0fe6169000000000000000000000000000000000b7c2a73741f6944ef4ce8fa20b2900612645c224818b7faccf6597827fa07f7262295f42be5f34a751a6400495f7eaf +000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425,00000000000000000000000000000000177d29de8a81db2e515d4241e5f7e3d35de22bbcf9aaa616b057cbf2dab57ab8d98213cdec82a2034964f3e1def8a4e3000000000000000000000000000000000a0cce8113eecb064a60ee2c470dfae8b3921f8da2c7ad8dc918b355ff44542b007add28a44848fa8d8f8671617431ff0000000000000000000000000000000010470fcc723286327e951e758fd0474de394778d0c1ec5fe6f263dea1957c60f05dc8f9d82b3c6a7d73b3e783f35ade500000000000000000000000000000000098a6ed331f03da7ccc9148f07b19b132152e15d9fdaee5cc092524b33795edf2b458b4e8383c5e29affd3f025094033 +0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96,0000000000000000000000000000000018a1f1a60172a65abc8f2d855ee7510c1e0af9bada084325027bd493ae86ea2c62c15ace7f63562a82cb80ee7095661b000000000000000000000000000000001736b977fb52eb1b466cec3d42df7e89047784f0e8362eb6425e37adb1e84d0438f5a6e82c7b31d59b0959a5f4aaf9310000000000000000000000000000000013ea0f849830f8e48161e840295637d8596b32eb576560289620b797b14bd395d835e8140b69039c904ef1d07a82127b000000000000000000000000000000000d7f58873701c138cb7e18ffc36cd0e47b07d70448ddd9fdc4b947003fb29cba0775916c752d531e527ab744c277e5da +000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc,000000000000000000000000000000000290fb3f38937ce4439ceaa21cf3b31db8a22f9f5ad9db0fd7d38ca978192bc05d41152f8f86ca7b2ee0bb58e125f57f000000000000000000000000000000001775913fc24699bf08f25fb946fc6527178ebb821c654b7bc69f6f86b5168fc42057a5d3bfdc53b3d57fa1ac05f7a0930000000000000000000000000000000017b9043cde8dbf500ad90463250a49f56b35713f2fd9a35d8391fc36c78c083e39674592a98cb857194ef9e73a62a397000000000000000000000000000000000e5e62e39433d443e7d2d32754d2ca2556cf6deea45e5076ac040e3d6de14e9965c53f8c65bd98ae7d17ad3a26f3accb +000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca,000000000000000000000000000000000d9927347a9ac9b0290e68143fbc6a5f4476604c3fa5ae87e729a03ca055e4c6543f9245a4592e195180d88781e46ac900000000000000000000000000000000175e0ee8de4002b18f32f70f1bfa9e0be87288cddf1c436428c2969884112bef5db19e041cbaeb23596e25cabea3777300000000000000000000000000000000074ed9e981818102b9ba818d478ba27033eb38e3fa19cdeb9f5820e59a64dc451342a160359c54bc8ec7d866b62080ef000000000000000000000000000000000a853930020bf01e20816d3aed242e00792b0d0e78fb15403fc3cc255f0dbd99ea6ae1d59d5978e562be4862b3317324 +00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a,000000000000000000000000000000000e9c290ba8a22f7bb3f7dfdcc9f5a221a5ce838d4fa85a00473a4dd830bacf583dd91a6a6f78d2ebb54a4c1bb217f793000000000000000000000000000000000dc51b0ae8bda6d28c51016764fc028258171d7c7646393228692aef7f1dda4a83e53553f63d6ba996d4c0a802bc967f0000000000000000000000000000000014ab155029dd35206811be9ca4efbf762a1673367e6b57528f79eb50008ce7c3b49a2d25da0ae68ac4030ab4bcc0daba0000000000000000000000000000000008cd743bb52e7908aa973c8518eaded75fc2858f4edb25fb7f2e09900f0abd3ac87e93cf1068bbe0c7d99619aa7a6b76 +0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4,000000000000000000000000000000001746a449993b0684740630f3f0e46eddfa135371e33e8de4dfe553c78845399e63bb3da48798b35df48d27e1f991954400000000000000000000000000000000057e0fb1113968858981c9803166d8b3eacc91bfad320ea0e610fbc5b276da1b46d74fcc54183ba61d1b2fe6743097c90000000000000000000000000000000000b3a178ae3b739cae3e80f3f44db42d8c465a5cfe4943b449d4c3b7f4ad153916c6cf4fdfece14a00b271222c72764300000000000000000000000000000000041c8b293ded0c647f2e4d6f9b35304179b723c3e6e421a5cb103e561d1655b92e74877ce22c99f22a3700c3aba9ebb9 +000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,000000000000000000000000000000001103cc395acf81772955bda38f951a81c5a6a476c0b5e1543616a5a7a7be22dd487ab2a8586524891300adec5225b4020000000000000000000000000000000003479a08e2811ae9aab0301d66ada470935984d7466201f3fb28c610c0b5f67e7305f5ad3514cec5f30b51d0aae775d40000000000000000000000000000000005ea37a6d20c1ad0978da68ded3a5bfcc5ad8fe81e39b525fe7d1f2b2b1ab0be7ada80173b1d0b7fe1e06ab6354e64b10000000000000000000000000000000008f2093151a285dac511df1755e99a652a1cad0af3a019650fbdead1421ba8e84afc9eb0a4fea651f365d72f031a0ca6 +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7,0000000000000000000000000000000019f79677ea0e011e5c9a892a407646798b05be05337c73135cb771abf101f450bbffd08e125f077f5ea989decc009b9f000000000000000000000000000000000ed15f35966024cf1de2926108151e976dcb0d51b2736b0877d79de81f6fccb9dd299d14855f4e257cae33ab7455b95100000000000000000000000000000000125e2fabb5cc95c0a7890e9ff2b70102a97a03f2d11d915cf4332dd049a467333e12ebb27955c0310ebdfe2afb3173ee0000000000000000000000000000000011718167000f9b749f1615610a30023db4b986364da5bbdc4506c726624a073548a94307b282590cd8a43b4900a1afb2 +000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602,0000000000000000000000000000000005af8fd9e79568b46fc42b2c1bac62d115365834e509dab032f66425b7a571a4bd3bf702299d3c5f36c372750b3281f30000000000000000000000000000000018499089f306b3c9f7a645ca2f9aabc4e57c046992fff87e832e21e21875c6adaca050ea8bd7043afec3a36ecf8eafae0000000000000000000000000000000000827fa0f46134e2dff80088129841f0469ec7360fd8b9864e9ed99c5fd3458e6360661ab4c671846681d491b8b823d200000000000000000000000000000000120f829e8d0ffc360a14eabaf52bc653b1e90a36c0a8af806ca745fa306a9739e31435039a377e0748caf5e80c2b0b09 +000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013,000000000000000000000000000000001745500b00e5ebc6f71c779ba0b0f8d6601a065c550ca19de9562455423d2ccb507e659b0dce982faa841267fb1a27d90000000000000000000000000000000009c36b54f12d130868ff9b9b61b714fb1067dc91637c09614c51b5aafa2cbe3ca7dce0f3e366d4200cbf603ad4fd630000000000000000000000000000000000172e543708bb853712d81c000c9f9f2378e628b4d13b074317e95deeae98e11e7f917f91e02a0b18cfe9b25f1b83f16700000000000000000000000000000000189fc572ff6a8c6606ba0cea7da7040898d9ee85a58f12fade8c5a22031ff26c2f9cc612bc6e1b82a0999fa93c6fdfca +000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8,00000000000000000000000000000000013c6f777df97ad3ddab9b7486d54d1bacb3b40ad3035b47a25a66c02e8866955e27a8ee52872c8222ff7466c1310bad0000000000000000000000000000000014a5eb510d7c743e824f4daab21c43db4d6de8ab2e825d13ae0e186aaba828d7b4a2343a11011a8ec4ea82f456e394a70000000000000000000000000000000017a55d3827b78a9c5ea792b705eba7777df74951930791b17ff5b861e98a4488f83007c073c3e904ed4ee328b6f6171c0000000000000000000000000000000019bae02f8d6f1e31dfa09f4feedd5217ade66f6e8248aa98b273574f72aef83d5048534ed38acab9e0eb4c64f4389af4 +0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b,0000000000000000000000000000000006490c327790b4c451f93197d7db24211a3b4b5f573a6df409206b4bbfc36bd10d2d0c989889efffd8f4daa4a68b211c00000000000000000000000000000000168f224738db3f07af77494f52ea5e957812a1acd62615f0eaa95c1d363cfceff29be9cf3be5329bb41175a0231ced4f000000000000000000000000000000000321f06b55f7dbfd4900b329c914f9ab9be2794e51e54498e18f83ece5bfd205131fbc254bfbf624d57ec2954b05f6f00000000000000000000000000000000018ec54f3e09bb2a6b112b575f9481bf1c85666133051e9c0ab53369d14eb90e27d2ed02dcda1250d5d539df0d0cda37c +00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244,0000000000000000000000000000000001641b4ad10da5089164809d82ae47f74e27eaebffc2a2ca3c1b924fc69c1ea80ba3da78c78e86957f6a24e7f75dcada0000000000000000000000000000000014e781e4fe79ea1654460f4b0daddaffb29b287efd8168cb20d7ac6c729f684c5f2a7cfa87885accee3a797febc904c200000000000000000000000000000000001c9a44547f0c5b1f4df190285644c5a31df61e3de7da085835ebda917d5e4163f2deea9a83d641a4759fa3108567ad0000000000000000000000000000000014c3d2a79d80687fd6e6aa423257644fa5d0cf641aaf6a7c5675a810767904166fabd9a2ced0727e3badb932e46fd181 +00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83,00000000000000000000000000000000129554de7de9a2b73340d94d96f0356a2d1c0524cfb007d76a75f462872e831f45553de05f5b6a1f9eeae37af7f6b4c9000000000000000000000000000000000b1ea2a649ca13a3dc7882f2423036670f68aa05792a8fcd72524420e37381a9ca80dfea701fa5e6da57afa534059617000000000000000000000000000000000b7ff27aba408f9759b5109600cff66c03cdb4bfb3dff64a4838d0516fa46bfcf429fcf9d5cbf74a27f70fdccdb1238c0000000000000000000000000000000005a99aec88967fe775c691d443e2dbd45080eec97e686ee6d7b32e801efe6563315bfafd5c7622d0543519cae4417029 +0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852,0000000000000000000000000000000007997a499b2194cab634750a189cca6783ff17d866d66f5998603f8639d2242e8039222c65b0d14001167a9b09afb58a0000000000000000000000000000000015050fe6b335884a225efcfea4acd025cfc05e8f5fe9a0e22a0c91b55664c118d79887de91f1ae6cbc081f6f55f0067000000000000000000000000000000000195b23c4c2c087082c30600ff00485d169dbd360643d163f1db363f270cd7d4f177c36b4c291d50da4101e67b229d0de000000000000000000000000000000000df596ba2350ff7d3e75b4cbe5f8d6b2cc0e14b3bd6dc021936e3371ba64031f6266fb1d2951801309f22bfb1c4b27e4 +00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c,0000000000000000000000000000000001fa243b548f8f5c2e5d7736ca6fa95b74dbfd31f95fd532b94f81a255c73e7c0e000e20f9ca6750cb0dfdcd2c1aea8a00000000000000000000000000000000132a893a2326bf61962e1855331a53667e6279ed7358bc84c4a7c218b6cff1d3f449954f56daea72bc2779c60f1113400000000000000000000000000000000000091dd23c75dd8266f556bf27ba54c95c3ccab06168e4e6d0747239722afb20f3db27454c6db3a88daab0ef10659a66000000000000000000000000000000000d3b2e3fd358aa3dae983e87b5d1fce6d5688e66ced6e3a2c96b8d48041557295d5932af6532c13965d4b383fb252518 +000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0,0000000000000000000000000000000005095d1becff61df906815842112c6508d6cade4ef5f4b7418f5f01e8c5a383addc1c572237613dfbbb88bcff80e4a44000000000000000000000000000000000bd2561e7bfbda8a48ee038855e37b03fee805689452e9afaf0da4185e0c194e407ce7149b713c689d25f953da36dd1f0000000000000000000000000000000015ba3ae4d4238175425ac5dcbd9e6e9e055b8c1b7752931b524fb546f7bee8723ef2e69351450c6d1ba3c366a22355e20000000000000000000000000000000008c17d77dcfda00a1d75ea0087c58e74263ce5ce4066e979c66397de8e236708831c3a9ca6b35ade8038a28930655eb6 +00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45,0000000000000000000000000000000005cabaf39b93d7fe15ef6a7a3031df58219bce702a5a77162551a3d916c22e8ec9af2aa20659e7c4ce5f6382a5f82726000000000000000000000000000000000dcefe1a48d8c239164b54771118f7520ac11a7a6b72d8e17be1cd788cad2f26d3a0d9113e6536426800a744be9f0d4000000000000000000000000000000000199d95a44a4334c87aed273a0184be9602ba443d5b8d34f3495b04e927f4687fb88487f586395c7babb4f218fdbecf8c0000000000000000000000000000000010972032f9cb3e8f45447bdd06df82656fbd3ce38a9f7564c6e5d62ea3596c9b7e0a94046f1c65bf0452ca25b15a885c +0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f,000000000000000000000000000000000f250b5e47ef616be106a3334e2f516061eec8f7ac69f08f6dfaedecd76fb1c9685ecdac2c3ddd534e3947d007ab177000000000000000000000000000000000073819a6de38303725aa3a9e5a7a9122b4d1e60ee8deb3554b5e06ef5e60d71517c2279c5066af003b32cdf83b7fcdf200000000000000000000000000000000070721107ac6dac198f7ed1a7f84697cbbc3199a220d1aaf82e6f015963bad863f99190f18a482f730254cef753ba22d00000000000000000000000000000000169910eb30b8fe1ad8f84c4a132c6c74a6ff06ed6e792af3baa6619e3c8aa6cc3e6f687299467ec9554f9e91bee77aa8 +000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d,00000000000000000000000000000000106e892e336b2155909946ab73b980ea761cfe8c48b13ae8a5302eacea08b9cef3e60d5b33c6ec4033218ae5761433dd0000000000000000000000000000000015daeaee59f3b4cc26d3da745661e74db8fe1ea115d50ba49ef5e6151a9ac2f3135f0232235cac7a53e1e8a70eaf0476000000000000000000000000000000000ff494d17c735b934c2c7fb8f413103188fdb116fa8f4d4e43262968ab0fa1bdec23b0d4d8b1c2defe624092de36610d0000000000000000000000000000000008f70b7e9f2d7083774fbce3bff58a1c73fbcbcd9cb049cba71c0c3f0c363517c8956240bcacdfb7934d4c67b1bfdd2b +00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a,00000000000000000000000000000000098f32b35e3b7dc1862ca1ca3c76d009f016c6b91c227f2cebe8f1fe87567d936bf1c54103bec31b3552c077c0242fb40000000000000000000000000000000005380a66d48d348487624a15b63d2ecf6976b5b599901101ea8b1f57736649b4397f6679ecab0ae29573695a921ac475000000000000000000000000000000001710c368f70a2b9cc92ec65c4c2ca35fd63440eb350f488e7c6646f9c42bf680eb62a887d533a91e47988221b46c868200000000000000000000000000000000033c3327da938dbe4630dbe16838229d7d427f3adf18dee6fa26b1c8067838922c1bce78cce08d590ee1acf2baebc7df +0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997,000000000000000000000000000000000404587c60a4bbd8b5b929ca2ec2a9ff2ba4733f4f2877478a669b238d65ca130cba398899f2910d6de04615f8ffc99f000000000000000000000000000000000940659b3e6de7c3d8de9169a28e68dad433bda78de0991fe4a1d404e5f4babcba9d57c7f3d638aef264642f87c61fc8000000000000000000000000000000001676ce240e1ff70ab03f94f3ba3acd31725ec306ce1fd707e29ec22cf91746216dd998d03ba13a79dedf878fae38d68e00000000000000000000000000000000098a81422511f77191ee15d402614c86f9447ab78a89cc348414108f36857a1929f2b92ced78752ab3604f276861803e +000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,0000000000000000000000000000000010a4ba6952d22a51dbb6762a3f9bd09712c2be5a98bf0ef298d7a7e3a9735ab0d3bf39e40b334895c73a36c218ad24b50000000000000000000000000000000002860f38ef61b497bdaf4faeee7b406007981c17246cfa36cee906452ae85e1c1c6385898ebadc3b4ef8887fff25b8240000000000000000000000000000000002dbbca9034fb17c3f37727d44c027cdf47c36f3f628ea9385fc9fc371d23f22d983656caafbf1cd1f8bdeff4ad7669d000000000000000000000000000000000b7e71b65765c4113a7884771952268a9fe10576f745038912e6877c78372cd261220793b888c43accba1646e902fe14 +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d,000000000000000000000000000000000e9c1a6d591be4da37fd6dc283b8d899b625ccc96371dd3d7731aca66cd2a978810497171f2aeded64fa2b10e480de2100000000000000000000000000000000006d2ad7287847255002480627782d513eaf1f68a3d583d4762fc156b8eb40deae6969fa8a7d8f8aae923800091386a00000000000000000000000000000000003c7eae0eda08df9b9eee2605a44fbb486e3bf2e409aaa1c8f38c06f969ff1f74338004b01288dce99be26a837e45d3a00000000000000000000000000000000178174d2f569a9392eddd2715ceba8762c5bcc6325217db5e5f970d6fde069d0e48a824e5b6ca017891de175c92f6b29 +0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616,000000000000000000000000000000000ce12c9010b4c4afbddb459c1b46063a8488277948188b4ec0b739e1cebb5653681d0e43a0d2c6b3f842bfc609bbdee3000000000000000000000000000000001123f60cedddaf4385e63758d64d4facdc443854176ec199ca0df0a9c258517f2512594f2441a4b9a68aa9a2b4a1f4bb0000000000000000000000000000000007cc6f77d181d13bd9736ee23a33b25b0bd969760642ee19004e095ebb8e2b3c0e09321eb15a2f7961803c0fb10b6ffd00000000000000000000000000000000004d8dbf2f0c14b07ebed2b9cb4bc87df78ac8a34ef0b05cbc2c6fb8e8156415399fa52dfb968ef0e6ec697030fb003c +0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af,00000000000000000000000000000000172805bc715a8cfb2e25c384214f4005aa6d3b809a0ad95322209851ef92151526a9d01a914c4d7f0c120b9bf3837010000000000000000000000000000000000473ceaa092a5ac12f38b4065477672deacc08e553d8e9e6391bac0d9ca50015934cdbc340deb05aca916cf50c7915b30000000000000000000000000000000012e85461fbd26c2d0235acf5c8665750656819bb939e8fae77a8d526ca23443aee395a985cdd4b1eb700311fb87e91a7000000000000000000000000000000000246d45fdd88448c93bedf4799becfc7c80e67abd483f2a0aa41e8bbb3f38cbc900314436364f1db6e1d88595544517a +000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145,00000000000000000000000000000000122e1f2081cbde0055fc34d2fe61307bc333b35a1e0772a0cd6fb25338c89824bcf2f066bc7b571b2fb314ca7f45106c00000000000000000000000000000000027ed81b54372d858a6ba2faa65fdc132efbca6ddcd56c3625bd9267cf0ae04f6d342209b995060f584be8d40020669500000000000000000000000000000000002a03427a093a3000a1bed9eba91a82dc2f2fcea1a16a1fb8af29c4988b589abe6a505ec87a82864b3c683beaa6420f00000000000000000000000000000000134bf64871d69a72e42766c2903fb4589b84d7772a62f7d2f8f8d02a914f4d3a278c680c626ef4d69de8aa88b57589a7 +000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a,0000000000000000000000000000000018fa44efeabbd1cc47dd9b1a1195ca921c99c77ed43a44502aad27b6c663f5ce2623382c3ddf208f42e3eea741281f4300000000000000000000000000000000138d11e497e3c5656bc8fc0ae4322a0bfb6fc20e249a47a103b164aa3d9fdbf7df4b1e3b0842b4b12568a31992a151f000000000000000000000000000000000182490d6ae35c1208c0d608984df4988d057f3ce5a25073c77cd5b224a5892768badb1ad5cef8f41d1d2022573098c320000000000000000000000000000000002a6e0523781ccdebb75063dc7ad1a9526f9ff8ea1364bae487914f254c0eebcbb2cfc3715fecb9599bfc2f5feaa62d2 +00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0,000000000000000000000000000000000dc7488491433d5b3924105c01ffed4f30b755d7253d867fda595e7d80197823e56e4d182d5ecc72d8ef1ba9bca15a310000000000000000000000000000000007bfeeadd6fc468ef6340a2b394c155bf50808cb11e89adb0de5499fbdde91760e9531c1deb23050286a15e5910f1d5a000000000000000000000000000000000f096db706b08485fd577f37b7bd232b5a10c3f80c25bcf82f7a3b666c6efaac8e856bfe5f7dafb7457e33eadcb4133d0000000000000000000000000000000004460d1f25159ce6df59efbd7c693355af4634dadeaee2ced68124b2a887698c10e9c4b40c4f4f9c8444acb881ceff65 +0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258,000000000000000000000000000000000f1aa4a7a22c568c41270d24824138bf9ffc763a5356b7c0bc1d051a0a0db12616700d9214972b63eeb2a398d27dc83f00000000000000000000000000000000020d0c2ff8f93db6b415c2a01712034e46bdeb6e665a5177a3877db9f5401d3dccb99907ef843062e394c1428983725a00000000000000000000000000000000088abeb6fc3ead45d5b261b7d684f168ca8f5f163cf338863e6b102dc40e2cd0ede97c47460ad6f560c27e95c8b71ca8000000000000000000000000000000000ca2e5cec212d581c737928512118e2f51a0d74070f40a998b7b06d22b9fc754bb2fa5499308058be9ab81521d057414 +000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e,000000000000000000000000000000000cfa23c46881893f6c50d238a83669deb520a7fffab4f912f77df7cca43f6827a1a0ae0b3f36c8f116ecefa33b8bf37a0000000000000000000000000000000014b7e5c18d2f9bfe15b0c1af3bc6e230039a341e135837d123e91cde9cbcda298c66b93f692232c912e5d7d3d6331c430000000000000000000000000000000009c8984999ecd3a4144ccb925d3e5cae5c1662dfbf8871013b1cb2946482fcb075c489c61b8d6261f2574b44da3fc1ce00000000000000000000000000000000196e7feab383211e4825cf98219c63bf9f45a72d66030219cb585d5d25237a01a97f00e122db6a51325022e69e7d8cdb +0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83,00000000000000000000000000000000005c0282830934ea09c9f51b52cb6dee75b874b155c63076dbac2cbbf220863d55557ff1b7d681fa185435df1522f49d000000000000000000000000000000000a1680ebbb185c8e7d8a197a523a7a5e618f97c46670622034d312b3eeef140150e03b00ae3dff8d9f1d872f3d3dca380000000000000000000000000000000019bd2eb4bc25f5aa6bce206f0683dbbbbb002098a118fcfb060c1353a310c2baa1063a782bafcf6ff6bb8edaf6f1597a00000000000000000000000000000000082edf49a0435e0b9f3dc7f207711d66004ae688b18f5b62fd1596899ee8edfaac7da38973d81f12200018fbe8151572 +000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07,000000000000000000000000000000000b8a715c1c2792a30f7ad752a808b621c34af1fb7f1e3392a36ca9481a019108a21e3ef338a1d05f2f23ac3e2cc42ed500000000000000000000000000000000101375c9de592031c55a7a62189fd3fa3c565abf7c64724796dca3b1c7a6e6834a16ef1c4e2afd6ce2e69487260f0028000000000000000000000000000000000cd385ec8245431d3b1aff88453db7f66a5d7888a5c1e0dd0abe9ac7db752933a343b8be53b7bfffb704768ef0a3dc5c0000000000000000000000000000000015d55c8cddb8715e25fa260d1e1fa672ff76eca7c80d19d00678fb9d08759b810cf266ef0a7e9dd749a576ce07240fa7 +0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6,000000000000000000000000000000001311de31229f1825d0bd2c9d726fd71e05828a20406a4705ea65f441537486338022bac4e552bf3c25e15717bee00ba400000000000000000000000000000000052e082cbe36c854a028a041981fed87d39fb218a88208aa1096e260a3932a1155db7f306c32d133070b0a5bb6d161760000000000000000000000000000000003269d4afd20002873f4305018a4432c1925eea28486d657cb458198ff2df9d304bdfc7455233243b1712d8663591d460000000000000000000000000000000013376fb98929cbe7f7d090d1c9d5c4f6332bbf25470aa03c35a70481931e4bc91c937029a5e11d2a3418eab698361227 +0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659,00000000000000000000000000000000021166263d1a443d5b2eee9aeca3678ae4c2b44d556a7cb9631d47e4fa3bb05ecb94d6582f4ca0cd787027fb5f2efab60000000000000000000000000000000015335d034d1a0ce78e1246a16e35e0075f73d4a392da1e05c11388084cf80bf31d499e57c48f4be6e72d3abc7b387ec6000000000000000000000000000000000deac4ae1900a4e1814624fb4b8c7a3149fa9cff2ca97f02e7d6765e034a1532a7b8475ef7aef5ebb851063cf4b9e79500000000000000000000000000000000161e3af03f226278a07ff3b08e5788f6c5029b2c8293e7a7e3ae11c4d78676b60dc0208cec6b82e1714d976007fbb389 +0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d,00000000000000000000000000000000120b4434babedbd8ff295a6e2ed5fc7af0548d7e60663110050be797584c0cb638988201ae7707cbedf0c8b3dc5ced85000000000000000000000000000000000d2de0a260bdd241a145e3f68a6de48da4c65107a500e02bfeae6ae7dc428026c7c3e9bdda9a3069d2744705df2eda9b0000000000000000000000000000000018a237906c0e277541c4f00c4c2feba7cb2c9b87709c18b62b7c36d78fc118cfd65c127765e01dc0ae5875b9552bb45300000000000000000000000000000000197485daf54e98e097b6bca24b0738682969256decbf3ebc05f6982e4608829f37e2877937b3f26b88efc3deeb4bfacb +000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712,0000000000000000000000000000000005de82540aa67c69b962d292133b09e6593961da8944ce02557141abd19ac471f766b4083db85c67a44b65dad2202488000000000000000000000000000000000cd999bf3cb004074fe9f355cd8dfaa7b9d3439d902fddd2fd0688419b5b7f8c4300ab26b658936a90c0b8e1488249d1000000000000000000000000000000000f97ae779429a5afaf7a3343586eea84a4e76f00a1852ce42a4940babd565bc8d61bf72fca9b123922f1ccfb1db8c06b000000000000000000000000000000000935960fa941c27e74234a07857ee680f53c31047235c6152d1669724bdef37ba642cf4e0dd355443ea470e6430def8d +000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72,0000000000000000000000000000000001b0aba02b0e907c03d2f4003083c824ce60f2f55f70dc6ec7c7f81f3d0ef4bf533b4c94833e36e8aa7aeec18b7255de0000000000000000000000000000000004fc227a6ae303f3006f75193cef7c653e6bddd28fdb843b41c7d39966a701ba8fcf611efa71abf059d7d98833480e69000000000000000000000000000000001077fddd0bf3d5c80eec653916f9095e900cf165315d74a872219285f62b5412536e43c4cdbc120ec5c7753318852dfe000000000000000000000000000000000ccd90e01c1d4a00f0d9e29a88e8134f2cf68162da66bd343645a998730190114a6921c9b048dda58b60b42a133287f2 +0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,00000000000000000000000000000000185520023714580a3f235e24316478b8260565ffabd39670811519066844e131e337bd62ed2069bc6d2305e6638e539700000000000000000000000000000000055fc74cc7cd3fc393d5b5ab2419414effb783ff4da2516e5465a4acc195339c7b5238be4e0744b3d7fdbce46ca7f5dd0000000000000000000000000000000005f584a0311c02d611c15163529130a2fb3dc853083e7225b791ce5ff32d5ef7039c80edfff317ce9ddeef84443b5a51000000000000000000000000000000000f9d5acb355f767cc6286cc09f6df232532f9a0e9e4ed1fe28788abecb200e22066c23f3ac6c49c47071cbb023e70183 +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b,000000000000000000000000000000000ceb56d75f3aa1548c50d7780ea1e33c3d069b2f37e7f96be6a8ec03266fa8d0868822afb3b2e54750722266f6032a8000000000000000000000000000000000080f15b7f9f2c22f1afacf558267b5b84f3a6d199fd3349eefa2e46c4f332849c0955d19d4513151dc0f3b566c0058440000000000000000000000000000000013305f8ff6080f7da05c28155c0c2bc1c78d855cdcff0bb2c6b82cd5107d7a070d0830e6705f6832ed5baf75a659c8870000000000000000000000000000000018f4e136859b4ceb230450f9abde0325a4d59db98279d7fbab710305ff53250dae1c8789cccc27586c9b9df5c0c4722e +000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035,0000000000000000000000000000000002a0214be95f020c70221fb4fb6856af7ce3845a4b607340f85127b52f8a204efcd94a152835860a4ddeef84946671b1000000000000000000000000000000001767777740a9922a91c39a36e2cdfcd544df902b31812ffc88418dab7321f73406ab142055b5bb264c187f2d4f2d6f9d00000000000000000000000000000000026e6941364c74997506df0f9fbe6b2769839e8b7c7293f4e63d13bd7bee90ff779cf82adc2f23c569d1e13826cdb0e4000000000000000000000000000000001618ab2ffd4b823b9c9776baf849641240109b7a4c4e9269f3df69a06f85a777cb4463b456023b7001adac93243c26f5 +00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff,00000000000000000000000000000000054ce66b9b0b3cff6637d6cfdd788719d4e33516b98402d8fba54725309307711fb576299ba99104d4e7df0deac9ea2500000000000000000000000000000000055e06ff52cda9116a98ad3709f788d39db53844b7db58a57af52848ce1c59ec2a1f083efe79c5994b9291a2d1020fb900000000000000000000000000000000040c9ad63698ec78d06b41bdd6f5eed089b67f106348f9300f822a2d61ea1e5d2ddda0efd1025825c99cb0e243573f7700000000000000000000000000000000195dd00c48186f8d1337ca857aea02c4d199d638133e9cbd2dfc5f633502f656343746ec2a416465c3c0d4e9d53fd097 +000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b,000000000000000000000000000000001141b59af8fe6cafdf2e247fcb0ee4642a9b4022b6d71163ec9b6ac2f7d10ee3c5c0173ac686b03cd6a7086b039ec786000000000000000000000000000000000f05ba6973c5d865ac5c037583b65eb4eac826b5a04a7ebed1e10bec6ec7dca93b1c2eba70ee0189fd552d5023f2a87c0000000000000000000000000000000002e54475940985ad2115223c5ea3a4c95890f3e9992e3e1a6df2170ab77143bcc5d29b9dcd1ed3bf16e545e9be21a8640000000000000000000000000000000019acc4705955761518cea482b83e3726dea8d1f66a5f19b06cd7ff95828e15d1b139077e0d274b0e6fb86c027844d97f +000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d,0000000000000000000000000000000016fb5839fde95111742255b33f040c41dbd0f142d1daa8abc7c63008ba9f63f07381d9d6128240ae9b6cac5befad84e5000000000000000000000000000000000389a11727c356b8f3bdb6a73bc2f6d2d73d33d287365283359521dcac64f17810bd58c0ec5bef4db253bf630bdd9599000000000000000000000000000000000629a8af1bd0c1b1b6d7e447bb779663d7bae8e895e09418bc350e644d7022fa877496f30e2018f5dd1c9683b2715adf000000000000000000000000000000001950185d2574fe0c8277e3f93f59dc5628ec3487911ba9c3194a2f716116ff0bb9a39dde802dcfaa61633ad7657a578f +0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33,000000000000000000000000000000000024c03edb9b54034eacca4b321d51397348c57f406b074b16a9d6215e03f842380f5358f5c095fcf5bf3cabcbabdc260000000000000000000000000000000014e62dc442135d729f65090475fb408ebae132cdf2c2932582af887ed54696f3cd15b163f11285b99e8d8f809aa2e65d000000000000000000000000000000000438a2c99df216c67d92b99d9ee8cbd0e9751e538074d146767bde9675ae3a05bdae051efcdc6bbddeb1b7a8288370ed0000000000000000000000000000000007c462a8f5720e442e1917bf75fc3c3dafab6c39c80d0b93d81d1db4080f6e199be092b4b025e7b02efce4f30d00299a +00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f,000000000000000000000000000000000e8137c15436264b5960c27d0c22be7fc5d56a12f43b3832ad0d7f5abddbaaccefd140e2f7c476b99e6fd9b3a52743600000000000000000000000000000000019ee3caa56f0329a2e2acb8907b3edb21f4eee73e312352796b51282e097f9b10af61805d5c222332888737c7f8e227d0000000000000000000000000000000012cb9c610391940fed7882a5cba08eba4226c36eca8a2ed22fb5e752e0a1a5ec556673e47013258b499268f1de77bdf100000000000000000000000000000000031b769f606fa25b81a982db86a1cd442ed738019e7e64728ecf485cddcc17d9dc271146196178740b9f05f56627b061 +0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c,00000000000000000000000000000000080807a0570b628549629d2eeee39de773badaccefb76e01efaecb0ef0356f535d32c3947f0613bc7d847ef8c8778f02000000000000000000000000000000000e8c091ea30465d204ace72015cbef29645206390fd92ba7c4aa0fecae4ecee53c0b06e1fece99511efd8c7e9cff1a8c000000000000000000000000000000000c881c678c94d80164bb3295acf4341fe6c726ca64a1a015c890450e719b85720f41f80369f99ad3e7e3169ede0113e00000000000000000000000000000000008a2fe01a7100afda40091eb0b2b14cd00b7a4d8bb5cf9d9a3847970a94f2035fec7f292c04c38d7e49890e612830aeb +000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1,000000000000000000000000000000000d17f6d9460566d0543df2666d6ade685565e668521a87fabc58148343085415fee92c32907311c9d04713c34bf7690d00000000000000000000000000000000185da28f07b86885031ff5cda913a85b0e4d07673f456ecf2a9f0fd1b21d99e22442f9b705039252d57380b6a42912050000000000000000000000000000000014a4bde5973ef43691b61b3c0f6c2fdb4bcd6ea88e53e2787a7d93ad6e05ee2e69f2799712520f72b3c577ee278008ec000000000000000000000000000000000d92a565b3d8d0fded054a75198b31c521e3223650cdf762fbf7b851f7ac0fc66b8c86c20b905117585704c23b27e7db +0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb,0000000000000000000000000000000008b1ebd753364a5a0a6296ab48b348f91668525c0d5f7edc4f2d29844592f34a209f9e77f94ebb38ba76bdb3f96063ec000000000000000000000000000000001062e0ff0a67372207052e2520d8b2823764a5075c94011afd6c60288e187ec77e08db01c95dfa195f2409b58c9dc4e5000000000000000000000000000000000cc2b87b613d97a716586f371c457fa869c2b8d1fa1cf4b9e8c34bae23e0544752b997df4711d0712ec11d3a9d96ac2600000000000000000000000000000000140eae891c87c2026f0b1293df2bd8ae2dcb0ab3f8de74676f37c905334ac1f53fe4b75511691dcf108fca51abcd524c +000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf,000000000000000000000000000000000276a138edecfc9378be4e241d64cbb48bfa6fd4fb1788f8bda870d5ec8b2132fc9ec888ef84c43a50b7de0527def36800000000000000000000000000000000153e90d52c747859f88223555bc8bc4e8b6fc846fe7028de728a4dfa085c6e350f9f1d12b9dca4ca8e07377648544400000000000000000000000000000000000cef00e7217da6df0a6d85f40be69f154300c423e86e54e513b2491e65002e308445238082da69aa9e5e83b5f4fc17dd0000000000000000000000000000000008da1da2a0d1da9d2158b9408dd9b0eaf414d237b8219fa7661e40c1a88eac2f9735d0dd6ad67b85aab85952369e8287 +000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5,000000000000000000000000000000001484993096c210c7bebbc4c0bda24b44a70e982b2528215c0e8578ea55f1181472758caf935aa0a3d6820cdad753e2f90000000000000000000000000000000011802324a6e03c3174bbe7261ecf3812c1a97e1be27269214f232274a3bf82775d47c5fdd70fe1c57155068b296d394200000000000000000000000000000000050f43c874c1cfb5fda81059cb7b4808492632fa20369dcfb611e503ded81a49dacff253e31d7e27ee84bab79e3c5d53000000000000000000000000000000000ef945b6f210fb09bf0ad5bbd4b5a6630f43304ddcb396807c967eb5146741f7432bfdcbd7e5f3d29917781efb62e6ff +000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e,00000000000000000000000000000000028233bf12e8dbd8510f119be30ea1fc13b755c6ee3ca2a3637a3bf8f73776c9d1fe231b713396ffc579ef9320a05b150000000000000000000000000000000018e7c00b8047d64ca0c5df54486439c5fb3d1414c2f71cf8a3ed591b7c45bf18b37473daeeadcb625eda638885ddb9870000000000000000000000000000000018b89c9b6bf9ece36f1eac08fc35ffc9f7f964a0a9b19d495ae1361fb4bc98aef8770efb47d9961aff694b878d659818000000000000000000000000000000000eb2fda2c29c6761e35ca4c9772bb232ea0d297582af4f50ef76c0b74fefd414b535e356c069f54ef5224225e95be6e7 +00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5,000000000000000000000000000000001239935827fb2a269ab064a3ae2bff2555f89bb3a71a47ae815ef755fc1363a89d20326855cfdd0e13f6c85f727bbe120000000000000000000000000000000012fbba047478b5f5b07a582200271a0c331d6f76864f9b6c6ef8ae6b0965eda481eddaf72c7a887b21719164c633d39600000000000000000000000000000000017eb4353b413437244983554a639a9253d105395ff9652504df7700d879cd9a32d5f0824b1eaa532bcf2fea34f8f08800000000000000000000000000000000054ea45475c01ea0557fd143b21c7bdcab6d287bf6bf4f88b6fb06e02ac6fc5ba96f323bb1fda3a1c4d8f42d01d267b2 +00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0,0000000000000000000000000000000015a145e379b7ecf4566a039b753f91e8ad75d9e9c9a20725ce34a900eb9a1bdf66cabee2100208d7792a963d1fb8c02f0000000000000000000000000000000007f0ca14fc4e34bbdf5008d632dd112c7368e037ce019b7c4ec412000ac02302c85ae64f9ab495361fa5b620e46420aa0000000000000000000000000000000017c00a08bba18426dda40e773d79733030b5b3b199a62436ed06b773fd1f10688e8af00e8a223cdf242bd1ebbedbf634000000000000000000000000000000000a17365cd9f7655793682b72e342227048da0cff88f6ace33ddab548ba126017e4b7f7439373a893e3b5803e662814b8 +0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,000000000000000000000000000000000081b4dc78b74250a82da9d803876add659411cfb467860b2ac6f0f68929d6377deb71d6acc9ea8fc8c1286b8f92056e0000000000000000000000000000000002c5fde71346a255ee9dc896f654eb2e0c66f4cb4c51541d2bbccf2463ecf0085a22b9d2bdc5bef39d80c4477824f116000000000000000000000000000000000ebda0cd8bf6ac7e86a1bdbe44ed1e15f8ffa1fff92afd67fb564306882f35037b61cf0d93f278f15149c04a2e83041f000000000000000000000000000000000fc38aa811f5ec015f10a99bf175f1479d4983c9d2180a5e3da88b4e9b62ef50560ff0a6c2fb7bda4c46c54551f8390e +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4,0000000000000000000000000000000007b46fcfb2cd8efe32754306ff2f503d7434168c1c3cbd7c80470cc5a5c8bda10a80bfc0129da349724d2d6431c5ac90000000000000000000000000000000000e1078f4f4ca993d90accbfc036219507bd22d00930ffcfe1227780c00914fcff845698b2541510daf59cc83d8b947e7000000000000000000000000000000000b7c6d9951570e685d3a71b19a38f5485f974f85fe8cd4b4c196d33a18750b278b6d374483d81dc3e15c9b8b9b5dfdd6000000000000000000000000000000001003a239ea4a2f213f0f646bdb62cbe4f98cfaf7298d8b2e0eaa07bf3f939e779caab5ffa0033467c5b297166df657d7 +00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125,0000000000000000000000000000000000ea29b1e059560fec21c3692d4e632a45c88a807c953fa23dbedb271b049d7fc717333b498ed12573a896f872e795dc000000000000000000000000000000000de0d10c47df92010a6635e3403dd6e91a1bf35bfcae82c1008998e86aa2d18a6cfd3f2f1207fde3bb39b723ec4d3ca60000000000000000000000000000000005e2aef9cd37430b15e5e76b2c7870630d255f630c12e865caefe308a39833e00319406746dbb2af3ed32135e91eed49000000000000000000000000000000000c229fad41b0d27ad7b5db33188fa70b97f22e323e429ef65fcf98f5339e908c31df8859b863356e0fc90538c5c49cf2 +00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b,000000000000000000000000000000000444a00cfd258bd46f659b09eef17be9929008d3d1c65e46cdc762eeaa2f0b52abfd636e6094e21983fad8171194c71a00000000000000000000000000000000090833e68614be5bf298e04e44527480cb35128bbdecae15eb95d6931a718f66869ddb68352130b4dd8a921ab3f26d080000000000000000000000000000000000994015b1b55340c3839d48320d178b2ffaa0bbff038f7aa63d4dff41a217582fae9613bc537fdeac8d0670c0cf479a000000000000000000000000000000000fc486e2a1680c10ca28d4c3bb22dbccc9572036512645bf868e7693ae4591569c973f9ea26342a573e23a06c2fb4b70 +0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,000000000000000000000000000000001375bd5ee66c330796bd8381a26cefa3f40f8cc8de42d4d59a7adbcd3852e6d632422e6ad9a06a6e497b23b17b1df87500000000000000000000000000000000165d8e7be17ecae9bf51a773da705aea42536d0fa3a2206267da50451f5104ee241811dd0e6710a80c38df77b126c009000000000000000000000000000000001559572407aff34969f83c394d2b095a7ae9f53a8e6c923910f256bb87b6ec076fa6acb85465102fd24d34031f88f7510000000000000000000000000000000015ff9ba89b55ef75f63732dec1e64106d7a912a6657fcc970dd011a03b5364117cca46d6cbafbc0c5049db10fa83fe6d diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_multiexp.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_multiexp.csv new file mode 100644 index 00000000000..4ad468ff2f3 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/g2_multiexp.csv @@ -0,0 +1,102 @@ +input,result +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca20000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b300000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0 +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c520000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e900000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a60000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,000000000000000000000000000000000153da66acafe91b6f13cd739ed3342197310e4824e7aef2e3414654c2678b8d09b296c3f928f3cc489893420031ab800000000000000000000000000000000010f501a96b86343a7c8d8c1250577cc9be6ffec81b5175ed07bd14988c5bbf7f2f3e7111df7d941d0cd267ea191d6ac70000000000000000000000000000000015e0d88894f7f83aacb6710f6c03ae60db8844dd3beec160fdb1df746b1f38a5e23def0893a0b39bee47c97af6535fcb000000000000000000000000000000000bcc275115e87f2f88c4afe8bf4faed46e6ad0c0357884356a26120591ba283f06b464c4853217865b1d2301965f2bd4 +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c4250000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,0000000000000000000000000000000013b49054c3957d1e77ba2dc3ef75775bab9f0e9f76b33ff22e244e897b8ab80ee0749c81eceea259e99b5d2a72251e5f0000000000000000000000000000000012e017e4354ef86f73ec51921cbfdd01e3113cff044a049bdd34e36401712420790cf718bd28afa280ad12104c1851ed00000000000000000000000000000000097f28bee5d903e3c6de14e834d5beea5c847c3106742978e586ba7e913f8b631a69c473aa10e19df9795ebfa3ea6a98000000000000000000000000000000001953493daf65b974b549bb98e735da44b543d6fcfd97176fdc7f6f03617d90e6bb952a607fa8e5791df5dc1c9bba2286 +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f85200000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da000000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd450000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,0000000000000000000000000000000000fada9f43b29abe15693d047adc277814cb94694cab3be56b92312ab7666649b8e9d92aad81f8e487be0f74b9ce8c250000000000000000000000000000000007f6891775811a325cd7f548011ad4c705ca0327ea0484d938ce061c913a7ee6978293c3258c4b865d5c2325816c39990000000000000000000000000000000016761f859beb90ea03aa35e954d112da02daa8e76de80297afde9c29cbfe8ef4d42dad535917685a99b2a91b1f952ae50000000000000000000000000000000012a4f24ab88341dfb8a60c19993b8abea96dbd7033d3686c40903728b4fd4da7d07961f2584b51e9e6c05976d555757e +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e36160000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd00000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de070000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b60000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,000000000000000000000000000000000b219032a2461a5fd1e43361c46beeae92e30247acadcdd241692abe81691c295ba38a1f0a2a45ae76b1b95d7d0fdc460000000000000000000000000000000016905f64e581aafe928520adc27c24703e7adeb36dfbb416a159cdb9b9a26c9cef0821ccf52f5ea5253b7c9d78769e9d0000000000000000000000000000000015cfff195b2123aa140f963628c41deaf19dfff44d26a38de4547c3d15edef10fe9f65b1802dc374d7ba8fb62117c8880000000000000000000000000000000018dc725cc8d8919a7414b7866fdc54c4467b0f87cf99fc9b36cd65c0ec526e32649f9c57495657a93487f1f2f5769168 +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b3300000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f10000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be500000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c00000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,0000000000000000000000000000000007638fa4e8823dacb40ece440f8f1e57cc5c3851f94357a5325207db92380dd57a7c8709e4d00b670e8af1b77368285a0000000000000000000000000000000005b66a6e6b13ea0eb367a61ffe7c620d9edf5563cb4cc0cdfa68b99d9691cf9a40efd967c1e880238eec313eaf4c92ad0000000000000000000000000000000004f7156c69ea88a71a0af2922d1caca24055d40df058eef02bbf95d864156f62fb0e17d9fccd193840c36ad8449bb4f7000000000000000000000000000000000b8f46fd695c5d96d939d42c65c3b709d32f134710a67909dc4bb43d752521a8d4f0465d0590f30f06ce42bf5f8cac28 +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e12500000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000196f78b64fcc342ba4f4edf34a3080ec950532a5de21a875dd061f09351def5ba3b85745a561e38117a14c20d33a14610000000000000000000000000000000003929c2bc55f323d57dc3529bcf6644e61c941b72b424d69969c1cde7a804d157045bbf6d5b79a3e6686509e11ecdac0000000000000000000000000000000000f6b659818510cde463c52cf00bd99da045c80af4d5cd0e55f9bdd81f34169fe869c519f37a98ff20c56db554469087600000000000000000000000000000000129709e97757724e765f6600c2b1928286efab55ec8d16876a2a3210bf9d31cc5425265d0576a2d5469cbd9a6c8c27c012adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c60000000000000000000000000000000009f5f167c9b61a0ef76415fcceff04f3fa57071c2d79f443ef8a7e6049cb1352f650ebd8f358904bb432d42772c29afd000000000000000000000000000000001524a875d73e03c53b92465bafca582479110611bac6a98fc7d76966e9781308a10cb202289c0776cf5c36515733ccf900000000000000000000000000000000002b1acace94a6fe196b217a9aff413fe0bcb55122ce9e344942843e5afba0d5f2cd0bba14c9c8cb9dd1c3e9024918fc0000000000000000000000000000000018e4f85c7663e596182603862adb559635fdf16ba35fbce7278680ea289f871bcf6755d85654b2a37ae77a37e77ba06ed1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000018ee4b4855f866781f38a618c2fe4214c63034620ea5b72361079b0a5c2b2d6fb9ea73fa202db3a2678cf07219cde81100000000000000000000000000000000180870513afef93870ca64e2363fa1aa43a599db97f3b807ada1c25ae331c80b8ead5cd69b6f5a65a083606591de90ff0000000000000000000000000000000010afd546703baa35a9eabaeb45d301bd5be115557bbb4ff2a0e493668ee790e947eeafcaa923f62ca00b8e635994e39b000000000000000000000000000000001089996b218aacde4ccfca4d2f66d79fe161d962baaf2d6696e1a76ea40af4ae7195e8cf9f6417ffd054f20b65ddfb104c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d30000000000000000000000000000000011757ad74a3fb341c8eb6862978ab3fb5e8cfc8fdbda7d82756532a890d61919cce931872ff339843805e00d8c62ec4200000000000000000000000000000000060783a06e93e82cb08e5dc1aa31202ba11676511300e186ae8e45248b7fdec3b7d5b6849f8b79b8f78ad84f36218544000000000000000000000000000000000ecfd8ab18066fe3408fd20f2a4478156e9a19a09b58da76486c9f6a013d861960b6b99bf49cbecfa8c9d01d5615c1bc000000000000000000000000000000000b45709845d35d7b560745375df79fb95df15e85b96cc1b98cc832c74621339c609018d153bff93f2f5493a52b7326073c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd0000000000000000000000000000000003f9de90222619216852356052e9819d7c6e8ff91e0c6f1d8cec832770ed9001db4569fbf579ab16964d76ae7d1b89e900000000000000000000000000000000010b7cf8f0d283cc22942ed73c599115763dcfc1ddc98d87979fc3dce2f33ca3531cc2909d94f86736dda2a4e94a4f0c000000000000000000000000000000000b0aa4d947644cbc7df8d1927cdec66a68862e5a806e25554f27cc1a3701f429fc7097497ad0419e21cc403b472c8ea900000000000000000000000000000000146270ecb66e1763437b824f2ae122f72f20eb93fb30474691a0a192ceb932b1dee111fa44954075335ab360d31ee68d61301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000000c977cb8de4b6e2e33d916f74eb4e42f089d22b54b59fac9aab0e4cafc8aa2b0f8c55d7251662b3499ea140e322dbbff00000000000000000000000000000000106944a9c2d2ecd08e109de29095f3460128bb751051a1f079acb58b6a60b0bb5f52e63d47b688f4a382a77c3b039eb5000000000000000000000000000000000d2f8be1c78995d54fbccab61f816b6ec52dd19aee6aeedc0e4bde2898b2d07c2925da0440a38c4c965a823fff10389f00000000000000000000000000000000183b5d15b243cc5d9584842ab1a0a1e01ad87268728d72aa8c0d7ec6e7069063a11fdd1525d2b30b35e4568da7c44c5495cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c000000000000000000000000000000000f65ad4c21fddadcc49a8f7bc281d2b7901707f51a67122179fe97da46ea5e1bc6e70d68eb4eb6776307510a67e972620000000000000000000000000000000009003dc68cb0cdec4a502436718f066348f1957ae65ecca8d32c5fd776215cb9a098c0ffe56c92d79dd68d251f49f13e00000000000000000000000000000000038ecf0bb98ff2e84b388c58059ba0de0cff3d5881ecf01d668495ce81b76b00323c665ba88309af5552b7950cc8c08f000000000000000000000000000000001924aa0f460659f552458fb469467a2925fcb2420d4fa6249310456853be3d08bd5c37a3f0a9d6e94e434391d20cccedfb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e00000000000000000000000000000000189c3ee691387fbbcffdb147c880218c3e5c0bf78c44461ac1bd3ecd5d4b85225e46cdb068049607fedfcca14882e289000000000000000000000000000000000260efc08531083db2839d1413c90968e87d79bc1a2c730f0020e40beb92e84b73ef43e80f7c61e1a30c0cee11b3cb370000000000000000000000000000000005c852ca0aae2c575c65ef18b624f50a32c007d299f24a3ec6cacbcef1d6e3bdba9650fd7d639bdc60a3e107ee9c013c000000000000000000000000000000000321c01a9de69d6b89db4ed88dd48261ee28facc5e26511fb2833fa45edfb58051c8c3ce9501e8b4c3cab9c456705889bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b8600000000000000000000000000000000183d50635b22e4d620130e0d4008e3bfffae5dadd7e34f4496899ca54eb4d9e3e95c54ae1d9664609c58d02ee5eff65500000000000000000000000000000000029e3b4496a379464302b1476a4549db371f5d6721704b1d6bd35e2344d7679f8a61a0c3b12f287fd86fd247f9652cea0000000000000000000000000000000012c6a3793fd23e955708f5aeb4d6efb670d25a38a67813ecc72f899cd5f926ab7ef198bf6d591328383aaf54f756c66b000000000000000000000000000000001914d3e4b6ea96bb91333468fe8f3bb74636e9a4f2ed198e9ff01b49ba02791d5bd63224f6a38538aceb777168bef688e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d250000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0bd4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b0480000000000000000000000000000000003760958eac45397eca1a1d951a80265a728dc3c584f7dae111e7ce04248885321b69b334b00cdb0334a362676c2d32f000000000000000000000000000000001031e4a63129ec40da5fe9dacfe148a67662eaa00e1fd5c30336462371c167348a10e50f4dc18469a1a6b76485f77e12000000000000000000000000000000001412dbf993c557323426b486f18a91d16b4baa2c497b30fb332a710ac901c96d46a577d04ea87afb08258aa6d204a1c9000000000000000000000000000000000da015ca09ac0c3245c090f39852218f46fea62198fba35ebc4a7f14887943c3bd1bbbfbfa300611e45f419b33988e404c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d30000000000000000000000000000000015c9121f72e2425cc8aa4c878907628dfe75a903b7f756b9e13728372cba598859d20a92a8297d95e1fbe25fd1cd968300000000000000000000000000000000025a3faebfa53918efa733949f914be08b791794bd4963f0c3fd78df48b14ad214374b08299327575c0731b54eafed76000000000000000000000000000000000771782ecd9980da521618af2f9eb55d91d67b20ba615c7b3cb1a48d483ca405fe99a1cdd17e4dc7aeffce586987d41900000000000000000000000000000000136000da90a76d538f336608ce877be943025b4c8bf15880ea9c1c001c20c954292d362dac9783b7bf66b8d51ddaf0f2a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae,0000000000000000000000000000000014cb24001bd933b1d5866cc3de9f4b8479fe23e4fc26dd210f9d06e7a05449b9f5ac4e2f48fb847599f625824336bf1e00000000000000000000000000000000033fdb2e899427f1cb9757022c5b614f08c64b53583486148b7431311a6f15aea3b968913fd5f3e9b624705351074be600000000000000000000000000000000035420be9c7ae3203d0dec61ecea70e22e62f50368be870e74f9a7349453647a7f61d2a42cec6522164cca0c7081d4de000000000000000000000000000000000fea43388e9f6e31d419c7f9fbb9839b4cec04163a7b401d8f7de73a4560fbfef4e272f1db9c9d5b37693378f139452a +00000000000000000000000000000000039dc2b60389f6c893c44072f4db23e7df4c2f299d6b70b70784d9370d9ff8e5413872c227074d429db999d30dc9499a000000000000000000000000000000001702273db356abe7a3f91a9fe4bf56584f13de4069a91daa6c0b552089bef60da98d32c615aa5610842dd8a507f9477c00000000000000000000000000000000095285e8c508ff12da79e16e0391dadbe9a823c586a049e729596864c3cae117305c05f009f9e8ac032abaec8a63f8de00000000000000000000000000000000078fc70e926decf7aa4c2e4b395e88f367757dc47a4cedcd5e632c456a4c160393837196af474948ce6ad53f830ce8aeb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff000000000000000000000000000000000cc3cb5e7b033cff3e5cb01ba29ce8e9f4a93e836ddea7d417f7b07ba8aa71a0efae2e1d7a8ec70bdff12d84d229245200000000000000000000000000000000019ce3c830505324b9bc7cda1fbb328150d71310f06a8424dba861d67a7bc0428beaaf697646d22cae9e00477cc8066f000000000000000000000000000000000f6ff67efefa5636b104a0351c90fd3e89a32b8a9beb0d123d3d6ae42eb5e8bbc19c7a972e27762daf852259c65fce6f0000000000000000000000000000000018d98c43fe5b13b701749f4a5dc25f0e713d241d573639fcc73429226bb131d448283338a909670066045c65789bf9e7064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb3346100000000000000000000000000000000003cf82958d68429503265dcc7d88a3763cca32baefe3c8d32564cf30e8e6b8255d4a9f6a76bce1da473b50287deda74000000000000000000000000000000000bfa9cde6c06b2a2ff8f877ca90b3827d0aa0408c4ccbed23ad461433dad71017d4dd387f49c5febdeafa17d06ba784e000000000000000000000000000000001770fe70513533d91c83449ea52964cd8b449aa81f71e71995db5b19ceddef18e2919439c80e10086e670be669696e4f00000000000000000000000000000000194c20491c9d5ed827cd9d370b9bbec55e4a7b1c34ddd1d80201e7019d9487a747b4fa57b480dbdd09af73aa4f5fa0e9a3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca000000000000000000000000000000000a79d9e0ff43249ff54526c5e1cd55a9bce93adf272508871326c933d526602dc9dae5b6f129a0f1c38139ed1c39be5c000000000000000000000000000000001458b554e0387c1ddb9dee9f4e9fba9c81c15807f496442f4b7210267912b9439a19f95dc80a1e09a0e5cfe750f43c8800000000000000000000000000000000012c06b19ed4e8d5d1b9fed56bc5bdaa3bf0112db997e33aa14899d53e1bddd6aa91dce7e9d25473b66b8578d398981f0000000000000000000000000000000015369b2228e728894f2fd7c2d8c41ac3550da4f297de445cc0f0ef7134c478f526987643cb5408a0bbb79f5f983c085ebd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000016649a8231407074af5ffa93f9db5a2ddce8785be8ee77149602d6afa24ab30b26d2f74bdb5f7464333924a817e242e50000000000000000000000000000000001b990f5ed0b23e113042ff004236646c6eacacd99d1d73fe0c3d9351ce8d622327e827b2c0556802c5657f8f06062a4000000000000000000000000000000000f002a2a5ca90285f9b2fd429721c2daffcae5fe48c571ebacaf475606f96cc8350ce88a850ed75e5aae59d445249bf00000000000000000000000000000000015157fe1a767dabc185a8dc8fea3cb208fd995ecd9acab762638faa987f8367ff7c1a60b657be6e9461acc9df16381e5562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000073884ffbe6deff99cb4b0ae1c0e91e2f4a8c2c7296339b1d7e117d5d47ab055743d643155680740befb379a1dcab666000000000000000000000000000000001995bdc23991dd4cbd973e915a16691fb860490bb54011384c553dd14afc37fe673d13950c1e7eaa29c324fd9304624c0000000000000000000000000000000012197a19a498cd94ecbb3a409337b04e76e1a52715c40203add20eb80f7eac66f3386242d51bea34ea016d778248836f00000000000000000000000000000000101069ff0af2ac4dc7a5bf7bf7b56d82a310d67cebc41a9abf1e1af489e1acef3e726fe9571b4382777573712663e26caf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f000000000000000000000000000000000849b88e7ff52d8136a120f924b20b45ea9ae654a0fa037b62f3c275f0661091038a4c1d6ce7d50512e628b6b397c9f6000000000000000000000000000000000e50e82e9b368f2e316d41febab6b0f626d6588b7217b4e28eedbdf50a4abc9039be9e66c97790d12cdedc90873993e2000000000000000000000000000000000bc5d2bdf06fda1e1d1f5c5eaa7988dfdd790bf4d952f5d3a532bb59edf619dafcbc29274fd3661a35a3f15933b1849300000000000000000000000000000000162e5ce45499e620d0977fa26a291a8e75943c4b5a2a80be395ac9b89767ea5a06606d6b75ee4c8a286d2ea5a197baa5e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e880000000000000000000000000000000005c11afc970544b96fc1a4cbb27259e19b5fd588d1be1c8f19eb4f111882292a463c951521388cb8cb743e5a4a1b57cb00000000000000000000000000000000013dc433dadc122376b75fedc923386a7ba5a363678fcf9edf165a50e160dadcc151b6f402648193d9ef960f5e401030000000000000000000000000000000001893af155aca343bc29989ec2b5a583d020a7558c7663accf6f3e40d0a8eb98ac548e933eb8e2d5fe3550927acc2ed4900000000000000000000000000000000043a79bcbaf07bffe6c6890d95c7e74d127446bdea51a0ba3adb164ea39684bb3ac552020ca28b86e34692c9b36f4384396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000e6946ddc8a9d73e5b140af80cc91b31b9a226a945a9574f0629566f7ee7650730c5ed758cc30442770ed1602b84175c000000000000000000000000000000000da0abb9f5bfcad73b3f24903e9ef887c660447332e5457e4a5764f6628c04d6fe903679b8dc8bb3aaacde410812286a000000000000000000000000000000000656016c01d3405dce9f7d40e47976bc8a84abc370e7e42849dd0bd93ef1da0bc88e428efea43dfea37dd834cf246d69000000000000000000000000000000001939b2c92c8299d7ec1dbeb9f291c5e1c9481e10df10e6ba18ae695a780aec5a185ed4c7e82dc2bb5af87a74552c2ea32087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000000942901572722e5005a9ef5f948c8cd6f557be8d114d2810d3cca29933a94de3c7658e7e28675c2a49f138d9c98c524000000000000000000000000000000001908e8b815e95ec07a90861ce53f545f0cd44aacc47df40c24d6cbc61e7b28fb91cfb1cb3c67b6c5b38c34fcb2ca35710000000000000000000000000000000017bad3616d8e510e325d9166790239c8c817c68ba7fb937fd5fb70a4219265edf6625b52ff26f0a34c0bf481c482b2c600000000000000000000000000000000023ff8a50a9c0e9ee829ec81972386ea012df5e8476d8c342df6b98fa1faa1382ae921c2f1018a918868672450355c44f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000014675ab3efd44bffae321791e6fb35a24b9c07405d9985c685795df2db183ee9dadf18c76cf4095e1e0695dc2c08c4c4000000000000000000000000000000000835f2cf09647061ced2bdf4211bdaea408148100f864f47ff76c0c63a43e44e8ddd9e01709b6ad129bd574d71a1a63c000000000000000000000000000000001017eaeaa6eba76923ff27e5848e5f3b09e7b2b9d55b2cb7068f39defa8628d1c8cedcbb0e1cb5810febc4ccea712b7100000000000000000000000000000000054c873449c738383e9fc2f0f74a6334904171fdb704f5ac35a483ba19a8f661187d36fb35014af9ecf88225466c86e48624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b60000000000000000000000000000000000439ae88636244d5e09607960fb033e4217343899d044b21e61335425b94a5067c941e83e5a77f4b0690e1de037325090000000000000000000000000000000003a67653818cece3ff0390d097f1bfbea9ba954a85710f5c24d1de1893f25f2863991fb9f330e60cad725708e70384b4000000000000000000000000000000000243394c3459a3af236189ec6155418c1916b854a20b980ca1044b48e23b725dab7c60a48e89f642423c805c117e64870000000000000000000000000000000004c8c9fd9f278dfe9f5e24e0f5b42699bb9751b56520827afc2fae8393c690a63f10e92f77c4a10b0c161408da9bf505b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb38320000000000000000000000000000000011ba67024503301ec72bfad101a48708e3521c8a23c6bf2994078690041cf7eb75675cf5f20c8e82d11145e31751a2300000000000000000000000000000000008ace953ed2eaef19595cc7c9fb1806d26cbf1e888075e3985b28f8d93b9c0b4c820c8e8b50fd4e0b23923d428da3efa00000000000000000000000000000000054ee6f7247296e0748d0b52148a97b930e69991a242767d80bd6434d42b0865a64d3ce60953fd2631aef873d8b2acf3000000000000000000000000000000000077748b724301a8bc48efd1cd66086e727e9872e4efdaf55ba90ad1bed7e229a9cfb79013333b50efb46090ac0bdab488ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb0000000000000000000000000000000005008a1d62dad51132ad38a226e8abd7421392414acda61111c728713a2ece284b04d75c2bc58d355bb1d3061415010200000000000000000000000000000000189725b7fc48b8a648237021e9a2334247f1cf18ca50008b813978db01667ba08f00b23b3aa0e015f549ff2d5e5c535f0000000000000000000000000000000010483cf2310f64cf0baf556cb2f2828a1c15922547bec03cdb182a316aa86b5473f03373cf7e59a9a78f73193c1caf520000000000000000000000000000000007f635394301441bdc57dd1f4f97656f4218ebb139c13a17e12839091e2e81327f3353c56880c608de824a07a17b2bdd951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f44000000000000000000000000000000000daf4090a229a1ce946064cda1c4b19c88100c8785c69f2eeec3aed12065787ab0abd797ceed07617d55a9c70ac3020c0000000000000000000000000000000011d77fc28355f61037cae3a8342bdf8d11e963495ba3b5d67055f790b1fd632b23565cad77a3d9968d364e4e2a553c9d000000000000000000000000000000001038d7e8fedea873c864b79d1cf8045485299a2bd4d26c5ab5c8d4a073e2c3fcb38cb230dc6ab7e8e228cabc6ed97da50000000000000000000000000000000009de9209ed14d62625ffbf770e8c528594aeddcaf1aaeedb4f3ca973e7b9f9f1a40370cc74b154f3bc641665d8e4d96b7056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000a6b0dc04591cbbb1b82a059e08b488fd66edca0f2d264c352f81cb6ec45e50f0af16917fa4727ee9888f84b6c888c60000000000000000000000000000000001369ae16bb0743f65cdfc8082dbe0d588cf8aa5406a095c3deefc27eb3ed462dda9dd4921cde6a1d878a805cd144515800000000000000000000000000000000124e08d4de6e831229005663df4e4bd5bb7af56dfb13244c50410e6d0aea420ba19208bf1a774207e0e0170ad3a9b4f60000000000000000000000000000000011b2973743034a2c362281b11a1ac1c89f59ace09f0a53afb0c2ceb061726c7aaefe274f6dc04e5d0dea2b687a00609a8aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a,00000000000000000000000000000000136ff52e440da609b6b73aa838f2eb9791221291b7b14d902458aa7aa9e37114c573edbe8cef7a98dd07275a8c3fd650000000000000000000000000000000000ba625eb47be09ac8cd1e2ec9015640f416af0e3e0e79d39ccac600ea08bdae7a2bc9144f13168a8cec03ce66b9daadb00000000000000000000000000000000095c51e81b5881b009b28006286c704ce3b002e4ca50ac8ea8e574d1e9665a5b1efdd60568d4a4a656ca6a2d1750a39900000000000000000000000000000000143c0c4b3b720fcd0b044a6f420961e2b7eb5f9f1b0d200de56ca8b02709d819f47f0a6ea7d6b49c4f30520586a45616 +000000000000000000000000000000000da4cf56fdbaa9004bf8ffa12d5cfb3f296ba5262dab079c91bdbadd6e41ee5f89912bffd5df1643146bce1f0e021b3d00000000000000000000000000000000150227356e48f29443a0ab4536e7a2f86f9e63840e23bbf1b091a59f52c27978bd6a15b29b105132298de45e51134da50000000000000000000000000000000017f5271c97d84f55f8b7ee0d73267bb69cdc7565c470a4b531f9dcd29596eaedf46e61bd79e71e5ade7d000c1c1d81bc000000000000000000000000000000001322812590e6c22bd90511ed72553c1cdb0ba83487b00e3adcb01a9abb438f365ca23fae9ee4a953544253696ddb0bf1a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c40000000000000000000000000000000007c59f94693320b01b56b36f8d1c39fc9e01bad289577738e648771d8940778276cdbfd59f07926e516fcebb70592de0000000000000000000000000000000000aa71d6dcb0b225526eb92b79891ef920634a007b87986fc0f776f85195ad7ec2d84b9bc684add947df8ff42c33b034d000000000000000000000000000000001362cbd6cca3d5c1ec68928be38aca5de1f224e7cd4f5c3ab1c2cd589bbd7c31022d4adc51720bedf2580d2acfa0f06400000000000000000000000000000000162bf0f38e19ddca9aaa370f988be9b35461d2a0f46143e8663f1fa549d0afa1596f029cf2f800b027b90d1eda6ae8a2b54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000000eb12a92fe65f79c646ba508fa615d09d86e582c3337ae16f66cd3bd74a9caa9dc17defb4b4e67ad62f0665c9ad1b6cf00000000000000000000000000000000058b6ce2582c46c0fc108a37e1d2713ff21ec8b1d8c18da0e69f0dfec7f2f327043e174e16d9d64f9ed4d3818a302bea00000000000000000000000000000000068192bd2ebc0a23092bb98c23f5792e179913c4ff1f23eb27296a77e83729803764b8db3b7ba4fe154ca467475eefb2000000000000000000000000000000000482b16e876aa90da6da35e0d7495a04d5b0a1d084c61821f23e1ad63cb1e66ef5975a3cef9ecdf2e696e9d9b50bf9b65849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b712fffce3e63362bcc246da566a14139a3d12807ba83ab3520b0aa3aa20cecd5718e2b7e00f24e6fa705315bc2175800000000000000000000000000000000057a66fb12f27e4a5268e56805fe2b61b5ef019b31fcdd861e2b0beecdffe1a3a69e8d193815f97740324aaa40ce34a8000000000000000000000000000000001080a9e1133f37288dbc3835e45b6611fe84ec4790e23e5ff84a2f72bfa2837f55cae9177e5a3a918adde777b7298a9200000000000000000000000000000000142dcaefd73d7f6342e87fff8c6cd161389b6049fa077f35076eadd2b4aa66f3a1819bf8272cac1c28cc02bb6440dc42aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000034c0f8249d6aefe4cdbf84d151ea9f84add42ade087048bbbf9de4a412cc805dd9b608fdcfa34fa224066b5f06d18630000000000000000000000000000000009e235ce5eb936bae00d3fecead8859e6d909da3d57bbe0a8aefaa5efdc94969a1cb2e12642c0099bca4e7bbf9833469000000000000000000000000000000000b6fbab498c2706f0efdb4effaf79218cf4b652a5205eabeb84f05a060da8cd18c8154a3d37594485ba50a8228f27f6800000000000000000000000000000000130ab70e17dc73f773df99cbe3f978bcd3fcb92a8226a1450239d209cc6969e2cecdc0bf3cbbe9a9c1de072bffbccaa952c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b20200000000000000000000000000000000031a2c10e95b841ecfcbddee4b458385e5650dec9a2d1e50216d9fc261a9829eb5fe894e47f171c8fd2f4d5d89771341000000000000000000000000000000001378471c7f770672ee82b70fc87af5ccacdf8995df9ce48aa9fc2f638105a2fdfa48b615970665ae4869f1e2dc7988e8000000000000000000000000000000001969517c503df5560628555a8780138e4c340d9d49d8fac4a8a11c894d283d49fd06aa81e9f0db8f015d9372762dad75000000000000000000000000000000000f5c2d9b7fc33167a6e9b5a5fb8c5d16ca009282edc05cbc8a048b835b16ba33515c226174d6ce5f9836581611ab403bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd90000000000000000000000000000000000a6a32f2006c4b7804e99011d934ac91b1b3fa6f5d02c574cecd6570bde1e998f135449dfc148aaa8fb8757d0a7299b00000000000000000000000000000000198beb461b59f57b85d858b730fcf853d967a1592e5e5787fd81c6a3d9d9b40c1cd7912cae21a47aaf78df5540604cb4000000000000000000000000000000000955701e84721866683b4eaba82c2df8a89bc906fb0a3cde565d314cd7278b0c56936205cc8ada10b03e69b93c48067b0000000000000000000000000000000004740253653a0d6cb15c76e145dc0b1f811bdc964f7d595b6027bb012b42409deaa8da83e6ddc3f0f7b4b237eb62b537915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000000f88e1e30674934bf1062ac619f1834f35f804a958e82121255f8087ae08f10525e740ee53d7514e0ee7c49e324513c700000000000000000000000000000000019d554645696b7beae881ef62297283c5b68ad3fa9a84a47c29cb53449d33d6ee7a5a3cb83b6acb75cd41ac3f52fec40000000000000000000000000000000004b32776966e52e8a72c88a689d6c56833296d384e2059d8f615ccd3616972074987f839b4689d5610a88addcd836d930000000000000000000000000000000000fd4d21b00d81ec993d2350f1fe360576fa983754a7159c2e81024a00931d84e419e8b5231ba8cf8f05a0ee6ccea7e558fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000177830cf34186191fa295b7f279bc819d8a53452e2114dbfe709971584ec7a2da7453aae3e64f4b14c261e22314027c3000000000000000000000000000000000ebf2aac35fe070403a4b7a5c2f102c67300bfd68af7863b45185b37ade1bc53d46772062189f348647e74c77caca4a600000000000000000000000000000000128dc7846b2dc5c453ba5fe4675d0c22f4d7089624ede05b0910c34ae623d4671979fd73455b35b61a57c51fe2895adf0000000000000000000000000000000008e33a3c3735be035b550613c712b220595a83c1953b24b3efd38c5913fc23df823e00ae5a1c2ea8a8eebbb93c5c721dc29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea0000000000000000000000000000000000a060a957a8da4384e3436110657110653685bb621c32810b6516c690a00c13e37f70185958beb0ed886aae5cdd611a7000000000000000000000000000000000b5afbc85e274049985eac230b2aede7b2df1485c9539a4a4eb6aea406d0f6515ad8bbece7155fb0dfb2123919fb8af9000000000000000000000000000000000afa722987390440a33d5103445dcef42cc4a3c461daa076d56fd38e0b220016ed2bb8e99b9a8da4af96b7da64ba90950000000000000000000000000000000013ea6b8d327191e53bc71fe43fda305a4a0584cad04048afc0480f179955cb27f2ac8791d847036470ffeb47aae36877dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e900000000000000000000000000000000032b490f795ac3242b8c7185c9e19f0440ecee3a65263dd4e4c9a431571deb7339bc6e2d73ec43750f6f027bcfd674c400000000000000000000000000000000076ab4ab3e8ed6ea3b882fde5cacb3bd094567288699e11f368c3f60f4283c5bcee7b4c5debeac541ead983f5936d9f80000000000000000000000000000000012aa2060e421f4f4249e83ca0ae1752dfa2b7ca958821841a18f05071a35fb9c1448619bd96f8a7adb2202d3ffda8eb30000000000000000000000000000000008b24f29ee7571f31ff86574e654a5d849acbe92653ae1a1d2baf4c9ca6e67da4937bfda51a70931a6e60d90162efb4f21c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea47000000000000000000000000000000000c8b41e5c47babd6ea113c0ad9f45a75d1ef6bd313b768ac01e6f581ef6630ada623c1a27d4aadf543af4055de7f6b73000000000000000000000000000000000a0f73af06f8f0115bf17f7c5db0a6bdea77a8e3d8fd0b52b0d4e2c558f1331f655dc272c86d98bf166b532ec8e45285000000000000000000000000000000000499b55964186bcc6986e7744c52babf47e274e47a202abf6f816bc748baf846df2b5ced2a5f61fbb0aa2047bbaf82db000000000000000000000000000000000d6c2a9a3fa5d0524f772cca2c7e72a5f2da1a6a1b9550997e7a6cac5b6b6c37693a01d30bebe4b9c742b63bd31487a1e1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c9600000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000134125416c7908cb4454ce6aadb30df46042ef2a6b4b69b19fafcb9ebafe8b5579046725590266cfd10fa26e1b5ff3dc00000000000000000000000000000000073f4147cce24e13b9eefad7c69b457acf126bf278a58a26a7c7c6b482edea6dca9725d7e5e4138b4ec81bc2505ce2e60000000000000000000000000000000006125caac1061cd6c556f4cfc122df8e949622a46ca707b48ef088ee5623df058bada1bc0cce1399f0be1ee86225f13000000000000000000000000000000000146e398c161e29c90c8a4fc44bfd5b3dba6f9e80ead561fa3d91ca5f416e06318dddcfe5147ab5def858fb025a1562352c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b6000000000000000000000000000000000944942effc77ad02c5ddb052acf86f3a9dc4127dd032181450295464b49ac1dc0047790acb378221fbeebd4c92886820000000000000000000000000000000018e1d201b38d88665696ee6cef11fb19f7daa7f11c5a5ccc73e6b66ac7b89df8437c9f07132ec8b69e13f63424ad694c000000000000000000000000000000001463117fdcf17f28956a42677b3ff431cc17ccbde067b91ecd6fae51e1e24ba8d594ea368d041656022611ad3ed44a6e0000000000000000000000000000000009715cc5add17395b7ddbcb961269fc5d4739d799fe9554b3c9e9f59c895ca5df8ec75bda05cbef3e6a165f7987e78662908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f00000000000000000000000000000000007c07cf1ac9b8b28e3d2f1f4ce22b8ee46e99914ba20c7362c679559a1618a906c6ea65c475ebbeca4947019cb6fbec0000000000000000000000000000000008b29f72cda71e0bc2246ead57b2f758b741b9232d87be75331275a5cd63afc9aa98b0e42c1b82cc258e93c97e596a81000000000000000000000000000000001512548a4bbd537a4d5baf673fb76ea7e35b2977216e7b29a6375e1f92049d7b7d5fd5d8b4ae6191f5592b738e149a5f000000000000000000000000000000000cc9d646428135296919808c6ac10c142e769bf71bc1490196dfdd4e1fc7b84e58155bfdbe77a9e684622ffd83e97ad3e8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399,000000000000000000000000000000000ae9da7d12d0a03cca3b41ad869f762784cacb988eac7ce904ec9ff47824e058e2e211e2285f9fe2aed0b4385949b4540000000000000000000000000000000005b0c873d20f7be1410d39885ce4f79884eb6ae2b2f27510d6f6874dacf2a66c64e56b7aacac61ec88261624936e695700000000000000000000000000000000076c6076175ad748dd68fee64431e5e4ad013797de4528287e7226c3df90233799ed5c8b36848c1a2e1c02591a013d270000000000000000000000000000000001f7f6972121d38ee2d10c621a38448ed12271f7e0e9e4567fe1b5fcb469c7906196fe92c66c37f8c5abc91160fea8ae +00000000000000000000000000000000139cbf360b10e59c20dd4155af2023d5dfe0681c62351dd541cbed41b6a328aa44b862d1985b132a4d4ca61c95b61ebf0000000000000000000000000000000004af6b5a0f7a02d5c93304be0d31959bb4b1a7a5838dc3f9cf50180c4eaf3d32e68c006263d75f9735c8f0b5f811d3cb000000000000000000000000000000001644937e5ff3b8d2005a2f3b0984917837d44412362a121be481526173a4c61e20d61076aa10b4072b79743c5f7f4c4f0000000000000000000000000000000009bd399b55a59550dd876f35e54a5803058228bd6ab6c9a66e438cae473c63816c96bdf378ad426a236b58b90e737831e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a8000000000000000000000000000000000f343e5118d7dc3a38e9975a5f40084ee5f2305e45a8aed28ef105f76345d9f5646b4f3924b92978846b4e605b78fdf400000000000000000000000000000000017e61a2ecf9b3403b43f5a10a97cf5088b4f98e5a4513b0912ea7ecef44e6809f10dee60367cf2fe3e903dd68c2a97c00000000000000000000000000000000039f37f414338cab0e12f99b2aa1e3c02cbdee3406d1bd17c359ba899b7cdcff605f894530895aecb469f41628c3da120000000000000000000000000000000001b78bf69f1b7168d735fb4b7b135fe70db79f50e792eedea23f83cee9b48e092536c2ed146c7499cf047c5b4f0a08735307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af000000000000000000000000000000001342346f1b553e29e661c9f6c0a24d8f788db98262d6af86af55d313a37eeabed1183e367ee3d83faa3f284b260e786c000000000000000000000000000000000960c8af3f7e6587cf83baae447491e73cf41e637e1efd730e3acd9793717e57b85530584942e7a030bad3b91a76996300000000000000000000000000000000166daca4ee2cb9516b5178cefef0553115dec8157f6194d24d191cfe6340406071883c89246c0cd5f89bbd5d0f1ee15b00000000000000000000000000000000187f668086b9b6307899d301bdbfec915cf24ac0be10d6897b0677e4f1de6a241f3dfb19225644858be0941530e67d0f7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e22600000000000000000000000000000000032c3783e701bcb651aef40c91682eda03f9d90f252740612c85a5727f8bcc41a886b328d5ce787031c08ace235ff465000000000000000000000000000000000b0eca06f9fb69ebb46d0af56d3d934b333514d7f31208b4ee2fb92009e6041749028a78246a0adc324034a94503e80d0000000000000000000000000000000019eb24ed35f6c7ae53047814cab14d51ae6cf336d140a17e794d5cf18450b7fac3e6f990e12d340291459197bd353861000000000000000000000000000000001983a596485e657deaedf01614dcd5f4ec515c0050e8068ea02c9833d0b165c0f467107a50da0d8cd43bfcb59db6e710eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df0000000000000000000000000000000002f1c29ffdf7bf20fb8a13363393d5f1cca5dd9af82888f0102030fdda641abd5532ffaa2669c0c4159a989cef1c5bdb000000000000000000000000000000000bd548079899d49cd368bf5c246aa168fc8c777bb84a7930258502c8424a4b68e1ab20dc9ef39c307e52bcafadb0c8e100000000000000000000000000000000070c18918f037d5fa1aa005e2c80ce6a80b4b24d33ce72a2bd824f9a061af1db236f04d6041314310b31b805b8a674800000000000000000000000000000000014422b173840da655aac6ea4b7a04313d5d0675bcd565258c73039f879176e51ec0c8a9deba9c78c33179a5ba54492012c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000a1be8e39a47dbe0bd19b8108a5bdac582e1d11ef7fe28df1f12da52924e734e1d591e8e33ec20c6d5af5bc8c1161fca000000000000000000000000000000000eaa7a7cec93b8d5eb933103b52a35b3d58214feb8e2de0bba3a0e57e7993a9df0dcf8089142f57f8e0d1d303588ce9d000000000000000000000000000000000089fbfb389ba448eb77722994178ee3cfd15a27be4ed6f4d4ab6ea1a4c10d6ee8424beb17d08190fb18ab8498d4a4fb000000000000000000000000000000000ab02df2eb474735e28c45b915299230ce159816419fe9c99a7da397b7210590705262ee14c2a244f4922c35bcb119338dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a0253800000000000000000000000000000000006508fbef44d36cdc6fb37b6324810ab2a1d94e39abdf09d530df34714168105e23a7d6f7fd9caf31f263b658f16b76000000000000000000000000000000000b5bb1802813f9f8a16991d41275ae6d18532e3dcd2eae091da7256aaddd501855e775b779959fcef2822685725cd43b00000000000000000000000000000000052146ee63ae277911fe491420651a96994a30c7d1b19bab32eded008a125369baed2ec5a963bfd863a83c29bc1afb23000000000000000000000000000000000a180d79335347a8be350a92491760c6bf1fd56604d4d99a1c49bcbe50b2d04b7cdde55b4aea8ddda4bfeb8e79ab6ce4146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000015dcdc17a9afbf88b54af22ed2168329bc43ba50d374c0507c790f37f9669d0af167328d50d322a827d45f39724d2b2600000000000000000000000000000000169b83f2567e921a4319fc03b2a7eeefd2aed79914bf608d9e0a54aa71b9cb3e09f1cbfbadaa520c0f77f547fd407ea50000000000000000000000000000000009b7a8ff8388c85a0fe3860f26b09b81b5dc51e00a8961fdba96eb462e1334e9e28a2cdc4be49dd8b96c548c64921718000000000000000000000000000000000243782436fe7cb20a3242a3a21402a43a2c4fcbe77cc7182ee3cc04f4795c269d8a64ddd25e89ba4fc796747b608092de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb94973680000000000000000000000000000000013ce6856b6df48e4c9e3fc0be0aca5b139e1b874de6ddc148c1c23a846d61e7a531cc889bab99706668a3b69d32b9160000000000000000000000000000000000a459676071c7f3065a6dd7632edd5842db34aeda8fa0e7d7a8ea29f842ebcf2c5fdfa74ee7685caa51481c4f46952240000000000000000000000000000000010c1d9ebf7bed9195cf0bfefad6ba45f1bd19a9a7d340b7c630b9953923efe4907bd75a3da066fe3d49d656f3ed91d2800000000000000000000000000000000039189de73332d5b5a160c296a195cb9d8a736cca23a92948d513da7e4fc46e1ed9c207e86751b3cf1310d8a7284877ec2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf00000000000000000000000000000000129821e97c65ad3801c011792f4c099e19919d7d03bf9fcba30b3735586bb7ead7d4f9bd10bc5f0e5cf1dae82d5651ef00000000000000000000000000000000038cfbe45bbdc494988a2dc72dea6a7e36652f5e5a2ecad41b4aeceec05dc4a389e54cd3aab349adbe32e65206eb481b000000000000000000000000000000000bbab53f2be2c471d6e9cbad719a73c00b582d0983e25e1969c0be1faa56b1dfa5b7b55797b3340cf8c7eabc560fac71000000000000000000000000000000000b0db19410e552a2f7889c2204a93c5cfc71c360329e3be3171e88fc7aa1e993a5d089c28b1a8f8fc80d93ba194c63ccc1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b0000000000000000000000000000000019320bb8d29b7b5a7130b87a39e87e271b96656b5a2749f13208520634009c26f9829401d3e21cee5a757782c6bbf9ca0000000000000000000000000000000009b37068d72463e72f3a89b9093c1b09f01770e647b5ff7daa50e0679bb76404cf7729d5575a39f5b9b3b371893967df0000000000000000000000000000000019ff29e41db50c736e12f62d76a28f4ca4f6b0f4f61aee00cc0e9dd4e5a75c0ca965b82698f704c604bb309aa5b457f100000000000000000000000000000000062c352a554dc4bb96b459378c21ec6446e15b868221b2fb745d31dece854bc281bc22827d84ea3b0fecfe5d156712ce6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa200000000000000000000000000000000021c52e82b0012537b57fd92fc276e8de842a59355cc15d69a52effcfaa7cc43dbda0c34e1b9af44c2db8e9356b9c71e000000000000000000000000000000000371a6da5dd39092b6108f631a0f4c4401464a109ea1e5d14e262c8a9577e1421d41734d2c3ed73645cc13ef3988e9e90000000000000000000000000000000004054159263ee60f6b1882ad7c376c738c7ed87e6b34dfb4be2fd7aa29ede414c2c6c3ff098c53f22a1c1cd836a6b0600000000000000000000000000000000012d7af6b57c688e1ce90e9f2796b0e525e775fcb6be65f5d2fbe3d1ce1e5d948dcb098c98d495a6e3dd813527b4635258e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca1000000000000000000000000000000000e53128fa5392dbae9e40ab1ff0149d5b577d9d30dcb85eb5e4fcdc17c7daf2ff1d6fafd4a1aba88d2e7aeb45a01afc60000000000000000000000000000000012972781f214511e9b78d276767b1b64bfe5b43215c7680c0063b6974f703b209b2929470dbae16f9767a7cba5311fec000000000000000000000000000000000cf6b37c5a60851d03752f68eaeaf37ac67c661f644cf507c5458cb5404d0ce903c92ef66a657b25ce07e5cf5d956929000000000000000000000000000000001835f202705c8b984a4c7a6cd219c718ab27a96671574cf7cb618235d19e9046a15212e0da6233f15f18bbe192df29c38efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c000000000000000000000000000000000d01c6e8e34e646911391b012680f0dd8f4b8d77c10192ac09ce57b6524f0eb8c7f83ff8f26d856e0945d7a909eb790000000000000000000000000000000000070fca42e34dacce0051f9e26c7c0dc328fe652110976df6df77af04202831dd095715af1714b60a99f2177e86a3443d000000000000000000000000000000000063ba43df0155373df59b009a8083b9f62004327b16ad455037487c5b8325e7eaf57a4d05c533e284004be6de79ad1e000000000000000000000000000000000870c2e5a7d26ba54bf0d45ddf0a4c3011152dd12a5e01a80e42bc4dcc784c7ffdb66f9d6d69ac445c1d9aa29586245147f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d53000000000000000000000000000000000d84ca02ffb6d3cf6eb27a143ece73d5bf006ff61569f0eab00c5a512c5b46e1fc21e8031d1a578010c9582d75e1faa8000000000000000000000000000000000a41249cf01ecd23d06f6a3bb8573186fe47e5165ec0d447df62bfc236f4c203b4feb8e2a4785648af86646cfb0c4e32000000000000000000000000000000000244fa6caa86fd27e044145557697ea89baf718746711c8dde334a2c5ae3c73d7a0e04fed6289ddfaf26e47a9d26b09e0000000000000000000000000000000017db897060c0a8e3e5d8eca9970407b46dc2c2ca0c004d50a171450852f585268bfa8a379acd01b6d4685e04c0b8c106fb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b55800000000000000000000000000000000071e13963e20eb1dfb671aa4a090973e4a4b7ad3578f8630db8a865847be46c796e6f9e095a9ce558b93d702f8f8572a000000000000000000000000000000000dfc4c89ceaad07e3b4c35d96e8534122ae48421cd4443de478ddf9a8867ffdab279ad745e55c87b731afa7700bbdb110000000000000000000000000000000015dd6b0c26f6821177d0cfebb7f1481a971e7601fb24ea365a0c3127a5b1042eab69446de05b61cb6ac0576752f87aa900000000000000000000000000000000156326c52bc78c82f5cb4aec5de35e3c128c5561dc80da2cb24d68a7e912b1f2dac2078508fdd4ec38769102c082f0f74b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a,000000000000000000000000000000000b537dc10a6f518122665f7d78326a4728a2889325e5be7da7e25e4752c680fd786cdaadfcc426343a9844efbbce8f2300000000000000000000000000000000085ba3a04aa8cea82b95dd994f5b3bdf0dcf63f13909aca2c2d61e4275a7ea22445c953b927ebc6b0987e98b553469d40000000000000000000000000000000019cec2e9fab640cc88073bd39e46cd571324904b1950fa8f626e2725936d80daacce2487f46ad23fa8af9c6ca0367fdb0000000000000000000000000000000007039a0e11cbb8bd940eaf4a192bb94ff8c6d6c79f775fa67821b5ba411641c09dfe9fac4cf45eb5fae52d2fc4beb6bf +000000000000000000000000000000000f73a297cd6444809aa11b0756167e71986ab31b52b57d3c0aac5637129b8702ff21ec649541e79644c27f0017c8ae3f0000000000000000000000000000000016f96d6ba02aab604dd918cc799cee61cda4c0164ed9f07d4932fc4ac3eeb92b1e6b40dd7b18cd8d26056b486e57ed290000000000000000000000000000000012156f3ca3aa1e79014dfd92fbb6c785cf0ee449a8920b89ad04355e0fb7c8ea804bbad082b4edc9abd3b24ab0df2b61000000000000000000000000000000000d51b5f62a6e70816d7671bcfc52f11bdac6221a23287286af78605b99ae8bd0c722e485bd0381b958a85f61e05de68368ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000acc52d0fca02c3228cd2e5202c4eda297b8227bf4e64308226bc487e5b64738efa4c07a3738397f90251ea9a1a9da29000000000000000000000000000000000b85b853826a28777a5767d5b1966ce12fa8999ceff5d6deab5c947fd19d19de9c103bb920bad615186d132ec22187320000000000000000000000000000000006b5a83827dc7b3580579ab7976a70ee160b712580919b6f5d4e180165e50f5a1698fa7cc63846eb1f5e6df955c3eefe0000000000000000000000000000000006c2957d8adc55931900145388583e5c2d5f6bd784e022702801c38534d2c92c6df9f95d022aa6d800e1e458eb7f313061529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f810000000000000000000000000000000014e4c5991f9f2ee262019c1344a0843756157dc85aecb15718217a2fbe23fe0843992dcd3953ebe79acd85517acece0e00000000000000000000000000000000076a18fe710aca2875bc102f21782c9649f107684a4edcb0c4538f1a2890a2ae5b46a182d5470e620375327965b6d37700000000000000000000000000000000142a0fb19b28a034d326121458628356561e50cd3a471ee78bade0733597b8b90f647f5199d4b5b1ee6be4e1870bcd310000000000000000000000000000000018f8b5933848813cc2c1a0f079b095d565e7875ba6693eaa10967d496fb47257c9c674f301349dd8f2d22f8857f9d5ca44d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c000000000000000000000000000000000db331d2b965dbc053b01a61e671d2ee6b04b072b6494e482f48f12221f23e3b1ccebf48046d92b4be2e4283c77f51380000000000000000000000000000000016704f3e1ce14f49df400592ce29627833ed1dbb91ae5f00779eef94fe9ab313c3e7c8da940085034e1a49158043599d000000000000000000000000000000001956d492f5764c6de0b8e9a716766c762620ebd3265a95b47a8ad2c0614c337692108800e22abbe321d77a6cc17f4b880000000000000000000000000000000017149865739d6aed0f2a4c3c71c2d02f8080d9339025b03f89a37a165fe6e5a4cbd489b5fc90bb2cc432e5baab213c8424872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000036804da102cce975f980ed5a69e0464241b5de87238f9892c77fc2b6e5ceb00d7a37a45b5520fce5f094f8b9510f49b00000000000000000000000000000000049da8b6c974f2d680a80d2007333f15702f1517d3dc11395662ca1db945c795bf64167840c4df0fda68a69e127b2d590000000000000000000000000000000000e94cc66f1ffb2112e37cbd5b4feb7d65032c2e57260504a42816aeac85648558f6997ef12028655103a8cb9de1297d000000000000000000000000000000000abf7703ddf6995d5c29124ba9a3f890854fe0622d547a4f24d6a60b036ec9e58f7ec2deca5a71e1fce2210cf810e2f901a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc00000000000000000000000000000000008d22e456c643ce680f5ea14553a9c249a43d4f92d94135dfec85bc58967ec01135507bd8ac3954b5876c5bebcc1179800000000000000000000000000000000022029d4abec7fc9ab3bfddf2f462660bef7449c4093144d9b7d6f9e84f4f1c947855ca6e09bbb3bee4db096978ae0dd0000000000000000000000000000000014beddf6a3fbcd621e2a592e1c87952ed277163ebf390896f7c668944d6e0a026d3df74b0fc877ed560527a80b981d1e000000000000000000000000000000001414af918645ce0d4d1f670333fedf286b01213408019e327d3cb9321f06fae311b598c2f78bb578e85692e6cb787a52c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000129f1e25d96b8c879710a81b727b31d27ce9887c245bf908a3768f3606870ca6bfa70dbf5135819d36582d55f230e94c000000000000000000000000000000000e91eaa33e7cacce4e1d6d0fe905c72221b534a72cd51e1de79a25ef0c06ab454a849a241c023b0f82aa07de28e35869000000000000000000000000000000001379e390f2f0f3636312465469b532d876529d58dda8b024b6b81d242af47b5720af4360d5a3172ad80fd9fd8a14ba2d000000000000000000000000000000000775992d5a8ae0640af845fae03dd0b2197699f413f90f6130d21db0dab042324094b36acda26ed86c65821d2d8a29d9e62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000f5420156358ddbabf31fcc94678866f899e38747e79dba8ae280704c4b199a03eb423ceed18b5cba7e7ce84583c84a0000000000000000000000000000000001127669ef3ba3785a859aa4e942e8fc3181f2703b0ece6ddbee8830d7ffbfe498794f1ca2e67c3ad39ebd33e838dbc5300000000000000000000000000000000138113386846310db8e21fb8bfe40035cd89e51736b491d5f2d3cf5672e6836c25f62eab80f25ab49d16dbb83796aa5d000000000000000000000000000000001711d74ef4995b473239a574fb8ea6edc6eb7a88793a093df4652da240d069c5bf9249b58e9b1e11f7d6619cdc28a5787a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000d32ccc6598af8156f1c5b35e69e7c7f57f9fe18748510605a2a81b4ee09882bf3fb26abf50206cd57c77924ebeda8010000000000000000000000000000000009043d364e0637c60223f9a5db8c50e983746fdf4c9f7986d27f5f4f3a6df487592ea42078f14efcb3eb1b7e81d058eb000000000000000000000000000000000233495c4961e71cffc2abcde4007c0d587687aea905f3ac5758d0f8d9020197adb6f9d7b86a542b8efffb05dce997130000000000000000000000000000000015b084e773e66ab1459825b6e6dba055a96e4dc1d94ac0b640e906e0a9f12d2124a58537c458e6e1b571311b93acc26c0f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce8880000000000000000000000000000000000e4b859c679a90c03ea4d4b0b3d38211f685db053aede0f7f359f712e1ae808185758546877502d57200da2c2137f37100000000000000000000000000000000173b24ca19436b51aae22838674c41c752536eada3197de6efc98303eceb3e6e8e47ee6679e61e3cb5c8c734c96c98720000000000000000000000000000000005232b8c97a4860a23999d6ed6d173d300ed50b77c7b3ceb4e8407d9d6877a6004e2f76c553bf458b7cfd8d1e6fd364e0000000000000000000000000000000018a115201e3f4eb308c16656b3ca0635e6284169cee3f28101903ce1cab0659c3d83a449918df6e58e8af2e001036b8d9b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c000000000000000000000000000000000131232788aa3038a6b8a055a896af4f8129e3dd3397dfd90ce86b3e09a775e5b5e19f4387f4c02200a36bc2a1e09d98000000000000000000000000000000000eb8cc0455cbaae97dfd05c1246d3d5ee58c286d263184ae342f5c0ef432355a574bb9fb8ec67634f999b6d1419f2b6900000000000000000000000000000000188b8a85a6b255408f074b3cab66b95e0e1a1b5b8965034246dcc196f2bb84aca3a78907409826370bd65cd4c4d0bcf30000000000000000000000000000000009603984f6d9876e9c235621fa817efe45727fd8c4f76abb7b0796ae721701161b39ff7cab4c57850014e7f1750954ab6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d40500000000000000000000000000000000025809fb06c8a31f31ca5b4a5c795bc93355c78d9a2a4c1d707e32ff2a71d94cc1bf7b709cd5d6a183cb05fb6b5f360c00000000000000000000000000000000127bd8c9ee6388905ffe59bb0fec0e42b4aa44be74e5961dc2353e474baabfea86c41c6173db413ee28681a6bfd3ccbc00000000000000000000000000000000181f40dd8581b9adb2981dbcae27c7e906138569ff41a833ed3e6ee4fb0baccf2ccbe5b28ae2ff8e08c4f534116b58c40000000000000000000000000000000005cdd822cb47f35f31e0cbc26f6c957d51c6880369af94fd84daa1f1ca95e41e240b910f031585842fd2dfb170d618aa88e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000004a06984a3916820368076ab8cad6ffffded2cf1e67ac33f539ea8fc7a79580c1969e55b2a2fe3b31de912d6606c20780000000000000000000000000000000008a1152a581b6fad2a23aa8b0b51cbe523e701193207c896d08b99a672dc047498e565a568b79f8f9188767ba95212be0000000000000000000000000000000003539e82e5b88ef660b6593fdfd9591ec23e7109642f4aea0570f1f8f8e00822d2af277632ba74910459535b35ad47120000000000000000000000000000000015d3441f621c7e6922c489e474f80ebeefbef66cc59e4350b6f803e409034b7f498be2dedc97d902590fc1e296fe983c26989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000f775e13276c2e32dfde955009422557f332fb42dd9ccc3246d2b080e3ec44d910aa734478899698a9b04f6fb1a8f922000000000000000000000000000000000460ee4df6dd0184bcdae6d53cb66967c2213fa878a829c3196664f8d594ca6d60bb2a56f93bda3b0d2e6aac0a1a222d000000000000000000000000000000000fc9bf81d4cc80ba4e4df7307f976c2ec1ea2415df3c263cc970583824cd83703aa994daaa6e5c20450da2ba90a242830000000000000000000000000000000011f08ecbda9a192b232e8330ccbccb16a26bcf4791707f2cf52c2e11a8b3993221666563a772d82f4665804275b03b613d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b100000000000000000000000000000000027fe7ca0fdf1cab9a52e304e55350195492abecce4289b0f1c02235412bb012803e7eb59e23c665ea86dd4f74c35c440000000000000000000000000000000011301ecfc78ada92885bcba8af75da6cbcb448e0c49511f3ea306f4ab944f5bc114e72f473cdadee2d0e84021905c5300000000000000000000000000000000010eea529fd3162ad7b49638a70f6f2c26a6844251b2c2f9f8ba54cd334914e84e5a1ba9c7b4e7a8b9cff1a909db78bc8000000000000000000000000000000000b8a6235a7310d52fc8050bcc484e6ecf299099e193f91bea9db31fae71fbd14978984a9e6de10939d0fbba96314b0a55639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef,000000000000000000000000000000000de312093622aabdc7523cd72f568060f4236c7287d61c3372bf81d9bfebfda2795c3182d508f0268d8f445f6ea0a5f3000000000000000000000000000000000b027f117583406916a8f139d47227bbea28502ed0df91cf0841345435376c944a587c3b4bd60f8ae0be7c7bad1c8199000000000000000000000000000000000e9a7b96136b26b0044b11288d35969c17146241aa529e581a8fcf000c33fcfff2dfe1e55c0fb63f6032d0b6b0cf81180000000000000000000000000000000002a442e740ee390d87ec657fc218b76adad7f6a766cbe8f34f4824ecd1587deb3706af77a95c1d5f8e79eab1dc482c45 +000000000000000000000000000000000f54bcf1637d03854cc2b785e52bde25de7e45308048ed8ec0169069c2124871782bd9d26471014d039c9aa022e1a99d00000000000000000000000000000000106698139b096a5a79d43321ea64adb783011f04e5779625c9f77e5c390b46ef0d249387e978e64529bba2db8d7aef2f000000000000000000000000000000001668d5261a4ba37d79c76f44eae9ce2aa3e216c5fbf6cd2e90c6a73cebd8b59600303afce70de3e83a08c20de4609b100000000000000000000000000000000004b1b122cb55e688f8297913b84d466c6f3d99c09f4b039660238c8bcd0b7f6977851a6ea4b1deb01346db06d75180c142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000192a28dbc40d5ceee4d33b5c2778cacf8c3ed7d3227e7ea0d6fbaa7cd4a81134b63415f4f1960656b1fed15023ce3a4400000000000000000000000000000000138f296c45594a930b949756d0ae14dc9a720bb2bd9e93c7895268121a086a9d55c10135962a172c02da1eabfcb8caa20000000000000000000000000000000001605ef8182fa13a09a6b7661472296af2b0fdcfd7b051e7cf1d9e6d7c7f4ad9521d7732733399bfd5d09a088f25d215000000000000000000000000000000001928f2e5d47d7273e035114cbdeabaca724409a56056b4e95a4ca3b2222716b3a5368da3ed406d73f43e9571d1e04902253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b70000000000000000000000000000000009b35f132a903579d82cae6a321c1ec7fb0281c3e82e9af05c3b2830ecb4a941da5b1637c1bf0fe9a39fcc9ceb0d09d8000000000000000000000000000000000eef9c0846064c866ae07b3709091b8bd48bb6b20f995b44fb49e030b5cb6d78b7f8201704b53697190a5e36e9a4541c000000000000000000000000000000000a98a5d0d5640d6399a3580036f0e5cd693a7cfaa26438a00767d5ffc0777b83c516316d9cd4597cf8601544038f4d9a000000000000000000000000000000000e59541068a62f105a0d26a5f79fa5fa8b41b2211f1fe674d84dd853663962d64a7f70e785b51ac3cc07267c73400fe6acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000157f58b1c7152a7f931bccd9a79073967ec28855a6d74fb8727f59c5e3728fbf07a5032dccb28eb8d8b24229f2dc1880000000000000000000000000000000019f41bbbb853edc1fe3ee82f901e613107dd4ba1d880284ee95a2c4cfb2220ec1408f8bff14defe59775136bc75b4a1f0000000000000000000000000000000015538789157505a0798aa36fdd171e0bb14bdac75339b35805807c18bf9175d877360748f97a8570754af0e28e89df660000000000000000000000000000000010500aaa99216aa979acd66c5b0cea2a6a973f1cd10c412e823c61cb897bce54d783a6c0acee22cf9052166a4bb5adb8d9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df000000000000000000000000000000000439729e13e6a9b5baafdaac65783ce79a5972791610a333224e61104d15c746d7cf8350e619f0f72cb73635f6795c5f00000000000000000000000000000000092e3c976a4a5424b09e50e6513a9e1f427356ce161e742be31f0e589e9ff862460d41281f0bb2d27b1837a70a5938fc000000000000000000000000000000000e0e51e92ac3cabfd999cd72b67cfc488e150b11b18f9a31b1c2338fd4f2c58937521b5a107752c342e67666b99fc42500000000000000000000000000000000023d8884aa3f556e98e006960293230ac966ad18f3f715e6ab31a6bf0872c04e6f115fb1608cd87ffb369ff31012a11705fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c34000000000000000000000000000000000be33bc145611afdbadc636e9d7cb7e3a9c92c32f6944a2b7b5f44c248a0754c174e3286ad307fcdb2ea02a3578aa588000000000000000000000000000000000457de1fa8642d302065319b1d32009c64e7d941fb43d1b3cf455248664b1db516379df87aee05a651c132eab8aaccb5000000000000000000000000000000000a711f3bf1bda60ca49271e8a3143330cf924328d3ac6f7a802c15be1d7413e300f398274f338e6bfd0225cd8ba25fff000000000000000000000000000000000a786c5c7b4f1701e292aaad9b2e47bb883409aae0c44ae813ba48f401f4e2146ea0b1d85f2ce862b6ac9ad3015d4b14d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee1190990000000000000000000000000000000007d03697e195a6b714fc9785b49e54e219694250cf5fe77553434eeced15422de3985f8c736996c1763d4b9248a7a7e00000000000000000000000000000000015841a70a168d2f356a8ad929e2d1433b782351f4833c51b50f3a1af48a85468c2ec02699550d21bd919203df73abeeb00000000000000000000000000000000170902520080c46faae2bf35de396d56921bd0279fc889f0187adbabb9ae52b849269d8097d5b3f331dd5a817f9b2ff40000000000000000000000000000000016846a000f037eaf5953b7c4b477e441ca4fa738895aa24dfb0ef01a4c8fc21a318d40a9424e151380084578ca413b3344c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f60000000000000000000000000000000001a50509bfb12040c0271b231c566d13510e6ba84448e59685f5bfbf5b008fdc64cd5e9456beabd23ac011b071e3a5fc0000000000000000000000000000000014a964c9faf1752170ca40cff1b9b4fa17f8d2b56a4c4bd7ffabb65798771cd624ba61ee43160e70731fb9b07af8ecc2000000000000000000000000000000001822ceaae7bd0a734f57b67e4834cfb00a6b415459d81c7d380a2e5b5c795eb1b6d63ddffb1131cdfdf0d76852c75a70000000000000000000000000000000000c5a1575b30e5470151ba055f577a0ea49cff869614c50194829e53a3e1a95847fa387a0f45d537cabef3a5925e61c432583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000018cab86a0d70fa30b4df3e05a91eef57f6505cbe4bb7284de56d420ef3bf315be9249eedfae92561c643bac2c92301ee00000000000000000000000000000000098ca598ccdffa9bc9d464d51b46ed8a8f22a87ef408cfa45fa7f78ae2dcb9f861d9d6a571f6fa702a71e783ee3395cb000000000000000000000000000000000c073c0a323c3051c302c0558463a5c030539d74b440fdcb16b42ad5ec097e10c16bd9a651d149dd719fb1fb865420a9000000000000000000000000000000000164e622bfb8ecd5eaf691abad9db38ccc64ff0fa1784d26db8c8fbebc929bc6d4dd471321e01233d55fb4a9661780b5506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000fa48147388181e8d0033004118848c50c6425f2e5f91945a17abcff4d11928d298c092d60184e75e67c7ddb9eaa8255000000000000000000000000000000000c535bc54df050c1ba8d858a346d3a644e03fe24873b7dc3e23518d44b06fcb3f52b4be6f11d3b66f0180a0a95dddf680000000000000000000000000000000015e279a2893c205dadc8e1cdebd9c85454cd4b5d7537f984c8f9d451f8316620279357e218fef87339f1728fa317fad5000000000000000000000000000000000316e343ba68c8a762f4c8f2a5c20f16abc4a7a8365556c1625df832219670619b6dc70727e9bd9a64ed491dc22cb9d57f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca60000000000000000000000000000000011dc72100cdf676e41f21015fa7c57897da8260609467ffd38c17868a4dcd2bd5d4d72e89cd0db2de83618222ea3b5cd0000000000000000000000000000000007e074f73287faf304f618478566b91c8e191b229ab40743081342e676be09c2523681cf7ca6f7a396f8589a4ae18a6d000000000000000000000000000000000ff753a16c16bf0dd1de9fa9316694214aea6f99b81f66b6bffd58837c00d7f5632ed5f8f4cdf32ec59c29241ed5e28b000000000000000000000000000000000851e26675814612bcfa639fe567633e1960578a0c8d2e6568418f633eebc109e6c8af97e77bb28ddd47c6bba8a7ba724429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c0000000000000000000000000000000009eb2f172db0fe9ac0332381d929fa200a97047f6e732570d23fe27f5ea3013fdc52fd0b5ee74a4387af44647b75f956000000000000000000000000000000001355f8e1cf45443855f2d62dba0fe45b2bfc4e0d06aa7aec7e4f7f9c4e25b33d9c46a01c224517bac9a1390a9806ed4f00000000000000000000000000000000179d47a62a5c847f47341b1ba58f2c3b073c5282f925f57efed1fc43db04185955075255e4e4f6c209757ddae59101dd000000000000000000000000000000000ef5f74d4b13754ceb3b468879f1a8befb8bbbdbb143eceabf2dc8e68fe6cc8e1ea4f3eca1b23a1175c9f5f5c4c20d3454a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a0000000000000000000000000000000005b70a4d5b91b85971aef26b1521e12904b7ad224f25e31ec6ef59856cc702043a3eb975bf21dc8e4fc55171a3865bbd0000000000000000000000000000000007cf7c3e75a837545b53ca3e175a275dc6fe42fb88678aad45910d150ea9c6c94eba615429540348bb2ba8efacbb20e60000000000000000000000000000000002eacb469f5f8ee6c9f557a6ddcc854e955c5b9203b4ca5dd2e097d3e021479e13629863eb5ff17db46a17d3b0227f58000000000000000000000000000000000905e66f3a051b304b110a8682169fa749ba0de7763d3af7edc3e40f2d22ce7b6aa00cd06d2c82d74f3a9709d955f44e13814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa0400000000000000000000000000000000060ac9ce51426d360eff0d911d9f97a86494340bc5c5ba31ef146b55ad3633ec57a700f04b0cb9d4e91e13c2cc5e68a8000000000000000000000000000000000df205ed85e27c25ce27270384d7c3e58c4e0a9f214d74cddfbc7904eb3115e7bf204375df7558c3e65f7a81a942c5160000000000000000000000000000000007a220d42ca8906013479442d7204457b3ff37c9ee70d64f9f6858ba788b7fc13b71d33ad527c6fc673ad8940b0f01cc000000000000000000000000000000000ad481ef549de13b174d82fe88fa57b7e31ecd8999bcdb0c7a8735ab619a13b1e684b9473f0c59c734567cc08c76ecd6aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000000366f604228e2dff2348a462c56e0043037d1b415ffaf155e72c559d185c6b0a0d125585d060f159a8cdad959af631f5000000000000000000000000000000000f69e829a0995914ac122299d4424b4e2e120fa4913939d2f18f9d1496e7255d00ff0829c20521ef47bb0dee06c28dab000000000000000000000000000000000a3efb4a376281a60f5246d8fc10bc23cbb9cb71037f8f57271a9b01f5e0340a562f9acf0e9a95b8c65ab7a5cd95520a0000000000000000000000000000000004a4ec86e2b04bcb35c7840d85cd1dfaa88e17ffb557ac591640ed8e563cac891793b92e349a7903c6c1f88d26a01c88c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e000000000000000000000000000000000f5bcc27c243ef65dfbfc0de6d431706ab20d6cf6408ca989a2bc1c52b78ab63de6f58b70bfcaf6878a2746f249b6b160000000000000000000000000000000016a4c9e8ad0634e8afa8606a1a7bd1d8cc0815dfc6906b6e6446e0ceddba4a4a2df979d27cd07b8982a12550bc700fce00000000000000000000000000000000051f8d972362caf0a8a39045bb468112f2e73afa392079f8a4dc4c3a3cbb8dc224c21b6633a5ffbad08796ba2f8df44b000000000000000000000000000000001825aeffda04705ded9c702ba30d24b9fe8eb7cb106ee5d4e4ba029dcb57bc42c74e74e92ef8360cf130590b838645429680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd,000000000000000000000000000000000d0ab61b29ddea1aee0ca4e81b5369f37cf45be383f64ba0b1a5a74b790d7264016ee671959444c94b8e6291c5158ea90000000000000000000000000000000000152bf3709c56b3add8e3396d17abcfebbcfeb230529ea8144d6a120a0a6aa83cb284e40ffb9fd9a96f8a2f7244212400000000000000000000000000000000041f516a7cb2a7137746d028b0739c79ffd8f7535f20ba3728ede32504fe058baaf684cc7677967aa46777818b1fb6630000000000000000000000000000000009f1035729c55cf6ee090983a54d8c0574bf96342901f471a2e5380f11f235a075b0e157c38c456b6eeeaa10b87d3afe +0000000000000000000000000000000011ada4731ae7df493e405383603a8d79ef77f3fd14fe7b8bd9d2afe068998cb712e84927d5e6ea6e94d7f10270fd193b0000000000000000000000000000000008a14eddf88826dc3be792a0c1f7395efdf91454cec7e26c89f6beda37b194b706dbdde8745129e821b6f4b4ea6118490000000000000000000000000000000011c29513e8a826e6b3eefaa20ad841605d04b813cca282fe02dca0f588b9a579b2195b0b080cb6d12c1a7881008117f8000000000000000000000000000000000689c67d05ca379367fec99439e3806f827218ffaae995bf38dd8e2919fb2e751f426525cc2c6ead3b9aff2e377fc99e1ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e000000000000000000000000000000000dd683a8e4ad54b1a95826a3000750c6e3cb250ab5d6add63c21b182d736b220d917d4e70044ec7101c3bf8ac620e1dd000000000000000000000000000000000f3e411cc6800b304fda1373ffa60c7718e20bf3e2e5f9784a81b47e398888b366e1f04f48f5aa070a661b5e2148d4fa000000000000000000000000000000000b0f8d0b695e000158ba80881a9256ed9dda5a7f53b550bf3b5c67ab160060fcbf5ce07fe38253ce037abedf4c6f08d1000000000000000000000000000000000bb92d407c457e9ea7b9851770d2743758e162dc9cdff2dd54b8271c046f642729cd2f10576013adac84a46d38623b932943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b100000000000000000000000000000000023e880685aa69b3480bf2b7f2aed1181e094322da9e79c9263d50a49ba4fca713740bdb55886fc81c81a51045b35139000000000000000000000000000000001707049fb8b7ad278be2949b9eae2e28bde9de1d9eb964eae582541c2d7a8afc4c1489624a0919047a167028b8c77e3c00000000000000000000000000000000062dbb2bfce2f67c32b87ec2fa01ebf7deddfcbeda2fcf0ef094b1be77b7411f657e745350b6d2da16fc83a96f6f20e500000000000000000000000000000000062daeba038c7bc379f56ac371745b91fdfd5b4cbbe50d9619bf1d077f3cde966f81f9b851ebd206f2609a780b6dbd681551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b553826dd9e2252c9da74c2bf1bf850df3f9c37439859f93df3fbceb7cca4fd949dcaa7fff31c9e06f41e51ae0b30bc00000000000000000000000000000000187810711ea5911a437a62e2ca483983bf2535ff9301a1cfe1b4d41902ef689f8d86f817a2a7c77128e4ce1ef6b037d60000000000000000000000000000000010170cf5f2ce08211cfc41bf54cfaa16584f833f7b97b2f6bc436eecc56ef44463690ea1f5c8c2a8f69d93a25206282b0000000000000000000000000000000001e627a68dbab6b0d05c85e49b966a769461ec38c38fd94992839bd0d46e06410fa7a48d418d65a8285f7852e8af4b318eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f0000000000000000000000000000000011ebe2edc3de58a57aa9ab4d6626d7b93235ed24efc3d75c1ecae376c00beffc5e89ec509d243f693d327f7a4551921f00000000000000000000000000000000088ca2fe0651e4d8f3958454640a58ea1cdd804bfd2700bb1bb8e26ac50f2d7fc8c292f94b0bccef5735c4548025735400000000000000000000000000000000154936de8932279cd39ae803a5d814864953f647a5334bad958222de765250e4bc847e02979689dc9cfe1993486b5750000000000000000000000000000000000c7ce07c9746c6d72dae11e243acbe12dc23423f870f3130b244eef34524d547fe0b2c4b704ecb6b2e6c32f5675ce67ff72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000031ea855125d75321a2a86a93e72fb3869dede7531dbcc1cb07ea2a352f3c6cd913275d0d43ccc370f4539f668f205f50000000000000000000000000000000006c4cadb11361f164f5899c6b57c0c6d8af365d902f4575c9d2d14dfd880501ce9ce218544b44bf07f0f04ed68e8f315000000000000000000000000000000000131332638026fd25b1a849c984f9dedd71e64fb52a61968666ba80238673077ac00b9e09817426ceac8c308f475303c000000000000000000000000000000000c7634af796e7aea4d4d83c9972fc822dad951d2473210ad82706ae0aa023ea85c1c467bdda68881094ad2a4f54cb33f331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000fcdbf0083065e13deee2020bb6e47cb9e482df3768ce569f1f7c0e1c6083c97d9f08444e67857c2dce40e4a7b8d50cf00000000000000000000000000000000010f246e8ffccc2e752049f638617e122773a6f10220cdcc0603d24f1a94ca7c100f8ee2d9bc7c0a931fa0385eee456f000000000000000000000000000000000f8b68941df75cac3d4b6b3bee43fb357c8f4e56309d8509fdc62620a085d7ee58f52c7dff28525a449cabfd3b7ab3dc00000000000000000000000000000000019f934ef0c7c40786b073d38cb3e4623544cad59cb63440d4a6e76944d491f6b982e3a5e84124996634687d4618418316d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000c0208c1f3653fb3a5e2acbbb42f2598b22db1a714d616ee6bb501c3338e80db34d517c7086d43ddc77e0134dc5a4f290000000000000000000000000000000000a528245342e44e36f8e02e7259749e63ecfb38cb0609075e871701f2b3bb0765277b78d28cc3ecb7aa8c9e3b27eaf10000000000000000000000000000000010446583a905864064400f9ef168a122d179d46a058525c9be8a65a5d2ac5e967d51185d4964f81a5571123717210d050000000000000000000000000000000017da91a1d0358271b11a0aa524341ba1ee8c31bed15efc4c9183d60c6e1842ec4383070a09914fda991a63d55efa8f2156be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef400000000000000000000000000000000176c64efbfc9958b9c8e71b55e9fdf525d4e5a0265ff01ba95bcd5c6093bd063726f8e277d00b138fa4d8c8f80afc4e200000000000000000000000000000000183eaa6c3c605828852ab5e8a9432bcb87411dd18d574cc2491f1a280e7a267ff9ccc80b06c22e95107a72f22ba2fafc0000000000000000000000000000000013319d3a8564ffcd6fc7accdded740127ef205e8299b390d21e96b2609cbb463569c878f36191d43927868b06dcb912b0000000000000000000000000000000000fbde0ad8e89f5458007ef6ba0f01d0aba04217e06745a5571eedaf544443150f59117b56937f533b4974e5d57c41cbaea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef000000000000000000000000000000000c044a5116e175ca1d1ae59d400de24e4f47132251b4b3dccdf458623c36b4d3d83abc644a2247ac4d0e3f195d12e7b000000000000000000000000000000000048dff6bf65f158b19b992167ff8adb5c858a154bd68bf0c84e41351bf47a8f870cc735d1be5d9afc62bbcda2fcdb1c20000000000000000000000000000000008c5539746d2610eea22e79b3fe5b33a47fd3bf9991d34c6f9d824a46458480b735c0051d7b4e4909fdb1f2a1a4e4b3a000000000000000000000000000000001936558ac97acd903a29d07c4aea399227ea13fd6dea820813c5519412c157e1a477fcfbab60a787c6b3834eac4522889ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea0000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000908ebe27a1bdf0b9e56325c00ea3814527005793ea97eafec541c01cf2d7c909d2521a5fd475589a31e297cecfd5e7000000000000000000000000000000000017e3c40c60cd369ce5a90f6c4aff14896cf73fe06432e71940bd8086e36c2353d6bf9dd414bcf92889887e2d49fbbf5000000000000000000000000000000000ded856e5b2b139487b3816351584f06582a933af2bd4573a89aab0a41af01ec1cb928a7d8035228302032d399bc7caa000000000000000000000000000000000833b77c5d5c98ad95a144c0f167fd3bd62b03f4ad721561ed1d84c7137dcb19521f781bdd3ddc22afdd52c75146e101f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba680000000000000000000000000000000005c6f28c5ebd981fff3aacd70eb18f134bffdc8507d1a3aa153e5787b68fba7f4a94c43045d2676aaa992754783ae87800000000000000000000000000000000148ff39e8062bd488accfead42a684f781c4ee579af6204b5b8dabad9022b029139b1f3670fc270710ced9a53253850c000000000000000000000000000000000ff50eca1a92f123e2534b3289f37ffd5d4e05f7678017ac20e35c2deca054dbe376c5529cddb5e58973f5c60914f251000000000000000000000000000000000b58298ba9496fe32891f4c1cff25395ac5a447205cedaadda4dcb929260ee55781916ef5e4e39793fa2831142111226d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a00000000000000000000000000000000184d156f881f7d10d2f196b7599db85ee826c9c95383978ed68918756f642a2ed1c951503251b0778dcc39598d79fc8a000000000000000000000000000000000952168761380e8fc90a4966e94b8d2b88a784f6e607c99d9af1aa902506f59d6879153339fdb7b8acda178b9bce4ef90000000000000000000000000000000009997621d4e17c76b7798ef2f99d3c0a7519cce278cf718789cd8227b2b1459af7fbbc93078aa0aa361167b1d1c9363600000000000000000000000000000000005369eb3a77d2e26f9907a2d930f39dbb87634346cf10525733aac8ea10eb918d4043d2a05ff8e80b9c69a670e17f15c629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b0000000000000000000000000000000019bdb390c66f7d28cfaa91bcb34c5c55bf93a9f2345ea396f18ed33ff2221a39cf68c5514fe091f7882e82470efb1fee0000000000000000000000000000000002d0b48d2c0377b0dffca247b7625f9901f86e2161626b4154bc25d6c643a48e9addd260298bedaa80e42caa5b9fc5b10000000000000000000000000000000018a2b0a760652e546eeb42e857ca48f59741eed91822c17692e9c41358b213c82537c9c6898713a13a241cca627a7dc400000000000000000000000000000000079c02f41fca45a56d9d8e305141b4fe8f98d102197e7864065d342e6b07f65b62632e0c12660f37de4d698c0df3d0f3c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d5000000000000000000000000000000000c0448fd4ebe9b5615653336fe0a618fa281b0fd7d72a8f956a5fde84f7d356b6be853bf823436bc0b61a603636db9ef000000000000000000000000000000000dc4f2b4d810c4290e263098576cac393fce137cc901b3be23507cecbda7d86d18022cf8e1a7df4b1298520ae5c9314c000000000000000000000000000000000a39413967b558dd8a6b2bed972687d984fb9abd0662a266680f8c90f1897e2aca1ba37b41d7d3fd47406bc5fa3c5b7f0000000000000000000000000000000000550fcbe5bb75afdd8d5f387798a8e83a8dbb6da4918c24eb2e5d2d8acd3512f6649a4ac9c8d3e6794e6f4f8a87687bc685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7,000000000000000000000000000000001654e242002aafa89c6fdb9e8fe2c197ad2f8aad11868568dd39d68ca35919f94308a80303655bc83fd130de6f9723a900000000000000000000000000000000062b5a064840a5a28b4991ae949f9508586447ad5e8c463593503c0e5857c5233b7ce7ac03e555c2675f2e320e8cee6a0000000000000000000000000000000017d65fbd7caa69629f66be8b201f53baee5ef2957a3c04fe384ae82959105342b52483eba6bcc1442763c677f515f6cf0000000000000000000000000000000002ef8f8ed1114cc9d299e59003c61d62edf8971d65b1b621779bd7b270c4123eb629f56dfa2e2723501588a0caf1847c +000000000000000000000000000000001392409b92282bccbdaa0268e1173e60911754eb3cdc28a52e93f4d82ec99026f314dfdc59b39a4f988100f9c30cbd1e0000000000000000000000000000000016b3c555d5c196551ba715c6c334a668bcae80f5a17f038038d35dce34843f79968a90e2102f0faa22a93d3240b58d490000000000000000000000000000000002daf83727fdf45dcc1a15adf47de3f8a1724cf4d34116f52106a9e6b22dc24a288e89b940cc57e5a6bb87ee70f680a5000000000000000000000000000000000446009fa3555e4a056a820efa7da52117c15eb105af57985d8e9b33b0b22fde6aef9bad30480c2b8c1246519795f61fc067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000000372ead514d53007690843484c966361661816e0d3949b868176d7a9bea42064f49113a74f2572a6dca7afa0642fa5000000000000000000000000000000001199d3ea66fad87074e62a0b77d3fb962db17dd948f30c38f5beb0e44e1cd11d9172b878128e9a64a08394f13cd786f60000000000000000000000000000000018b7db157bb326ee2f72d4df2b1e0ddf0a90401ccfca1d4ffd6379c62acf5d6e4176a23ded2f81653038d56d848b4fbb000000000000000000000000000000000a932cc9740812c8bde33b68d94220690e0f55618b7e51d3e3fc29d0cb9a8d42b8f8e1efbba5984c3c1007c9a80fae408b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d0000000000000000000000000000000015798d10386f6d24caed3859875be5fb1a43ac753f725f28da6b3583bd9c0e404d36265c2305d7d194e2ad84bfd2bafe000000000000000000000000000000000ef2ea5f3b6e03e3c9693d6db60019f2efa4ea586bdb7623f03bd035c603e8996ef2ea7cf745aa31f60679ca04f93875000000000000000000000000000000001792a66785a3087a80c4b8652c1e4db8f602cf75c1a6955f480a977f92ea262965dad84061f6045177c831dc4a3bf8400000000000000000000000000000000006ea3862318974d6347639ec0d70afe748f4edf32b9e437fd98f38eaf72168a153cac180c2d67bac8a358e3a4d57a2b32db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b00000000000000000000000000000000000974da7500df70d888d5876e7c61bfffcdf830b49bdd40edf65a2ff476e9add35eaf9451a2166e9781805192ffd7ac000000000000000000000000000000000cb2e7152b5b40758b18caea356dd8e095f400282881207c4b79d10d741756e526be261b98b726d5cefb668dcf73a0a00000000000000000000000000000000014aeebb995d464f4d77bbb72f15d9078936b5ab68eb8022bdd97d050576dbe46e6010eb72250c8ccf2a59138efb38f9d000000000000000000000000000000000cf7162768e8eb50e21d3c0a076c7bac4920c70f334336037fb40e57e0efa91eb025356ac3f0988a6b127408a02eb53fe2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000b1d6214796b4775c2b50e634a549ed104e6ebc0e032967b17eece6cf88c93aac23059f263faf3c3f38463270320135c0000000000000000000000000000000013ffa3894a36226664ff53ba9256d39c6312303f5cbda6847b4f68c56134b7d731e74bd711014fe374f909a081a7d02a000000000000000000000000000000000ae4590cdcb1367392635d0f8dc6b9557abd16290fd1abca6da354646d8585a7c9432978dc616e5fc38cd71d55f139c200000000000000000000000000000000124a7b5574ef52359b4beabcc56d3286db8c8fe4ca4718f75da28d89a8a95efb878c18b48360dbcb6fb50a9f18f0d559aa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a00000000000000000000000000000000148ab0e847ecac963f0156da025dbc52e765cd8827fd55ba2969da6775649529226ab13ab8537ad0b89e8f1ebc8648ea000000000000000000000000000000001395b1adb6a56b91c3621a4ac5886a7b13ec00f1c74d5317eb74a766eae655e09e269ec48cdf740abc38f4d6fe52dd0f000000000000000000000000000000000f70f77f07ef2909033665bc05cfeea7df6ed55f2f0b1b87d9f247b6c07c7e22f516840efe68005c3953a2702573a9b400000000000000000000000000000000166a334a711416cab180cc498308487b281711f2d1b832c410ebb4c591af54b154fc8c8d7ac9a49a241f7a3840acbc75e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa0000000000000000000000000000000008c19bcbdc2ef26a30dd88f3e35dc7fbb3c81c0224cbcd6b12c90883f3973bd7089636f997e5f213fbdcb79514c551c600000000000000000000000000000000058620cba8ed5b738167e809cf71392aadfe8f384a4cf397d10f674cfa914e9e02bb1518e42f16806214fec52d880f6100000000000000000000000000000000048ac1120d26e4173bb33a58c0ce86329cdbe9df6a6f268c8d5ee4f1d6110f9d81cd50c46256198a2462d50be3e781270000000000000000000000000000000010af13ba791d554720f5075d46d03b55c0c1dccd679cef5a7d439ae868d3ff2780cc3ab151feb72b8b92905a205e630449510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e40000000000000000000000000000000005c30a126c94b87c54270d0f23a486c3b36a8b491bbd805ae0d5f2bea818a87ff5aaed2d5e6317b786ab5a23f1cb48da000000000000000000000000000000000eb2d4663eca7f8433f10e84984781a57fffcb8f9535518721521ddfc7a4958778915ea3c57bef399a453b8ebc10befb00000000000000000000000000000000161947f57d97a858e5b3e918dbb22dbf28629e51e81335a9bf105d0fd660ef80087c8d69d8db9841cc69fbb5e7f81487000000000000000000000000000000000c52b6a559928fe4ad984a0569c081f3f71eed3d5b0d3c14d1a23afa45594e0fbd94143348390bee178720fc603145ab713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000071aa47d392e1a7787b37c52acedbb4632d5549fc11b79919bab7d22f1bbf1c3a239df622b8824b07f6e35e627283b8500000000000000000000000000000000198e72e05388021919dfc1b2a58ca72bf7655cc6c9b62abe3b45cc782ccfd4a2334780e451b8a6b7c311887036813fe4000000000000000000000000000000000e20cbedbafd96c42612e146debae48c7fab4846b20ad0848c4c42c6aa0603e72f94dfc938ed9e3a9886d221ccbdef70000000000000000000000000000000000c861d1878e63e313e672bebdadd3fdbb691cff5fecbc24da895febce2eef0a3c774a8a9d751498e4fc8e2b71daeb40dc040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae40000000000000000000000000000000005f7cd2205fa2e17fb9896efe3fbe110e1fa59db1ae5f8d6b5f4510abb4da867933d4fe3caaadc4457dcbb35f1b9c62b00000000000000000000000000000000126f2ef6022a7211fa865c1dbdd5b84d96cddff424b06647acc462408f2d31f34ce898d76e1e124db7c39e08dab0bff6000000000000000000000000000000000987f916ad6f718695f3c40703c59ca93eba38931b45d7c33c64c9f75556f075b744dfff8a5f21489b3db6c3846ba09e0000000000000000000000000000000013011b8c72f3853738e22957f742b05ec428ab0da28901800f787b7c3678449acd0359fee93c40c69623aa4acfc0a81017b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a00000000000000000000000000000000014b88c0586fa18333ab11a79acab8e12c6257f82a4ed16d929768a60a3a5d780a22101c32ea9b0099aa2816f18a0351a000000000000000000000000000000000de0fde69efd2cea7ae08d6d2443883002e0b4e11da253222429f6ecc67ba8d282eee84d7f46e0ad00b039a2c2ad226f000000000000000000000000000000000aedfa0a5a8b7577dcc1094469233f8b07e6fc32af26841894d498d70c6a9a046ad636086def948d21e39833c5b6c5a70000000000000000000000000000000010ec6aa0efba4995582585bb67f997f60741648156324696312d17656baf6aeb3e2db0d1a272912fab2fe81d139e971cbd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf2575200000000000000000000000000000000004a847c06abc8ae7ce6e6ff0ab856889dd3e9697a75e3cd4d2af9e06d4c2fc48c0562289348ff52f4d9855ad03d83aa00000000000000000000000000000000075673bc79bafa9a64de6bb0e9dd9fa29cdc9c82e90a7348593eec673cbbf22b1eca436ecf767d45852ed888a3f23949000000000000000000000000000000000f3f8543d1e667404b4564dddba4d7c11d13881fcd8ad774c8eab8fc599f55147c353cd6e163cd7b9d5da55ebc13c2e800000000000000000000000000000000069edec7e7d26962d88a89dfad213daa36046bb2851e5d67adbaa227220f29f83ea67cd3747e6724f148dac28308604cf221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000c945e83822896974116663d3e2769f3df5a70d55b8392c1f6966e330951f3cc5688742d4588648a6988b928b9fe00100000000000000000000000000000000003e94b7ff7c71d633ce69bb44d0ba1bfc7c27a5ee618e703aef81a45ad61771a2fa8e3dadddf7c8038f1f65ad7513801000000000000000000000000000000001727d768c1b51066d2af87a9da3e24ea2a75b0f75b8ece70727f9f54ab77d841e7ae01c9c0760f4186d02a28d6f8ddfb0000000000000000000000000000000000a273f9395cd49b646e90fd2526d5c93fd46c7366b715546529c9edf5cb3d274c9947c21a03add3e7b20612636a6745ba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e000000000000000000000000000000000c996c16a16879bd3194ac366bbd11b5863123ce6fdabeafe56407600e5d49c92ba68ac1256e1515dc9256de14ac26de0000000000000000000000000000000018c584d8a4f14900b2fee70b50b700199ec2372b731dd1380f42ec7fd3d01f0c9a007554059b85946c1c4f4e2fc504ad00000000000000000000000000000000073d6c7d671762e5398e4c9d57f6b68c3d97dfe0d01783f124256fac236f03b774db58b79cb4d5558e1ebf18bb9e19680000000000000000000000000000000008eb2b95e17fdda916b08ff2819cecd2eb031f41c8299b308339b7d9836382ced75e8eb1514a70356882d3a43227a9bc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001029fc28cd502caf3ea3619f6fd04bf457e6a452b5cad680ec2d4f8222a5ac2daa92b880bda76016973494e605ab28c60000000000000000000000000000000002c672c7571b5d8e99de6e47e0a2eb71c6d9bd12baf2b083e6f88598b32c4644d1486aef582c5936e622058bb141db1700000000000000000000000000000000033cda383a77d5b3adbb0809e834993c56717f81f8c66ad2d97f2b298d5a46f7b29a74d35da09271b7053a05af096393000000000000000000000000000000000132da041c6e3e1d68bbd2223f8531eabde8e180b36b2cd0ed4fca248f255cf3eeccdc5f61e1c581ce54edcfb2b73e0787eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000009721f22bc49f68d703a4dfccc3bae791caaf0d73892bafa6e9da465ddaf0fb1a069ffdd55306acff2407da64c1c5a0200000000000000000000000000000000056c0a4804a19aeaf1b4fe52064e43de8e5d41a8d77de054e2cfdff078eaf468d123d7317818d1bad1bf3469c0070b680000000000000000000000000000000007f1f318aed043d9ad7bdd53eb6a8c3167240fca75925b04795210700463c93a66ed64851195df1bafbbe4227d7db5ff0000000000000000000000000000000007b8945e258311e7672e842b91b540fec9ef4a79296956a5cba3749c0ad95ed83d7b0b48384ffb3188459e997b86695d3bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded,00000000000000000000000000000000086a1ab4c19c27f70aa422e8292752c50b365d6fe3eba21e8f2ed51f283df0446020834ad27c18b5c7285d1156049bef0000000000000000000000000000000007288f40fde69bd350ce1f4d0f68e645f42de319cc032250b76fe4fa305341e244e5b2366751d5311105e3ccd30e701c0000000000000000000000000000000011d0c487c4eceaeac009b694931f8eafaf8eecd6028f14a4de33d2940bbb747025eecd509564721b50b7186910f81949000000000000000000000000000000000366f0c901fb859b4bae006fbcc9ec7e456eedc7366c899f68090fbd457c37b03ab99ae982872c7888b65c1a056c134c +00000000000000000000000000000000072f3f03bb09ca30239dd8302b05e0d9dc4e43ea33e865864a82578c35eafcf6868bf0cd9431b92b76f00990b780ffa400000000000000000000000000000000170b76cfb7944ea5ea055aeedaface3e8f0fa4d0ff657fb9d5311f3af6e736da84a5e2bef5188e20f76fb42591267fd9000000000000000000000000000000000d85300009165a8da9cb8e590f7f8d372e4264df150b1551185c80e49dbedaaf872ef69c5763fc3713d0c087c89f21050000000000000000000000000000000003ba59b682174ee61630df95c8e2b1c48ffc8f7f8508c21f3bbe8f7bb3266521fcc06c8f90fe5126d872707872db6d59f44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000004e8f16480c2f080a13b9f2b66e6480132d76c4ef76e8bac995a8e33280073ed5610865260e154b32f75f527d89620b3000000000000000000000000000000000f9ca48d732a8055d22fbebf3d2bc1e1c9c815c184f594ad2337731709317ea6a205478ba05ee9271d35a19dcad4db5b00000000000000000000000000000000078013b9290284e7ad528a1bb9a2a64b3ef43964c7226ddff8ca16ab17b4a2e8a2a7d921ba924a718587954f586a954800000000000000000000000000000000004aa76bb1122116cc0c04d65265d8652f08b411632a732a9e66d7932801b77c4ad398d582e446968f7f4966e9167894de25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea000000000000000000000000000000000145de5d101498bfc7c57830eea2931663ca1165ec85b77654c866b04ba6a28bfe710c1aac9876a68cc6ca119708eaf0500000000000000000000000000000000096f9df9d5723e8379f2d09c76a3fd059be47d2c2ed8905d333b2464f72153c5f50b6345980626358839ac691c26c967000000000000000000000000000000001788ffa765c19758da6eb6c38e793190c64d4a7b116576f6827fc090b0f65304988f6a95cf4397f82b7691fc43960ee8000000000000000000000000000000000746e040d7aafdb06a31ba3d7b590dd28f0678badc261a93dc7bd9a605047ec67ba86b2b6dd72637a449872674d6b5982e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b21000000000000000000000000000000000d8f3cfe1cbd2629f3899313cff16ca3d8f964ec1cc0508341936a7b3b49240db1116b2c3de28f9bc45cdfacdb5fd98c000000000000000000000000000000000fa642ed31293e44211b34bb28bd5b389ae6d0510cdab46c89756f31795506fccbdacafdff21b0127e80557e5ba9afdd000000000000000000000000000000000715a8951cb358b0d8cc63377799a9a61ecc85dac795d726fe60e429d492c9ca843be2a2633c17f830f199335e5d7741000000000000000000000000000000000b88a23fdac7d35fc135b45d7565854bf010a75f072b32c57ca4d0979c111aadd84c71df6792dbdc8e975ecd46a15df2e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a913000000000000000000000000000000001171be5820b5a19c045abea399f2b8ab9905d2aa367c6c8c0f84eac132d26150b759a9c029414f1c8f7e4880214446c200000000000000000000000000000000147f0877321f2709183f0b617a7c5ce898db508a3ced4148cc9f7af011fe8040e90885ce817aa956d9f5d19dd968f6220000000000000000000000000000000000acb005c11481b214a17e3cca02c2af266e4c8cd928e3c4e221d866e9f296a2e913bf34c4e051c7503a5e4e7cd7449900000000000000000000000000000000125f45d0af1c010cdf8438bff0f406007853e566fa646df40a581f65496197755eeebaf4f0f77e1e936f399dc4c6c020e6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c40000000000000000000000000000000019cce3f872af5cc515ac4cd7825a5318ead5b464d50349909a70b415a8950206974ee0d4203f208d8e6d14690158f5720000000000000000000000000000000002e08e8accede11afe3e2d085f35c08d7d414c26a9caa992d5a090a43c9b0c0cc1471f3693f9d342a973da65189c888b0000000000000000000000000000000008a984ad2ca60c492cff2e95d541d71e33b269b10d3df107c0513dad5af511c51806068da6cc7226df1cf5e5a2fbe707000000000000000000000000000000000fcd3ad75bb0a5c046cf83be3d973bb3685bc717d7b8262fb8205935db6e632472496907f7c965fc6b52042ce69999f9e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f24350000000000000000000000000000000014ccaf7594d8ff6157f9439ba63480d3d07f44e62a86caaea510d0ec456cd8c6c4b42cf9e38713213eb4942ed45df2ca0000000000000000000000000000000015c2061c532cda006addd2fd6ebbae458197d55fb336f75ca7decc05dc6d421a65495b71ed11874aaf24a0ec13a7c65000000000000000000000000000000000101f953aed7f23b5b6208032f05b818e0147079b7764aa3134dd9e4a316bbef0309ac378ca3cff3bdeab9ca56cb78e60000000000000000000000000000000000c76a2bc721a4d3ead95af79ec24be9b7624bc80d7debc07e388e52ec621082b9a69f48d157b168af4aa73629697f784c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000761832bb5b530b80c668234ab5996bdc225c0c696ea07dcc61c330320404827ada9d58d658e230fcb39a96b339b830e0000000000000000000000000000000001198b85418421d96ebfbf436193b411a3a89c206d006291bd23254ed5fe12ccdad15725a34d962005c0ae60e202bb86000000000000000000000000000000000c1d7ab83b1d2ad57a407e248492773a357c06b83c16c6ce1490e84bc4a3cbae395f160181d2bcca3edc34b764754ab0000000000000000000000000000000000f1e9f0cf96d7671763739b6c37fd442f0e816c49d9c8e001d322397e9d6741dbf8769ef9eb83d08ab024294e279a02838cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d147400000000000000000000000000000000063049bc3282934e29f3bb3dee432bdad6193a5d2247270e88887cac565f4b986e1b3b2af5387cfca64f0d50bc0ee1640000000000000000000000000000000019f0f05fc7f8bf2f0b8ed375690b53b6dafd0a07c49fa55d36e040798334700a3aafc4995bb90de9c4dc0e077ee18b58000000000000000000000000000000000fbe702d148609dc8feb3ac11c5eac8e32a2f7221aa135cc33a585e9f4c97afa1658d8962fd96e26e0c4c1d5108229ef00000000000000000000000000000000061fe418d3b440e84728091a4996119b515118900f54a6f2da2ad5592f48ebc17bba50b59ecf435de3cb892a123ae9d18a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f80000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a4553780030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f6000000000000000000000000000000000549f6464b657eac28f838c6a8bcfcb7a189d6b3b9712e19c1a23503ac209da5f2ad4df83acd505b0231f00eb88515c70000000000000000000000000000000001bf4a46dfdd70542e9d8cd6d6215174cba28f9adbff31c02482ca38205cb4afa2f7fd65ecf57b39e4ee5cee320e33800000000000000000000000000000000012d04a693d565f96566b7c313c47d272fef0ecc828493b0841d58f6bf690a77cb72824a656442e288460ecca7cf05504000000000000000000000000000000000b33eefd5df8b098e6505cbe655a483ab5c6e417a4ed55420beab95e8614c8538dca9296a7848d6aa0495a173df6d0b80d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000000fdd9702ed88aa857254c3ba50b484bfc324e583659c57055e4b09eb1662af2f70b547a1eec139193a0d3c75b565d3b200000000000000000000000000000000193df0fbc5f24065008b5e98c4c4bf9f1e743a6ee60c3700ae4a9108639e540384eaf1f9d7a60b8b6a5d79e1f34949f50000000000000000000000000000000001022f8a254d17e448cadfad35b7a54dd2fb319c8f9ba219874bd8280a5077301ff4332d731a75646cd93bbf31331154000000000000000000000000000000000ca1eb350844ddd0a65a4ad56e1a96821de2c6633a4a45be976577c223e367853e2b1ecf2cc40b8595ba5591ae8e40f3969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000001f9cda056a0f8803be581634562e975223b5311f4752b189cb6bd6df1ca5e3824bbd2889b9b93da59e4f08d482734240000000000000000000000000000000009f43c25de25c5d76ee1a03691aa434de6a063bb3a1133b045797a279346fc938dd2636abf0c4bbcb528c9c28d3105c40000000000000000000000000000000012afc29245da8bcd3c0d96c4ee61617cd9ecf42a47c2ee822003af26aeb4e4de8e432ffb6b2d8241090b814401a8676100000000000000000000000000000000053edfd98742dc70d510f1836fcffa6a3ba9ffd4904c7f5559b48e49dd21071401362d0b39bc0d786b7ee2e84a76af0d957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000001dc3f016ea1a74ae50c21c1955ca1eb4a911026a1e72b316c7bbdc708caef63f0c1efecbecce8901d65bbfcaae429da0000000000000000000000000000000016ce9301888808323c9baf6402d7073fb85ebcd389334cc69d7947e345748ee44b2d6aab3ef818beb21b54a19ae4f5b5000000000000000000000000000000000c49817753eb6459cdb4bc737d3710b5f044bc544c8d92c8ef138ec9d83889664267e1a5691f4bc3fa235ecca2a973a500000000000000000000000000000000074a8450e35f1da18e6de05960e21b7059ece8972c36f000bba9e24488730a44ce3ce200c437e06703addb3b442a790a8e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d5000000000000000000000000000000000bc1f2e9af093ae8235c93af098e692e697ea0ab4c8f53019a6e950f7072b56d5eef6b3237710f1dd1cd1970668d06d0000000000000000000000000000000000d9a63f7a13ff9755c6a3832e3c4c852919514523092367fab7886cac317e564d57fb4042ef40e696edce868e697c45700000000000000000000000000000000129a30657466460db13575dca367105c27d631eead330319b084adfac591f5b3b94988925d778e6d4645d1d2816baad00000000000000000000000000000000005ad64d6e761a9a301589547929f4952ccbfead278cbf6658255a075966340f185d5f356679fb02ff2197468ed7de19a71a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000077d1e5b35c224e2cdc849c02e800c0b80d1c19f3d74d9eec34c40f56bbdb9e2b5d2ef274991dca843755f91a50826fd0000000000000000000000000000000014f3b653e0df0c608b75dee3496a7af04a828e6fc5604f16ed49c39686ec757e96adb0a667853006a8331c3d63ae4ec2000000000000000000000000000000000aae011375b337940f2a53d9091d3581e8197e79251b19c7fba01de987721a9d6fa694b7978f0abf877f46ec26147c98000000000000000000000000000000000aaffbd468a2eb86a3cff59e2e9b7ab88286d2bdd19c2e789b1a68810f0cdc76171a2661ab54e81b17643ff0275eafd72d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641,0000000000000000000000000000000010a2434fd3150f6b9b491d3a51226bdd457504077ef2ed5a11ceaa8284900d1b84039a34d5239a863809369bf20a704c0000000000000000000000000000000007934f34fd50a98225fe6578d3f34ae5e5ef5e104bb9cb398b2ca4f09048ec39cf52e7fdbac48d45212e9e4c1dcc6e120000000000000000000000000000000013ee70f1b52cb8b07ad957a7565b3e3c56306392cf7b5aa29047b23e5b41fb3239ac3611bcb16ba7c7ffc4213e3d9cc800000000000000000000000000000000035840f8ecf56359dc3239945720ad08702b4ea8d0fa9bea3bfb234431df4618e960a1eea87da72ba4d9443f14bb87a3 +000000000000000000000000000000000d04bb9b75bc8078ccfa85e27d32e137ff5f05f9241b19ea835bba2fffc9255a4a3028c0caf9c32d3d27666e1394fe820000000000000000000000000000000013f59c3d8aaee34230cd7715a32e4a45487b9b16ce68d178f95461229a4d0fbe7d31edc7208a7338eed08e65847f8f29000000000000000000000000000000000d63ca2bafaa54e93ea54846b26f88b4c6749953f9cd00c670914cca279b794c1fb5e2664fce44b8c04f01c68698a8b9000000000000000000000000000000000b5188b4b7ef78d3662baa01b1813b4a0b0f855e11397584a460d56f594f11ff2e5d708a23a8e64d0ab337c7076872527740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000019bae57568c879cd743f7def43b6b994f29782c6a0c74734f35b97042a916da00daaea34f321481e6cc4749e23297c1c000000000000000000000000000000001853fd11d4688b027146a07edea647502e80750de4e5e2d105faad3f71ccc90badcc750f76f1b02db3bc0a1a635b2bbb000000000000000000000000000000000b1e45b90e6a7032179236f13f01ab664c32ee5728414ac0d6b9d79510e8c5bd0f5b62e6c59c1a3c88998bf45636cbab000000000000000000000000000000000ed16c2f88b5b8d29d7e01633e2876322caeb740251b034e5e898919f836ae73f0296c62253a0329ee8f71fdb5cac3a1d226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000001455764f99e5eb0e0371e89f88bfee1c43224b9b5202746bd151f72336285556acc5ff36bd8ff87378249e82214cc5e500000000000000000000000000000000007fcee74e5335d96714e4d1a7c6f5c211b1a460efa283e0d0578c6c1f56dbd252198eebf0625362973c40d95fd890d3000000000000000000000000000000000ede26cf87e604507230ad996788e85799cc07245cf7191a6c3cecf0bfd5747b3a277cfbe41252808df6da19f005de9a000000000000000000000000000000001855991a4dd78dfc6088e6a43a64b56c8d86a0278b899bc8a1979a40a287979dee567217b006ca71374156a96b79c176c133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000cd032a7dfed029af020bfa249e6adccaaf5bcd2ccf33736281c4fce9c6e2b2e87fa828cc20301269d8e0579ffb866a1000000000000000000000000000000000765c4d6c4062cfbf7e24f9772dcd812f7e707f2b0ccf9043faf10018326834934df121924abb74d736b0da47554794a000000000000000000000000000000001540fa51e4580ff73e58def90a6f19557dec3c8306e2317ba0c25ece3eb4f8c39beb57741b3c4b9b8554fd2597743ce6000000000000000000000000000000000d875c822d0ce50dd638254cd4aad5dea1443813689a940d72cfa5db9309b171299ca3d69b137dfd37f0b7538a0852750fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f0000000000000000000000000000000011c230689175cc672c25f3c56ef4eaf2bc5766ce424f6c596b40ab24fdbfa56a955205419c149058dffa4d86a48ad35d00000000000000000000000000000000078d493ce3a8038134541ae5f2a82b5e0590218a499dfd78c7a9c06b92307003fb62d6414d6c04b22f2877c3de0b65ca0000000000000000000000000000000001d53c22a622c5d91df934783f8c0cb7e370043ecaf99a0554987e6c5120a0e5f4ede023a9ad988d30d945a2132ba5770000000000000000000000000000000015b1f36a00fee95e13443c9f6e67935a840cedc7c3fb7833ece8e180991909922f59d4f4ecbbf23f16bf5ee7f0b5851b70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d0000000000000000000000000000000014adff1607236910597a951ae169a7f56d6a3b4e0f44ac63a247716bbbf61feff7865d075f79e4108cda6c0731fdcfef000000000000000000000000000000000d740b13885c268da876898b77914bf4a002beef5bd2a3edefbf366e45ebcdf593ec6d9ab21e983fcae9a0832986182d0000000000000000000000000000000002a0827e812e983898351d9f03f660317d41669b0fa378e5c7667b73df299ddc4a32a529ca887a53245d7e1f946623b3000000000000000000000000000000000bf09a2de1a8ccf24a8a65dda72adcb96535ea7235de87f05d27341738b0b4ab16afbc5b37c97e255118dea9bf180ec2ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d7210000000000000000000000000000000015fbbf7e8c26e2f41be32daee2c81390b9bc4413aabb053e3a88bc6117377bc16011e81ed167370b72f84f0e77c2b8680000000000000000000000000000000013d48a27d06ff00048b19879493a5f8ca52b7154be2fcb468b9de9edd1395750434b0e95ae6dd941e84fd6d8918455bc0000000000000000000000000000000012fd2bc91286dd46d68d87a3f8793db997ee684dec6b2de1c4202e5e7eb0e4a8a21222e3dcf80e1ae4a3a92474107d330000000000000000000000000000000004d8b71978c9025dabb3d1b1b3c7f4f13f166514b8b356fd064842269a36c6f1c07f150c03510af7d0913103afda4a68fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000007d679ac21bd4634b415ef8e0e3670a8a1d673f6a4f7f3786b92d55458af980b035e4dab165a3b773ff3469fdd9d5135000000000000000000000000000000000fdb82db6e1096e73322050f828ba41b3012496a4fc4cb481f11fee338243aae20b205ee06887e28f6ba6dad00445f9d0000000000000000000000000000000017e6894b48f60b3d9b4184d58ab9554851e285a1d445b4d97cb1a7ed5a984ade8b0f62ab11ca75fdb280cc0e526108ca000000000000000000000000000000000c03b61690cdd9a4c6c83d03749db72c8946c21a944fb292866cf3a2dd1bf3dcd95743227709740ce8124319d0a540555af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d7000000000000000000000000000000000289f850c4834153f36bfc4855f89e9437a172c35a856117f8b841e5ad4ef973d3aa33fa73d8dbba4b9b2101708006bd000000000000000000000000000000000700025f22c0460613c05f8941f8a79a4319325c37c2b8f099cd910df5c0c27121a9de0e40adc7ba0fda61ea637b47d600000000000000000000000000000000069e17e00d4d726e8eaca8235c88967a7c093c70e5a46b1863ad097acbe233554048838a0a486a72cbed7001c83a27db00000000000000000000000000000000016ce4afb84c1a9e0216f23bcd2dda0bbada6a4acca78e1e0d765a5290f6f4929f6d0eeaf1306fed3c9766ca7c7268acc7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f10000000000000000000000000000000018ca46a89dadcd3b54f60fdf9a7b97c95b9e0668ed9329bbe4121e588a1ba773c9d086dc35b699d65487f428c00ad8c30000000000000000000000000000000003ada6835a93310d0ada01bd7fd6778bd07e718d1ce05aee2b4990bf32322fa94ca898a531ec6e3b8cd7ae3bdc77e0b70000000000000000000000000000000004a8abd2b9f7449213e63ecdb435e5e13fe2aaa31a2c38673a6adb5e96f4dd383dacab391787f6c17579c78a1cefa5450000000000000000000000000000000002a8768d98ccda80149a767e9b5a3b0bbbc0ab4b5f696522c8f1c664f1d27f2f0a6690531672ba2070355c0e77095dc02d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a00000000000000000000000000000000048fb58924bd5952d3bd7b1cd57a1dae6c1034df3a420c1151737f88760e4b0e78fa3f891a0dc32fcb50f89e67b0f08300000000000000000000000000000000073e9723c80eae7685db774d3e2bced53a52f24504fc3aff98e2becf8d59c6e83373ed024ec1ca50101d2d613abd286e0000000000000000000000000000000003b64c8e9a1341bc6a444a871843b3add7dbf04bd1810e1d6da7d31c7c2b7a264c362ac9a366dc8d93bcd9392c6056f000000000000000000000000000000000064462d424e54f50e9849a2bba1b0caae966a8618fda0f8965b1a841dd2173872a44a18ace1e2aecc8e3546a9558d7013549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab71000000000000000000000000000000000c40c8da9281a8b43478c28b2fe59a3cbad0a818e2077d40cfe44624dc2e46f72d4489cccf63eb8460d02f895e78edf5000000000000000000000000000000000735d768f6ac999a47c88bc2f3375f01052259dc69011480e468d8963ea8eda74726c4ef32c8feba52878eaf5c0147730000000000000000000000000000000010adb3ad214b17b963586a10701934727edf05fcbdc94d98255632647d73536decd0c91363840e1b55f29f7d32f650410000000000000000000000000000000019349045e6fd25960c03336888679cb53409027f35a1f211b40d24ebf724866c085a978ffa3a91d989da1a7902bca018c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000a5d2dcc05e218b0633e0a965b6d69a3c6c1c7837e1fff7ff75cc9ee93a112f8e34cbc95bd9dd8fe6ed22f2e9221aa110000000000000000000000000000000017d2e5d2c0578b1ec26b57c3305b209c979bba6925756892f031a7462ec44e8a4a2527e6aa2fc13bae91dcacb8c7a30f000000000000000000000000000000000d437edb45ace50700db548db68b9e8376b3039fa00cb98dd00cd197c14d0f92c8a3945127c43b10b34bef7894fa43410000000000000000000000000000000010d5a2e442a2eb35aa85fdaecf094c1e1f307dc9bcc540693d7206cc4e0d050ab900f17fbdd0754b59bd2aae705c60149ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000016c36464b426c3066aead1aaaf65ca637e93279e8ccc9d838b9b3ff1aa7b896f36de506efc2b0864763cb6ecca4926f30000000000000000000000000000000006d88d5764fc854ed7d7cf1c0e210496ce347bd887da2a149a09679469e98c453d85115afdd2fc4987b64a88c4a6f0a200000000000000000000000000000000053edcc0ca4c205423ee6a7031939379e552bd2d2657f8f25370c9f0ea0a947e77f18b5f218f98d12d720667844f3795000000000000000000000000000000001292909190854cee4499faa602af99dc49d1354a71278b439e983bd89e6c504fa5fcaaafb6ea26dbeba9850bcdfc1f69bd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b000000000000000000000000000000000997e79a7549ada9ee0233b3bf9289df3ff797595f4b5eb2e7dda6977ca981c1c4a2b91b924812b95418f1b1d9d0cb830000000000000000000000000000000000256b830e80f238e8494387429d727a91cf5d323ea87f7dc143058c05e11858796adcdc677429d1db4dc2415cf23808000000000000000000000000000000000cab529c6b86beacc57c874f07108d1df7d98fbd59fce44c48afe9eb2dff823f4869b620bbafc121b4ead2cf244974de0000000000000000000000000000000002774906c1a0acd87de224a9450617db37f8f36a0a192f5daa2774eff0b73aa79b4804342999df761f8572974c697c6010bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000012abd02540073017011e186586023adfca36fae454350b2015a796b7991eece65b63964fcdf581b4b51dbd7ddd506ec3000000000000000000000000000000000ccd3f2d9280908d4b30e924e4a862810a92e1a880cb56e842a94a2a5120956e8713f548ca279d66d06ab23e4976e54e0000000000000000000000000000000000c052ed00fde2cab515694d8c004de910e62d07c462345ffcfbd3904a0171b970bc58d99c5833059315283004f3390e00000000000000000000000000000000008fc4860366074ec0c7aed2c6ffae7c93ae0a81067edd8911b4c53393ebc0f23243823aa7aa2b2e987cb510f6e0a55a65c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d,0000000000000000000000000000000006ced307065868b6d082bd205bfbaea3b0a8cfdccf831bf154563b5a942154622b0d7689819b337479480d19aedd85e4000000000000000000000000000000000c0f04fbb26cf85c2c22763f3e78fe255d8d1f45ea47232ab58f5b785ad9f2458b0b28f3cdc25c4dfcb47d59957ae10700000000000000000000000000000000120e38740eebbc3eeea9beea483e70d6a9c30a5abd61b86e5f94bf65ffb40fb92c8d246edbeca425ace175f79c9c8afd000000000000000000000000000000000d5a503a26e50f9be34c2e64e4a80402ca6e17f09db1b334a9c1f6318f3e7e63b3847a7ca38ae6aa7c96ff94bf5de842 +00000000000000000000000000000000036480931a5a585ea54b6dbb01759eb1d86804e3f03326188c71f859613722e662c453096431171a49eecf8653f14d470000000000000000000000000000000015fcd6a30b9d59a90d8595ca1758eed7d6810d2916638dc2cb637aa09b16b5ba4920df7d21fc0b923453a6c7d32f056b0000000000000000000000000000000019aa4d8e98808c2fc1273d383e836876b087ad5a7d01743bded01314bc62ced94052d75d312a18839c1b33faa9e2e5160000000000000000000000000000000015747ce0f1171c0d0ff1fee9dbb2e5673b9db0b0c3618cc8bda474f378db58ea42184f907593f3d6fc2fa215cabb7b2308e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b79000000000000000000000000000000000222640c1d64948daac3ff93e86ecc96bcf9c93559266529a37ef1372a81952431673d69f1220e07b8aa0a4f3164c83b000000000000000000000000000000000db593156078821cd0ce0270e8a444d0d204dce0583774496620bd4752839f3451e505aeb3db568048739c7e71d279b40000000000000000000000000000000019932ad2c7e857c2dd51f7846534050b9243e388260cd47a91444fa050a9154eca88ab4d29a37def16d4a11d35683f2f0000000000000000000000000000000004d15ec653a72256ac6b616e9870b0acc7d46286893c0eec523dc27bbcf5fe596204cbf83ce71c2690af67b3616794225e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d00000000000000000000000000000000180622bfa9a1c452f343ed21a3e9c6fdf76589cebfb9a3f0a53782a3e7c9d066294e10699c386b5d0525003289f0ec580000000000000000000000000000000006615ff63c856302dba6d4e25d1070fe873e0c4950ee5ba8bbbd4b94ceeb181f1ee450acfd22f21010b88f0b88375777000000000000000000000000000000000cfd3940b5eeefa92d775792affa34371d13f3098ede3007e06510344ac8483debadd5a2baebafb5ddcb45a9449768b200000000000000000000000000000000145be0107a1e3acecc89a116668f9887579ed7a72abed3f4236930edd3f18974465c99ada86c4980c88768824216170f1362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000820e74e6d0333b6b36590ebae78960d019065f1681ce68a2a01a2522496c840c668575a57f9fd0f50b87f928a41b0de000000000000000000000000000000000dee60d90e96019cf2bb552d016419e92dd358ff97039a61838b0a89ccbbd537f2b435cd11f7b6e75a4ec6675964e7fd0000000000000000000000000000000002ca767de9fbf8af7c73d41a07e1c0e38e3fc971472e11928b65393a27354b2d732012dc57f498f94c0b933565a7493200000000000000000000000000000000134fe97b24e153f0e9a27d3fe7b89999c6a19e353325e0746ead013198b8e00ca6472fcbd2a112aecb9ddf671aaedd9174d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de00000000000000000000000000000000031a9c29323196ef31030ba73827d228e56fd5209eeef0803a189e0c0e5b186ca1f342483eeac99e1e1b12cf490856460000000000000000000000000000000010deea45a01370602bf57a1f81413e8d3b337d7a1a33f9525e4ff7003454d1da2cfb1a9b42c4a654320f91fe7d04b6200000000000000000000000000000000002bafb7b7452a173a3971c2ba1768061a043307d2c32767056f18c1bf8b066176937876a87055e54675876bc1b2d2fc3000000000000000000000000000000000b5c77dba3b4136a7efaa8c2e28f39e88afbf26a7313b52ad6e390da4d948209d96e39aa08eb52200dfb890d7e88b46a389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000018bd1ea5ee8e39c43d442e9c6fd22706e582cd80051f18334c4db2ea91ab019f54bc0074c8f0e52e50367197a797e7520000000000000000000000000000000005c0bcd1b047fdbdff25b138248bf4da4c013beff7dd3030c348d6b2b8724a147cbc44d570db5c4b273c94d0b99bc2290000000000000000000000000000000018e033935c20be5940863f7e9e39fcbdc29ba031e58c10beea90cc48e9da9988fdbf108bcbd87948058f386928f81fa800000000000000000000000000000000107d179204db7b288315e8aed7b92ebfe53b7ad2366d5d7944b3df68d9d9faad023e477213f85214047645bc05fd4cde5f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000000a7b364fbd3bac7e2f2e7ee501db2d248bd73a76c2a12a3e51718b56ca9a8ded14b83b8cf0b5bd46f0c26896a65fdb15000000000000000000000000000000000eafea7128fe20ddf740a6396bf18ff5f2652a0317ea9b6e934927c3ee95b59c7dcd51f7c895b3989d40ae5f78ca508f000000000000000000000000000000000bdce57be904236a8df532c2c0072165b5cbd4103e9061fcfc0a45a67e4b25d11b9f816f63fc0eac4d6d3e10d2764c4a0000000000000000000000000000000012419f94ddbd8275054f8f89fdc27a74afca2eef314393236fca65705354e5cc0a470818999c96b5087997813823e9be0dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b60000000000000000000000000000000011e4010d0cd7855a92cd5d4954ad735363c0c2ab00053db5e078f34e772969d8c492892329cb95ea8893b4b7ff7aaa5e0000000000000000000000000000000013badc54d90a19b84d76b30fef8e3ad2cb268204fdaa50ae951b63e48aec9cc6d585751dd48e4a8d4659b835f38f8da8000000000000000000000000000000000460728f686b9b15cc19ef135af71312e174860284c3f0e7a84cf85a5c934e2bb6cadee8e482d88afe788a796605f79d0000000000000000000000000000000019a50c06ba307d83452a30fbd862270652cf5c7a09b150fcea858a8102ce3b1e9ec13b6abfb323d63d2c4edf209c7cafc579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b5360000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d69f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f10000000000000000000000000000000011982264c8c078518cd0adb05034761224e9063654904e06fb5e5a6eeb1f45e4ff3da661f1232693b79336215dcc0cc40000000000000000000000000000000010c96c872160d2de03a16e85f2828d0cf2dd16a3389effacce46b5b5eecfea1042a77de653da5a1c0380a84c435723fd000000000000000000000000000000000a4ad2d9956bd407c555b26c192c6bf59bf89e40d9c6f9c90780bba313a39db71a73e7633397d47a3f58f61c81edee77000000000000000000000000000000000a7f912530d27a7bf74e01d8e48890cc66f72d14950554991ed1edfc504062ff6bd3cb6941bb398df9fde3cefd33fc0676618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000015bc12aa9ecf417fa5bace8d9e5dc4a418555eeddde1da8b624bf7d6e1873ec4a257d5f6dfc058a8d9b02528e699abb70000000000000000000000000000000015b41567f8c780f83342449f27094bc20a839602ae482de14b92e40017e7acac8857db48a2d27f1f1a625883b6e5255e000000000000000000000000000000000cbe79ac0718555fd8fdc38b68eec8be83b32499d2654be44888e45a2d610b0e81ae12fd56550524ad85b5a632db32ce00000000000000000000000000000000069f46b5baf4357d8010869685b3828c0dbf6e2338598c9b42dfecf0b22d803f95fca716115f74c77778d414cbcbd881fbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a07876177880000000000000000000000000000000017ada4038189c544902167be958e43ee133730e5cd329e572dae2d853b694f5ff8032bd9ab41cddd11c51e8284970f810000000000000000000000000000000013eef75e6d28deec945ddff33128c199fa52565288d63677c824b8d56a6c29eb98d34c5834e84865be35d40c1c59a40c000000000000000000000000000000000e2fb4f9c7ba6bdac1d4ff5055be609abef7fecd7923a753a704da537c0ff41951552420bd78d14cf972dc84fa3f5dd9000000000000000000000000000000000805376b814b8a59435310d49a43081dd7ea36dc7dcb40d38068ae9085b3ea9a3b2249234234cacc76724d8ef84a2eaca0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000002288860f2d671c84c5239313b7f6b82e31c3976e6d310e15d3bfe1c566e2ab5d86ae6ed0df02530f9f7893ba419f1870000000000000000000000000000000017365bc096e260f8dd7b189fabe10eb66923783b41fff70a149251576b3b465c13230dd0af13cde562751dacd8298335000000000000000000000000000000000fa8eb9c818df27181b45a74b333ab481dc7212e417c4e12634816f9e177064f9e1101deff26156d26bc6574db9617080000000000000000000000000000000009379598bf02222e1ec37a721b9ea31a3adc33524c6a41bc58da06caa3da3bd730659f0a80f793a0fcb9c07b43ca929c2c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b00000000000000000000000000000000136870e08ff5fabf36410629ce5c23470eafbe73a7dceb633df5c1492e39445b86ce15c22bf4c421cfd0adc6518e78c30000000000000000000000000000000010aefa3cdf1225da09b796430d096807a83eb2fd5a58db3a4bfc5e500dcfcd472fea3077f0c059620f4ff708f37c95a90000000000000000000000000000000019ee2c62ff860338af623c535979ed31c42c0d0b2f82cd56c153e80e6d92bec9ce39bc8e8f285d1efd1c1e969521dbb50000000000000000000000000000000008ed69eb0a16c8a35d507bc3a50bfc97e18143fef611263715aacf5400cb1aa285b6d2ebf2ec219d2fec477360875a03141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c5920000000000000000000000000000000014abc4eec64f2611197d0c1322c3248eadb725049379e64682f2b3d7f83f7bcea11358d88f52711b3020924b6ddd84790000000000000000000000000000000009fd78c5d1d2043d83be30a88f046f5b633c6dbb11bab25fa3037bd250b6b9d9394327aae25d1939f777fea9f3df46960000000000000000000000000000000010f413640aaa16a95afba98660f9e1b03a8f3e0a7a3d7f2b971f71b5e3d09016ac2b410f97d20471f48621d5a363e9e6000000000000000000000000000000000154b5df93298a5a14a6157819e38db33ae7f2d11dfd13f7f2a92b2fd9b053fbd25f10a8c45db3026f6f583bd56eee0f1d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c000000000000000000000000000000000f1893df99adeff5e4042c4c5e8557e53f7c34efcb2a7953d5347f81d2f4a75ca0273a3845f54e795ac1c1f8ae7240dc0000000000000000000000000000000004856b05d58898be6aba07fcffe487dd895144c7ac8fa8bb1a37c61e73bcd062ff541d510e24c5bf005c8351d3ddf61c00000000000000000000000000000000178b22c2c698dbc4929b119474a741ef44d6275fff5ba058d9debe9475e71398e464aa14a6712c5deeb5010d1c7758ba0000000000000000000000000000000005ad09389c35c45f349e6dcaf1cdb3b63648b3df427ea0c2a371f45634635f9253957ba6987df4aca6cba4cd472308a31b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4,000000000000000000000000000000001090d83d501373cf07c75effb1c85852019b39eb0d77226823aa3c1054d4e408e82fbf0f4420a30144c611fbb856748c00000000000000000000000000000000120a1e3795f6d5c4ed5b886256c611bdd209677f8324b7091cdd7cab11788b1c0f780e8b4c38b84d7c2ea528123d4783000000000000000000000000000000000d250df34d906ed421eec2a78c2ff4ed4eedb717358d7ca879d58ff5b4d2d72521082dba6ac5d10859125e32c2c8b490000000000000000000000000000000000476adaed9d80cb1545be505496222dba1f0ea85d38d5bece0663461e0e9d47abbefe95303c926db008d08b8aa162e27 +000000000000000000000000000000000e1a9066289392b0b0b8f0986366c2975463c9cbe7a74f2eafcb3b8b6d4ee3226ea5886aaae374341bc76b53b165e22a000000000000000000000000000000001557cd01b61b5f2361f6b558a87c67f2778e11c21734b7ed25f72a88cc62cbed396d583de4c2190ae6bbfd096c33bf73000000000000000000000000000000000eab68305118d7e7076043719ac1e13ecda4497df2cf392d6aae4b7753f114d30aae3e8535742947636901feac4b620a0000000000000000000000000000000002cfe5014446556b82d60adf874cef25e58eabd035deb4717c93bf0361f37a4a67aab70b95627326bd97f111efeed57f58fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000c8fa30f6055357f6b697f2115203428b8005ad03286d2b3c805bf3d4dbb461c30e6ee8b0973ef41f884b91e857c53500000000000000000000000000000000005e1c785feb4c4fb7e960233d431d51a4fe471f10321251d018a950374d2a686d52ee8cdd855a29e770bdc1bc565f471000000000000000000000000000000001158d31faab483832d39f5431a5d8aeb952d6a63b82ec019f235b5b2e5580df8cd91b46cd53d4a90b9db354b38c5a1710000000000000000000000000000000004a389b09be6fb7ffd14d7f3359b17991e93d92a1c0b9a89faceaf71f5ce77a1875aaeb7a0ec3b2dfb363c47dfc9875273b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad0000000000000000000000000000000010587118c5f90b545ee707466ea2c5f378e6795c260235cdf9876aed8bd753aac592ee05e23882ee77f4a13bff97f5940000000000000000000000000000000000a0344aed244b90c4fb9ac337edb01429e09f951062b06025a5212300f5471a95f28e09bbc715417a6d98423b518c3a00000000000000000000000000000000128457cf374e5b8864b8241f476da093f48553d609a5f30c0f0f235ecf7127231237b6c8802f2904a8304c7c237842620000000000000000000000000000000004d55ff04eb09b33ebfe90f2a0966a1b59cc224215c0359a4ff0c09e60f9fe7ad8342868184d8cfcaa1d8c28328864241ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000019e6992c3da47715bf379a668a15668508e7ad27bac647490be8e82759b9b79c996735aa1bfdc3cef217750e4ed36fce000000000000000000000000000000000828f782c5bd4f2de3570a4930db2c020f75f93adc98aa0e48449d29c7a3b0d5c349963d956bab7f985ba6ffe59c90ec00000000000000000000000000000000062c7a730d286e895c57b75907713ebf1d20650b5e621f270f1d22a2ca480d022346def4102a62eebe867210e4b6122e000000000000000000000000000000000d6c29462ad449ee6cd122e3dc00d56dd5caf17a2510e5305aecfe85626cf73adb401ec2192eb693158650893fa67412a691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000098de9ab41c289a05ba5a774eafe27d91aa8272fe9f81fadefba9a0cc0e31de20f808ff454a8647c44f5aa632742af9e000000000000000000000000000000000c96019bd5cdd62df1642656f0832ac8ff6aab86f671e18c1c7023dc16b8ff54a8e3e446b19682a23b73ccb90da2fdf0000000000000000000000000000000000178e3b4366b2517d4c19fb40551be6979d46319d7040682241b046f10ab88d269dfc097ae02952d46e69cb1cf159da50000000000000000000000000000000008341bfe1e2fb999f0c3f4e79523c720edd332401f9dfdb8dddba8d1342c2c1fb20ae2fd9dda92c7bde5a0c95ad971f80d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000001004d60af8c21f7c62fcba1c5c41b94fc77f64b89abcd23a218f0da8f47d2ae6879ddcde52f3e6feeae2dc7b2720577d000000000000000000000000000000000b8e8a7da87aa62ca852e2984b0f12b85052fdd03883f01f4496df0835d1cafa48818b5ff1e3cb0e9ecd66054540a0d40000000000000000000000000000000009c16854580ad8191e3e80a0afa8da759a8b2bfa7e0d556418b5c96d97e88a12fb75a91cd68c2f4336c3ed7ac99199fe00000000000000000000000000000000195ce9c562c460c7e715908991ea8b017b81561b45133427f63cdfbe8f65202bdc8e8958ab0977b3a244cfa32fb35f37c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000107292f77666064b7d80d73ea8f3b623170ef79ccc7c228b8366675a422a0cb8491586a2e4ab1a067c31396cd670a8900000000000000000000000000000000126f8136dd61d61b2a9c0f4af3ed44a3cec3ccdedc74821f341d200601a7bf0a17079c824de6cfe28467e843d0c74d2a000000000000000000000000000000000bcec8afcc7ee56b36d6d08b51f61454c8fb15ec5baee1117ed55af8fc85f68674250334f79b0fce632e75623dd173210000000000000000000000000000000016624d64660b63b70ed197f6a675911b02b0bc6f880348faa6ce4727af74127c509ce8535d8dc8db5ae2d71aa497e0756fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec5000000000000000000000000000000000f57747c20e1b3923c7e1d8bd7d877736cccc0e0829837a086d62d48cb54f323d90b57ca3339fe4b256df529bff11363000000000000000000000000000000001940327a1b319dc4212e7a553d3f49904660722c89636f6a38604d96771fa0fc71f57674b7aa710db4275822c2b89903000000000000000000000000000000001956b81bcf961d16e50c053ca07ae67cb8597138f34a9dad4d82e0e8d23a7e08b751682d588f229311bc63f9598ef448000000000000000000000000000000000208981064443e8c72987945e399b45b74e529a0bb75e99b7d6744728e5c182a6b0a10e449147bcb0b0cbe70edcdd845bffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf0000000000000000000000000000000018c35ca3a63053fec853e8fda5920b560f1be28431f2f4b08789c7a202336c8905a5ffffbf69ae4427f267b1e13288d60000000000000000000000000000000019de96be76bd93886cc486c2671b5b0d731b568638b1b830a52dd4c481b9a1fbe2b3cef14b46e25f1188ddb3c158da6e000000000000000000000000000000001813ab16a11c79eb3d3d47ae7d9a7c05401ee91eb1183266d23077ec4c0c8f3ac7188eece06876025dc3fe271d65d4ba0000000000000000000000000000000004d2a416dc874e956fd6d29a3fb96195019f4136561b4c127541ac171b5a6b229746af6d6e535a8017e64ce06709e52e57f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f4000000000000000000000000000000000623b7a8a1c24dcc603f01589e6679c74c4ed3452894e536a4cea69e99047092acc877dd0bb395b0cb693cb1702a64a00000000000000000000000000000000013de9dc75e42f12e905d729a52f25bb1a4125f5edb435734649281bdfd41083716d0797b0a80d842c2503d09cc61162a0000000000000000000000000000000006453c06f56dbaabd4530160bcd5312b8a148dbe19fdf9f1e44b7b047a73ee9ef9d981116d00269942ef73537885eb7a00000000000000000000000000000000075376135ff3acaecc0eeea32f8dc15add57e8f0297d053ffaa0fb0a8fc4418c5b142f96b6b9ce9eee2f949c960aed682d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba84000000000000000000000000000000000120d088fc12210c1f5f6cc3d1091563f9a37d4d0e0d2c305b479f4d7e893c4d5c8170eb164e34e4843a21c9eb193d11d00000000000000000000000000000000159de80db3b1f0ffc5fa8c93e1bd54cf8ae19cbc9018a5dfed86179cdbc976c1c312212080ab221806bbe142d496e7a7000000000000000000000000000000001103abb75a78220218cde4bc4c59ddb5fb647ff808754dda200bdf586ee9c47a09e03762bb726b085928ddcc998af3ee000000000000000000000000000000000bff4bea17eae0f2ff3e7f99bfa91e6ae8aea28f6f3fb6080eb644861defdefc26befbb7874f612edac0cecf70dfb275614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000012dde607a2d4452c6c060054c8adb6307743edea3ccb6ac34c275717f177f0e454d9e33d4391208198cae39d7eb6f6c00000000000000000000000000000000014cb4d8bc98060ee68a8ddbc44b83db5cb6d09f09b0d608357629251c35e44383e97058d0d68fe2df3bc47424a5dda03000000000000000000000000000000000c14fbb6c844fbf896fbd3cb3464a83aa4c6e9a7f0450ad96a07527df6f1eeeaf587f60a990bd6abe7aeaf5eb46f362d0000000000000000000000000000000001d9468774318ea711b79f16303ce86288cee312af296f1c9f607ef5f97c7d1cb48a7218775c8aef00c227ccb586286e7c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef728400000000000000000000000000000000181e7f8d0ec7a4a7858bc96b61484c24dbb9dfeb3746fd3a231a8e442369e3e83516ee6043b1c06e7e2043dc86f6c75e00000000000000000000000000000000184c1d667c0ece59f18fd2eeafc66f1ed530b7d5f4560a6c886429caa13255c63dea01c3e357e3408af58a39420a8b28000000000000000000000000000000000a8475ea694cf607246a1c50064cf90cbe50ad5cf8006934a1fdf1621ba38d20e70860a2b5aecc05acc60943224cadb60000000000000000000000000000000008afa03c2df8e83fb64523c57d0daa7cfbb7af6a4bf2960ebc64515a61a659b2c37ee661050cd538fa00cb34746a371b64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f0000000000000000000000000000000008b33a297c8f86f1e9d7166f9e905283c8e1581e582b879caf48585d0bca3608fe46d8d9f6e7c90855aee9d92283d7a40000000000000000000000000000000016962410d6b4b6f91437617e84bfaaba49de0369b8748d2e2dacb63b421e0d7de4514e7fd3e0dcbcfba8baa4915610d0000000000000000000000000000000000efdab72953b870d0e113efa7c183d99aefc100ce59791aabc72423aff70a5b74c577c06ca94bfd6a7722199b4bc22660000000000000000000000000000000013b18e31700987dfa4344384f9b41e72afe92c39bc961333cad3e7d0a5efd3842a5e849cff5655c4673f720fd0127dca35bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf0000000000000000000000000000000008b86f70c8d8b03b0e9a8975776d7fb0d08f95eded0a0124551d363c2df57124e0e89bd45ddd1cc75c258a4ae2f87916000000000000000000000000000000001120eef9eaff7c308b629deafb060d2c12b20b57562007fa810a2191d99fabe9c7d3c364caec1724665ef556de66b57e0000000000000000000000000000000007698bbef6dcea67a2c643342ab2a0f830c329fb6244d4a98512daa8a3c9d808cd2acc0cebbe3da920053ad73eb7cdc7000000000000000000000000000000001155b6beb28fd88d252c6b407bb9f55d22103257287ce77353bea580c90173b5c3d49080b319ea28817d67c52bead96f7980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b2800000000000000000000000000000000142b28509d72f9e3be9ee916827fc1a8dfc4ef7ae2b72eebad5db605fdb2dfa4492b50cc3e472df1b52baa6e2b0eff5500000000000000000000000000000000134d6821088ce4a8b42383d5a43a32bb0cdc96c85f304a2601292670633d5e231b9dc479d199829a9ba9f39c162318d5000000000000000000000000000000000636da344fcb0fe50ff3e22f8591418f64cfc722b2860b4a5047f973f42e4cefb93c2f8eb8a14b4d150758ecbf3cf712000000000000000000000000000000000e6fd06d5dca702cc9f199f7583add86c82f7b530d4dfb9faec36dbb669cf7c1cd1260c7e4f3026824eeb5b979e9fdaea256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca,000000000000000000000000000000001684f447f8929ec0187811f66e985f0014eba46eaa87de2d4ac2347d10c0550e4044ec7792d9f315c50081dc2097ebdb000000000000000000000000000000000ee0c46efe930bc98f39dee8cc6a792744e84de4fadec035d25ee8ba82e1c53264d0885a1fb05b2b8dc9c6a1846c28320000000000000000000000000000000003a5ef98843099235a2ad9522c9cfce1908bef77b45794e7df9eb38a4854460031829e947a118e8160365fbec3725b85000000000000000000000000000000000dd205e195abef6a4cfa7da66f022a418235e1a1b2fefa6bd3ddf8a3851d8ca8c27652bf87ac644cd189ae55e3cc7808 +00000000000000000000000000000000064698182f90c20ed6f6da7248cea32a291f901876a900d344ce4dc1b07822b480519cb8d891b5ee4f33b4efd90742cb000000000000000000000000000000000e7e9d2e79ec4b07015baf69a283f6a4abc8d7c1699f3356fdad6ea9b1c70e41e73bc14e500634d73749f9900eeb65f5000000000000000000000000000000000002ddbf40619ea5123c100e2d6553213e37883fb34f0f0f2124795dd971892d5c9051cd4aa78b9d20f196301ca9bb4d0000000000000000000000000000000017a07b32fbffdbf7a80f0437eac1ec5fff5a68f3b053482f034064992158b604bc34489dfd41a24ffba806ccb871fff15805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c0000000000000000000000000000000018b4de9c04fde8b708408efb3aa7f24b5f7bcec14e7d06fd5a5b36bab528e5adc0bbb1e378a5ff6fcbc95aea530ffc6a0000000000000000000000000000000010da98267770a47e5ed14ffb3dbcf537dd14ae5eb79522c772a7a2833be214690db0b4e86621de1842d88018fc0f348400000000000000000000000000000000135548e2eec9ae7c3d23618d8286db13a5a628fee04fb6ec9da980f3a46838899cf965c1cc6f562e71d5b5c7428cabc8000000000000000000000000000000001669fcee7804df9b7bef32e2ffeaf285e8501842efe87c9e827fce872dffbf92255d3c3a2fb5c382ab7aec0bba1ae0e5502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000010ed20c069bb300a27571adabd239e70b767af90b91c4d0e93d88278a6da47b7c12fcfaf62ac0a7b9966968cc9f3770b0000000000000000000000000000000017273eddc25cf41f2d7734a3866711e83d4f2823ee6a036942799f837d5ceff10dd6022ea25e3c1e28c7b14ed8f4e7c5000000000000000000000000000000000f201f314f66f6b2c6e1365c0fac7b187d31bc45b5edaef5243b5488e26581dee24de4a5fe493bee44165cc31d8d72ef0000000000000000000000000000000009dfbdd86633edfacad6b78d292141a1e653a1bfd8c48a96b2f6bf8271ed6033c0511628caf2ef258eb64cc8b63d8e5be7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a740000000000000000000000000000000005c4a4a5ffcb4a39c8809821ff275360ff937070cb97a791cc9ec45f429256a6d2d6127248b6ab0b6c71c30c4fe84ff20000000000000000000000000000000019fa60f481c5be953c9c7dc86903a89af0ca2b4205be3a00d793d6de7103852e147ebc7d983c6d6e8cd99e681241ad440000000000000000000000000000000015b3b2eeb0f81ff8a2624e2ff2396bc69feffeef62b1b6a1e73ca4b9e60506c2950fdd23a37cf56387b8794449d3237f0000000000000000000000000000000017021a69ceba3446dad9fcfd8cbe5b89b61372f57d43a8d2e2c8f4534bef6b91408409dfda9438f24526f7e6bf1f4240e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba0000000000000000000000000000000007c8f07f22a3412fb4638cb704751959cda4e42e4612edaf5b1f22c8f9ea314508353445114bab6c07ccbb4b0d0bfa6b00000000000000000000000000000000062d087155c8722d0102c8e5084f95f5f58ed626d48197297d21d2108ee05f70f16d595ef73e8e1207a3c0b013fe16710000000000000000000000000000000003b6652934f3acd4c91c6c521c2476bcd2594a939ff2e7ebcbb0f451fcf0a656a518dbd4f36f165f9b2f58054e9f778f000000000000000000000000000000000bbf21158227e0ad5461de9ad8bd580f9e65327dd4e23f1ad55618f6b0aec45aa6076fa88557953ad15d385a074bc7d96e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc50000000000000000000000000000000019e336d4d342f110eeeba9773b8e351f26bb56361c77fbf12fd9fc218fd075ae38b95f4a8a5ef830fc2cd92558b1711e000000000000000000000000000000000a112725046ca3b6cc43207e6b36f38d96ff98dfe3444d67ee3f4b0208f3b8543768dc9989f936637d7819e7dc5740fd000000000000000000000000000000000527682076572d8cca15e47a2faf62b129baad29afed22d32ea47983a8d0b138653c1353bfc6fbf9fdbec2efe36700f90000000000000000000000000000000007e3c5aff373b5154ae66f978fcd66d09cbebc7e0c96b4a4cf23c4fa5f2fa655410c7f1ce597a3f5f155017720f7c50f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed000000000000000000000000000000001865c265ed4606ed16056c0b28f953119751d7272bb33b9865eed312ba23b32d01733ad5446cea5873c2bbe37fdfce7e0000000000000000000000000000000007018aca1e7ac211921cab1cc6bb18874d2f39f00d916b8f3d46a088a378f3c9b49ab8a296d0aa21608f11b144a0c687000000000000000000000000000000000210561c0bbe5a9f4b2237e5bdf88bcd73326d395277deb2a883526978df90792993e6ee520c9d5ec0a6f7ef5c6b3542000000000000000000000000000000000cdd344124b7b5da556f64ac5d651a6f9b74427fd712007310d720f3236724e2284aab812d739a87f3a1bfe8737dcee7586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53efaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e40000000000000000000000000000000002dd11f4dacf3d9c46579182df1c1c45a364a8dc1eb7aa7d54d0141306f1c23bed85235783a22b8e6dc4adc35f9193ab0000000000000000000000000000000010d1c642fce533039e98712bdfcda86eaa62d2d69b861ec4fd835488732fcea414cfb6f3f8414152f9d5398c73a74fd2000000000000000000000000000000000c6759b75b1e3fe86c00fa124d09c5b7438ad61fd1bb71695743ed7793f39b7a0fc99b055201ac1e3aa07ccec61b24a80000000000000000000000000000000017580c9341789484fb31386eccc9c344539a09f1c4421dd124b1a0ce61f2d0528942f7fe8df67c6b2bbf782996def47b39d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca8000000000000000000000000000000000d9c48a111c8c74bce8cd78d127999531e46a411b2f0be3507226766bc8abd088638a237674ac62e0fb7dd4a86d09b79000000000000000000000000000000000073675bb81e2bfe6adb5cd929e0b7280f5d60b3dee7f797d65ffbefc2c2944a9c7207648bb096f13292ff4440c3f03f00000000000000000000000000000000024d2e0d5ba1a804520c72331fa23a2a326d461177fa527473240dda130f4ef893870e893e1dbf7c5dbb0178dcd29b3b0000000000000000000000000000000002a4c9487485ec33f8fb347d246ab0d41b883bec30d2a5e88cccafa676569f25ffd8341cdf6c09f68afae442a574f3334c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a000000000000000000000000000000000c1965a745e42853b4d54739b2dc507d68d80b330360a4020e4412ba5422daaae313fb9597c98575c66ccf351e62a527000000000000000000000000000000000844439e6f08a411e61d37b5b2b07921049432e1833e839b00d6cc11227dfc8770ad9ca06037043668fe7ce3bf3ce84200000000000000000000000000000000152ad6fabde2e0310c978404a5244209a9363cab1f3ac9f71339cdad6d40c84f8e5a8a196283b581d0209ce90e1e3c6c0000000000000000000000000000000010eb6af62c7dba122b0e24e8326dc906370bcb4ba791c47630f05f657a228c20e010c065b93537ec84fa14a756b199789af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000001febb2cf2d664e4a277cbf08fc1fbacd05db415a12329f7be551ed56d67f0b5dcc917d1b02951657bff3a26bd8c178d000000000000000000000000000000000018af160555292b2f7ce27112c1d60038b564f5427d62604387de97dcf48e4473107f91936b5e8008065a1537f7ca340000000000000000000000000000000016bbad2a7f5451098294a7cab2fe10d206741a99b128dde5eade581d02ca849bab3662fc3400fbe055dd93a418aecf0b000000000000000000000000000000000b1e9586cc1b357da6e58621ce09288e62a79517144f6c6b867359251baad6d40217578d49c1501f23206b125282bdf4c72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b000000000000000000000000000000000b88892250c848e7bc7bb7e42cfe1048a1f61dc546929211846f49501ad8c7c8817f5b5b99ed092d5a2236d59d9c8eaf0000000000000000000000000000000011680c6549f6b7d9d187a6409d40cc26554df654083f1e8a47dde826149d68da756adfb1b65bbd219f79a10d8454e881000000000000000000000000000000000f9596121dad98bf7acb3fd65fe7e0bdc8924e2390341c11d9cc9cbb0517f988ff79a5e1d60bd89449b5f042f0d0b0c30000000000000000000000000000000008982832ef53bafc23ea817be378532b95b5872217093e7c7c2f4512d03a9c9a6dbb7950563a520781c7ae213fc82897b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000ea5bc2f8bc2b4088d1fed7090ba389577b11a3ee0775cb3f0657ab5b07a6709d3a18fa5fc33554dea235c60baae4bb100000000000000000000000000000000196b6259b06a4c91a0bb0adecea134c8609cf983c2c87158a69c9de3b6768510fc56543a84d1266dda78d90c3b0516ac000000000000000000000000000000000d0222d8ef278cd0d85dc8765fa7c4256394a5ef61f91301af6c7422b4cb17889224c75ccecd2df3ddc9bac98b493863000000000000000000000000000000000548809ce26cd498816ef1222d062b1ebb7313a07e99e3aad1431f984e9b8ecfd43357ea57da7e0c6c011c5d5400f7ba986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000000b95455351fbce6f73de0345a195f91bf96abee361908cea6c4dcde72048a13a9a23991a75b9c988ba0afd9491d15696000000000000000000000000000000000305f29b05fed06ffab484cb065d4852eb323fda8c9b7c0a78843bd7143effa95cbe5e50c1a0c3a9675bb5381709b6550000000000000000000000000000000016ebcb25f1b8e8d7a8f7131455ed2be084bdcce40034e7ef24a47fc29e447f912c20c7c9910e025aab975cd2c8cf1a96000000000000000000000000000000000d84a5de7a5fd8592f6cc2bc7c3d93c06e26185787856c922d95eeee345ddfb7cbbb60b6d992c5ea4dfb33101f2ef1dc979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000001377d654f80e933c4598aba1f637d1e37d66a96680c3a89a762f412e187817ec08f0ae897b08206a73f1a423b742261900000000000000000000000000000000014b71954b9bc22ac22cb2d7d7f373c3238c923205b223cce6c219175df2bb6d7258ae46d6cdb019311bd386275499fb000000000000000000000000000000000a08ef83b67bc972a67b9174d0e5b1536af882d505d03464c9a97f68061aa319d612de9db84e1e7b12fc3015fc2973b20000000000000000000000000000000005f716d0ffc30005e4a744092704a9e29f58fb06bf7d8d6fdbb95a4c0eeb5c39452cf662721ea3e0bcc67f25931a109425ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990,000000000000000000000000000000000c53f0ca8901f4751be4a478088b30dce70b9ecc382455049df9ce108eb0a8d2696bb325fe9ebfd7d967ab5b9b2c2bd800000000000000000000000000000000033460babd2984a5d8b7002409349972f518364e92648927e223d7a3b648e952482c06cc713bdc29ab83f2646e9398510000000000000000000000000000000007cb9dfe603dc070151cc477ec5bb5a2a949062e8442399597c5eff8f1decff538cd0aef1384256dec73746e63a6c66c0000000000000000000000000000000016b56ee9b21c533b9c464178d14ba5c92a90e6a54c3ed319f487c2082b1ce1d0ff81131a5fb3dd7d13e0fc1d9ad9e4a1 +00000000000000000000000000000000104e0b91821c59290be48b97936458af89078b176b5585ca9a79070c7050309b01df4b0bcd84f137f58304d90599212f0000000000000000000000000000000013b00ece925fd17a8effc43e21d982553ab2764b13defaae5e5419cb9a23ca7436cfc44088c2aded63785e4f07b6e186000000000000000000000000000000000267cdd42febf0706675b60af8c0953582ced84dd5ae870815654cffa46eb14b747fb8fbb3b014e59c929da49c6908050000000000000000000000000000000011c5384d7c3e0f4fd66ba4b4c2ab60f6f78f9930e1fed233263dad25294814d9e2aaba6388ee9f924e2a323693b6e43bbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee9670000000000000000000000000000000018158ad70994584e6f2443b8b96c1e4772a00fa0bf74865c76000eae470eb02cff627579126cc465046d4e088782557b000000000000000000000000000000000d72979d455733756a0849baa8afd79e18960f3f6dc9676c33d1663961617831f3266015cb998fff28b78300c87c2a73000000000000000000000000000000000056192c20cbcbde6099256a8f40c78a32d3fd212fe9c511951c7523a3559f60662e070f5b5e5f87b1686be0bf6cc890000000000000000000000000000000000c7b7e8ab7486012d95af5b2474ce15db612bfe1508852b8d99f4402d0e4f075ba056c19df3caa3a93bb4db89443096143cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000005a2741902dab47e8d38992180a9670faf56d1849dbeaa75b2b4ded93ee5494184c8658232e9131a8b08ac9b5460bd400000000000000000000000000000000189077d5130b3a4d7d4c3074633fb12739f95b8b6ccb082dfa61d845a389e6ca7aff835fa0f194dc349e1584b3141507000000000000000000000000000000000f226324f242cbc5f616c4a897f82bc5503ab1963ca38f30070c7c9916ef6bef5caa7e2e26b3f9fe68a1d59f19a9831d000000000000000000000000000000000a999bdfa10e4838ca69694272b0187f7d0198d6db0fd85eae688424fb09baa165c623dc6da567fe034d7cf9f9a0087e733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b6000000000000000000000000000000000fe85d976befdae8fd0ad33a4404415304afad1c5698b91bdc15abb4f268807c906410a6ca827320f5271c8fd4c8d6fe000000000000000000000000000000000cbff7963daa20c1d20717bcd47b872b3ecd5f38de1a467ef50936f13d6aebd978116a736cb6c5d676c6a9525bb0b7fc000000000000000000000000000000000c3d20ba17a21bbfe873d88e9221571f1bae7f02f35b8e677c9c42907673d765150c737f0011fdbaf4faa883b0dbf0280000000000000000000000000000000013482c68a5e1084faf12e8aec92cd9f0692b173556ac8ac3c7519beb4bd75f847f41ab9432421c631b14c885c001dce25112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea71000000000000000000000000000000000f9091a0df2c989e12a844c447287b704803d1532a3ecbcc890e6f6a885a54b969c53323c105b3d14d12f2cf766b8ac8000000000000000000000000000000000e54f3a9def8b3a9f972726e606195849584b7197ab70a28cf5644cde15e70bb6e3044042b649825adaf5e37c2d5e614000000000000000000000000000000000cae412d8a3ee3c5af38d7a65bdf2440d9cc2d6348dce0791f4a7e71ac483d7487b6c789be0a401777de3f57ec65de820000000000000000000000000000000014df09fd2ff406707004f6afa366d06bcf8bf18f5fc4b444b07c98b3f358247c6056a6337f5b53c35db45904797fb4455683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000a8aa422e1d58fccc84615f9ca4a4743cf5efe3a1066c9819f05042100bb8784fcceffc8b3a739f549b42f34d62629e7000000000000000000000000000000000c737cf78b10e82fc0cc9823891f1a5f1e9229d61e8f369c589512d01e5180246db46e4f09e811464c6e1ad930226d390000000000000000000000000000000016017354434899e2285da6ff4b27fbaab633d962197d2ff4fa5f688c4a85e1817434cbef13a6b018df4e359d7b9ab7cf0000000000000000000000000000000001433c364428ac69ce4f5678aadfed4e6d076241519310686de01572da5cf78af4a98b3502519beb0dcf04b748d08cac5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000001f8b803f3f76aee9825a9a960cd2f9e8aa931568b32be6169036683b4e6d8c4abba6bb73b137c7c6d6b6ea92f2023ab000000000000000000000000000000000fe9edeab60bb55990ad2c85c8fc9341e81de54324652c08c615a745813f08153bab3849dbeffcf4073f087f7c0cf0f6000000000000000000000000000000001955289b1210fa31542bd89f95188d60751b32e8d54f1d4d280975850e57db7b151b872bd431c528c22fb89c9b8784af00000000000000000000000000000000079c8a56c72adb9fc9baa503db394635abb10264dd43c60f2c82d041d43240321ac1028688d92c4696395d8840d52f15f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da0000000000000000000000000000000000b0fd79e62c6129fa115d821b8f2a58a4564f5ccbb14088f59d5e6a17a64e803f32bf8e5a415aac4d6491612d95ee8f00000000000000000000000000000000008d837b6c70468e1e10f6b979b7c0694d65942aac48b5baa829c191579186314ea35fe440e6d843fded02b95f9816890000000000000000000000000000000015a05bbc4607b113b37dc0b4b8add23736e0f1bb1e48aabc15500fa6941b17153918d256b6442687a432dd9ca9a198c70000000000000000000000000000000003546953d97306266bdd359d4daa939e05c0466691de59d2dbe3584e2ebfd9a9e1516cdc9cb643c5d31731835dfb07c657a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000009a4366299290c3c6651b22865fb22cc972a05ca5981f5682574851e41096d531e375e981c4e1b1cbfebbc70a41bb6ad00000000000000000000000000000000001e6fe2097fca2afb8385a3100dbd5ee1b7ae972e06ef9f5e34eb9fbdc65455e1c822299e06a9dd5a3f71a0c1efd44a0000000000000000000000000000000005ad2ffa8861848c46722a7924ece68580fe44e03157c982b7133361e974b59dab7b75358fe498fcde9f68b5b99f23e0000000000000000000000000000000000adac33e0b7e6740c980a4f297917fc4fc13f53a71909f2eecd0067656c6f82c3b371cc638509151bf937f8257aa415d86ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb000000000000000000000000000000001462d509503d2c33829c3fb5380199b79b970c2ae7f944e54a6d0f0deab3571976916cfc311ea6ce6128c467665fbbd10000000000000000000000000000000017f6fe356cb0dd5bddd489c26669f0f365260bb48a5f862e9bfb778a7ff5392938b905759718d050f7d93f107236cc75000000000000000000000000000000000d9b3ca93c5133cabf3d3daa565bc6b51e63b7e37f68f3bcc43b9b3ee7db15f8bb33052eb7e332ae3e9ffafb17cb77d60000000000000000000000000000000017d6b898d9799385990c9dcc3f72ed93333486b98349ef106a230a71d768b75cf56cd946f5952075bc41f26dca9c83c003fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac3773213600000000000000000000000000000000158e5e008796c10f6050826c29523864d06e68977cdc95d281a8606924aeed0b475ab152bec5bfca8e0ec53691b307f50000000000000000000000000000000006fe8e75328c067546eaba93f4be2b15513bae4a3458112c3ffa457d15c23636816fb469f071889380f31870d713e949000000000000000000000000000000000b9b21cd58f8742ed094e9b770182f6f3f855204d869e53c02d0c242a133e957c53c9fabc827d6379b39541170be313000000000000000000000000000000000014eaae1f0789f0b1e8ad3b452b4ed3ff87bed49ffedd13c8c35c35668c33537b63050c06a5bf3d88d516cddac13b4c935ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc627910000000000000000000000000000000019f976b3584ffc188424614fd287eb79f060c55e9b3dd2f3eb99760a7cb5b70e2b62a0895b05e7cce2e390853fed61b3000000000000000000000000000000001117181241fead3865eba4804ec2c14f571aef5351d5bce29399113d007cd4e9c262af1c77daf9183346153e562864b2000000000000000000000000000000000f823f71035a4870be2ef20bc94e97d74d18c0a1be9895fb27c54df1f663df6f9e6e45ea5fe4502143a84c05e517b02b00000000000000000000000000000000141250f392fabd4566e0cd3a472a4b2971a432a3a5e1d9c924866c7a9516322bfa691e9dccdd5ef14c561bca6dd70ba204a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa008452700000000000000000000000000000000014d6827b9bc782863491bc7c544263f58dc04c18e08a87ca2fbb5799c4aa70bc039416a85dbba67dd83bcc27b70748670000000000000000000000000000000016c2816e93ea9d4bd6e42a9720cb89d637d88e00074da3300c6409be98a03403e9ac15f83167cdeb13800ad174ac47f10000000000000000000000000000000002aebc0116a62f93a6e86c7fce86745618e08f4aa9cebca7b520e9176bcdf1521cb2bf7eca7f7af9487fdc82dce76bb50000000000000000000000000000000010684e3254207c4ccdd49e4775198df981afcf7d9f89b894e204c5dd84ef42b89fe3e2f6b9278470e6cde4d3f4abb3b003f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c9816210000000000000000000000000000000010ab1d5494509060c9784b4744a0572a9466d6c374524a6d338ea12ac5ad89519217c462c3487e398325439311bea86400000000000000000000000000000000197568cb53ce03f00aeb04278f355da862be757366dad14ca6d30b3a537df9855a1196010773768a91cb4bb664a34f0f0000000000000000000000000000000001fee249315794d30eaf929f44b99e07927194c6015ff34a4530698d7d68239240c9cc48530d52ea06218a826a655cce000000000000000000000000000000000645b5d701bf3422228576467120935f014c754dd68bb3555b50aff5ca04001a26298982c97a64469aeac3432784efca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c94234200000000000000000000000000000000113e17730f8dd7258157085c30cd9d1950a26c848b55e3a8a55865eb567edecfb09f32ba27fb3e2096ea00c30f31ced8000000000000000000000000000000000076db9ccf8df9530b64cd43ef7b496d1f432885062406028901bbfc5882fd12533f84eb12aa2ce8b7adf9dd980db0870000000000000000000000000000000015e487de49f1e494ce9907cf0ed31fb0a159c5290538ad969b2c8a504986dc9cccf7c74a61f622154e928aa2dd689c0800000000000000000000000000000000195e887083a98fe3f50a9ff4b342e004398cdfee55c4b02a4db0f65a77d3c0b142a45201674726c96d5f79f8604d61860033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000d80c7e50973205585b20a068c64957cf4572eea40e32ffa8b759c38c6ad6f4468421f2fd6a6f5da1b0d008f625b3e6600000000000000000000000000000000009242dc1de055aea82b3b917f88b6232c550c3aff41241a7e54caab4c234d29b5d8138968846f7c754d73ab3b4e7913000000000000000000000000000000001188c31a9d8359d737576f4ce7a7900314aca0eb3b51baeccfdc9245bffec49143a11b3331f9126b01de0c307aa4e44400000000000000000000000000000000104ef4835124fa6b30dd551653aca25db5a544af6782cd0b1e7d26178253e0e33cda77428fc1dbcfe6114a758cab5c814c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87,000000000000000000000000000000000e79d18633c18ac818786bba87d09c9bb1571e179d8769f8fb82e2e2b7a6a8695c1f4f06deebcb84524e8facdcb49d0500000000000000000000000000000000149d0231fb030a1bec170decd307c10e72cf1cca55c8a1b67aa94ce61e4c7d2ddfd0b8e71598e1abb054355dbcac1528000000000000000000000000000000000090f5be784dbafb0a8aab1516c773720341de6176017e0fb43a275d60de54c1189144956d4876d989232b362b90851c0000000000000000000000000000000019dba28eaa6706361f285b3abebef68f764204c74ee93ea011db01c19591ddc6f98799fb3026c3c223effe4489a7c676 +0000000000000000000000000000000018a6a982acce5693e632901f3136eded40071e8c7caa7887f302c32621c5bcf9478991ca519978b52f8f69415c0d070b0000000000000000000000000000000013420ab920c8ecad5b2f9aaf9b0074c2386b0b08c81923558770d4c4a6b206a865af8322e9755706cd5e595bf0ffe564000000000000000000000000000000000c0e5bf5465d564e3ce86d6b742ca687448e6952439b1ff44b86ee6461464e07f8039e8ae7a301c6caee7eb99e38fab10000000000000000000000000000000015eb8751b750af62f57971e88b436658758bd5712f98861fa07328d2b11e8725fb55a2a00252e0be06b0c73aac0f7b8cdbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc80000000000000000000000000000000001328927910ab502e573188271108706152f562b1d5f6ec074f8f9ec5eaecc6cd5e8284a060b65d26463d22c8290ea4ca0000000000000000000000000000000005a1fcc348122350981dd5090c865a2aeb851ba8b6e0443c32f48b157ba673ae5652a70390888b3458afe6fe975321700000000000000000000000000000000019edc749a9799c8d3df75d4024791943a8fa02ba0cac90b6819f0bc42687b044457bc7cc6073506e8fc19af37f224624000000000000000000000000000000000fff20fb2b554b63758963c1583b996ad450cfbd5ca9952e38f38a8994809096086ed86311f7d73a0a5898ac261ce09e57df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec00000000000000000000000000000000002a7005dd32bddf1031f27c2ab999604c048a37c39734db48a30baa86c61ef626cf82084651ae9ba8a265333060a408d000000000000000000000000000000000421bf913a25108b8f520b2becc6f8064029dc046d0d5effbef31f0af59eee71cfce83fec8dda7983d50c6d5cbc8329a0000000000000000000000000000000016c75708f1dbfbeae3b06e5e9a7fb676c27100b99deece14d979b32a9c3cde6e9e96c8560a00aafbe6e7decc84e7e2780000000000000000000000000000000000ce23c27b5128bcffa424fd1d181d21b06b77bd6549ca5eba9a28cf18bb9a979270f6a5807c640dde57a0cd4f3af8cbe2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e000000000000000000000000000000000690c7ab321c0c93b5ae4ed77843ff4030e4ffb504c685d28573e98836e8e56dc19d662ae9f496a346bf2a8be5396741000000000000000000000000000000000fbbe3861a8d202b10801cdd606b50db0ad6ec7b923b90ae81ff5443676c3399e249e9efeb47b72d2b0a54cb0594686500000000000000000000000000000000148a27016968f0258e5eafe0a8182c22091873a5a58b27aa2160674584e06d5b2f46fc57a00617af18d0688df75294cb000000000000000000000000000000000148449d00b3d1b5b43b08a0c6e909a2d9c66920b60224a2c6a2521f0bad35b99e3bff8be0effb2f7f34438662d7a4882897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a9400000000000000000000000000000000026b55509b81befaf6baa682a3e92a0ab423fdaa84d2897613fd31acd9e1590f81581ba0ba87d68af76b01c36093e183000000000000000000000000000000000c675e190570bc5173b8f508d5bd2768c83e7f56a08cddbc636792dd75386939942827617c4aff8628a74b74195adea20000000000000000000000000000000014f59f38ae9e77f3a76478ecd47f32200567bad11f191d303cf15d7801ae7b5a3286095fc8726acc9818914b27a776bb000000000000000000000000000000000da89fe9493b2d9d46596d80162f5831d4fd8cbb83b46e84e95d5d684eb927022ee62ebc3519442007fdc543701f97bd2f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000170e52a240a7ccf2d57ae92ea8dabe62ca4b458a5da42319ae89cad22ebf13541b0daccafa1b1d3cfcffe81b500c4cf400000000000000000000000000000000174879425f3bfd40fb74a88e3dc578e45b0e0eaad94da009e4076dc42d234d78248ec3a035666dd6de235f87e1a47bcb0000000000000000000000000000000005aee47acc3260d11fe0ca16050a29f92763b3cf8ac78da52b3b2b3e26d8ce7b6ccc187fcd81695aa456e9b94a84269b0000000000000000000000000000000005eb297abf35b51d57474b4989dd8f793005bf8e82e49859c41b786ae39217b2321299829198bda4aaa261a2723d43d6ce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a61000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e277b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000002d947e728a3b376de520bf78e56452930de42544241180906719a24d72df65f8250402ccaf14d69935b1ecbb0b4d34c000000000000000000000000000000000d5614ec77a9f31915dddb3e4bb533db001702891a45f0bbac49e73d9c19a235a00442b52d452d77018f883706a616f1000000000000000000000000000000000dfc6a73a8e36b7b2d0614b1c6f7bf1ae284ed740c768f08416c0c09a601fadf3e4d7b17a93601b1803d19a04ccd570b0000000000000000000000000000000010d6a8e4eca2e818d6dff13faf0fae44a7fb90be436a9ef3aab05515a35cebfbd53e9af866cde1745f0e2c3b045486dd2b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000d6c5a6fe9b4d4580f8e1d89f0510bf5dd04e113d6ae5db04af2553bc0eb3a32fb881300f638fb33f7c4bfaa10b063660000000000000000000000000000000013e001b08191707ad98e21b3e0830286c6f3bf587b971dd4ce39e55f06db427676626a5c31c4a67a996a5725ec8f402c0000000000000000000000000000000012f86ed85113ed1abe9dd3826423911e63df0dfb51ad3d1e0e0318ae95991a6a11150176cec77f9c83268a322cb7e934000000000000000000000000000000000dda719cd2cf1aa769f94c21af20ab076b8f024e0a4903e38ddaca21b6bcd6f00baf7e1ed23259f135eb8bcf9c3f97c44f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000195ccfb9038bf9e637b88c83c552ffbd562357792513b15f703bffbd373ebaed715a6772fa7e6e5678c2e6422811dae1000000000000000000000000000000000c5a110f31d71b12cc42974003ba39d99dfd91769c2e93393449083a9b84d31473e3a7dff7ca40164e6e7215b03f44ef0000000000000000000000000000000006233b2dcfed96559b565928a494f2a50c2c375b3d7c60ee6b286c538f4fd5ca6f8b2a61654fd04d679bb3e05b9bcb03000000000000000000000000000000000d42233b7b5ad809c735c89c455ba1e8fbd623e1602bc729c01d362368666e4f90e7b076e32468041f3f5665c6fddb0d0ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000f6d53de4f8b20de19b2fcbe8a6b8b8ec4bb801bce7363f89b133532ca7ce4925312e23c618a0182d158037c0d0bf07e0000000000000000000000000000000006ce094e24eb14b9bb1b4a1838d8b6da5f53b5c5799ab8dc8934b488cbabf698b99abeb016259a4e1b0f626d27f2c950000000000000000000000000000000000874aec7c8ac360e3980a6e2cbf3f7468f1df7a8d9158f8bdbb0f387d19f3b05326a081129576251ec41a926f670e58f000000000000000000000000000000001711c9b2ed7e2f789b29073f180e46d0c373d6e75c587ece67b8aaca1e9d9b43a96d04dfdcd42f943eca48e240b72ba8002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000e8ddf88269aebf190bf9bd7a8276de92ff6039e479e42a490fe4ef00f646b049eb8ec4b8e073caa000bfcd86ee8724a000000000000000000000000000000000a9623655c0121ea0575de714e53c9e304fa3309f00828ba0e786112781a38bd458cd67864ab17929448171b5937c1d900000000000000000000000000000000198fccc4a333322599697e904e9096240b9c54f89ee6db97475beead62ebf730da1a179409133698ef13abe1310689270000000000000000000000000000000017b059ac08a3fcebde5888bec4d7cc2c70b147b3b1483fd001330637ff1c036faebf292801204bf2ba49350795708dedaa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000a36274f33b4dc09e03a5ad648af0913e5ee95af83df8b4f2a158456aedf0a0528f9b4832b11162dd67e4d22b26e9f940000000000000000000000000000000008ce96d8bc0aaf2dea732dea188870d398b1f3c266b9bf019e1046cca05002416c910e02e998a1604a17c333c65c99a0000000000000000000000000000000000c1a0e4a80bb0331a94ed14570053f941a0438794e6f19d976cc62b3806a565697720ea03c2531004f13453991bd99bf00000000000000000000000000000000184bdae93abbe4d931a6a51ec85bc330d6181da2d34f2cc530e56b6803515ba87f5719fd6fce6a1a8bf1ee5a968bbfbedf9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d8600000000000000000000000000000000016c3782daa55312a7cfa02c3be73ed75f4b726df5592351fffae19121b5cba73f427d35d5a2df7c63e2a5c68bf57f3800000000000000000000000000000000018b608343616eff759d512c97257f2103cb0909afb4c24a1cc9d8204274b7c9ed51bc762a6280e223a6116a9b23d1f1e000000000000000000000000000000000c687c11a879ec285180cbae3d2e4219df4614e238d4cbdff148ce5a8d21647c489ade3bf6f738052f149fdbc76c8bf6000000000000000000000000000000000936b34fea3a2633b9aa32244329891e332745876d05f95e4efdef859b23ceab4869db562555e5c8edce87a6fd075ae54005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000000d4636a5e13bb59878319af6bb7c98e5d247c2c9cc970b9cec98027de2d4a8ad12d50906fe302c3d055c499a3742ee30000000000000000000000000000000002b0214bb1ee887a7ff10d458fe35208573456f685ee2fb93bb470762c9e27595cb00f2eae7574c8467e417c63c2a960000000000000000000000000000000001710d130f91861230562cd7ab87984ef45916af8e1168fb17b9765183d9d3f9b2c81c649687842de495a757471e28067000000000000000000000000000000000dd15fe505b1364f134ee77e5e3c1a497a20849b6ec7e201813677a1569a9f5a9edbe3df4c36bdcf9ada139b20e048ec893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd151570000000000000000000000000000000009a48d7c55d24ba49f890791d0f6a8a5ae08a19177575dc0d734fa37b52c3adc45b31b5e485a5d4a5533470c3549f5f900000000000000000000000000000000090f680c6fc1f0588add04ee03bf821868b1ce588e3ebe384dae657ba7885ef74da0bdc98d9d9594a9b979d5b50b93df000000000000000000000000000000000314f6aae1e99dbe3ea9ef85db7e1693a30869f48e05cdb073bf8e14865a671e75abb875d1b41f13d4eb74fc802299c70000000000000000000000000000000013c698b76dd68d1b9ab41672c2b07cb9a63168497d1144b51509b602c5acd71ca6cd049616d949214d95ab7a906a8f8bf685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b,000000000000000000000000000000001747f6d3154e0717435fa023754f115ce2a2b3241b62525cb2833473d84a8ccf4c95e3ea030f2b8b0ccc61124095ac86000000000000000000000000000000001827ed7d84a61c21268857036e91c732b304f609f285cdc4736c951fd8954b10267a8505f25d8be666792358632058b400000000000000000000000000000000121ac61f59051e6e89a7c1e2fb4df4b3a5b7773f46495a99e55348454e1d9d42254e5e11b841a1654ff9c80b157389c70000000000000000000000000000000001bc60cd06879980bc6ef2ca109d31f12cac28ebe4d2a934076d720b12f430e1bc4d4260f40045cc7a862726521a69dc +00000000000000000000000000000000012a6984f0f8967c5ae6b13569a62095b5fe61ec607daff1845961bdd827c00fd56ef864802673dd21d90560fef6cbea00000000000000000000000000000000085ececa080d0f4c996d46c80a1fbad2ac9cff8b3e324aabb67182d79f941927050f025b633fd5119f30bb29b8e4b6f2000000000000000000000000000000000987518a5edfd5ae2616fc60000e117a4f1dd1db68195c3fb68d8cc639e4200945b2864d41ad86fb3e11c504fc1f9766000000000000000000000000000000000310939c7e11b93e5773cfd36fa70020c85396e525840742f994110e20019769abcd339db6881291639c193b987b68ae94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000ec6922dfc74009c3750ce2540558c7c1e05cb45a5d651b96427c615d8fc563219215a0ee431c0a4827e40b26c4f8d3900000000000000000000000000000000040a4189d002a0e1ec600e71303575e82414e6400f06b9abf57151a28835d454f56421a6dc4049902bfb94dc0e9967ee000000000000000000000000000000000dfefc7c163c34cc004e9d97d812b2717d4736d0d1c722b6bf1a29676a32c8b46878d05a2d137cb7fff5fed8c0f02474000000000000000000000000000000000e3f0c9cbc778693c8ba88af8306d45477493ed6be1bdd9c81c65341239eb510fc948142cc30b73f570819b38f13e20f6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed70000000000000000000000000000000013705ca4ecca16559713df65b376c7c5825b4f63d001ebbfce9cd1b592af5f2ddb38ac7c5ce3c5f7af4f39f909887e8b00000000000000000000000000000000179efff38ea1044e91ccad467cd2b49438079ccb4d0fc692e79e0bc374abe064fb9979c4a1f4b92c15cd1b042b501d5f000000000000000000000000000000000b6fda2dbf6339af225515681184843f1a9bcd72f7b1389f186f8d0e048ac16e20967c28e087cf09d7bcac597a85398d000000000000000000000000000000001946fca8c816e1e11187aabc40dd2436533d537ce4639eb2d08630ed2ce402c1806b6c2b3e04a960408fd4d2049849bae5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000001802064095d029d3897725eeb93ed6e3b090390769026120aab6977d0de264a262aa312c5777ba322c9eac29e5396fc6000000000000000000000000000000001410f17820941e6a67b1b4993496cdcf0d4fa2d4fda3d43ee985f2606b1408aa9c9ce412c80c90a0c876cb5ecb76878c000000000000000000000000000000001514e9b2c65ca86713447f2d5bb8395fe8552e059829afc68bc43ba9267ef41ec6d69d06e7407a731bcca77ed5d9716f00000000000000000000000000000000025b5bb18cad46179fab15b2ccef17858f9259a90ea4548852b8c6fca69f0ecdf0b175669bacff1625a7143e762514194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000146eaf5da57b6ac788f8caeb4b2ebf7c8999e03dd839977046ca834fffa7e57cd949e3fd44999a007b5dcf3c8621ba2f000000000000000000000000000000000d859632d3424ffe4227ae14856e05c4e750545cf276c97aa9ec03ebde334144eea670dc68e92b61fc775e477a2154040000000000000000000000000000000010b44279c0c80886e52fde5e71726422da2f9457ff86b21426d80356fad95d5ff3a7491002364d9de5ca99c2500f344d000000000000000000000000000000000851b769a691f0ebb53ee3693833881fed8dc6d9e5f1dfeaf4ab1aa7ad54e2fcac246b70d81110451ed78044a98d1547de4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef100000000000000000000000000000000131c9a76109929fc977a0a6eda0a7c71cfc744f5f3654e2221ce84c70787598e24c5d8049f92a7c4d78fdb869cbdd1ed00000000000000000000000000000000049872d2c7d472e090d2975daa64fd96f33e7f934e739633b1d7fcd5e771673ed8820752a0d5c8b0c6933318293a4f27000000000000000000000000000000000dd68fbb592a3957ef893180dd758f75978042add36c91b7bf87c4493b0baa875e1854fbc09e6856688cc241b76ab5a20000000000000000000000000000000006143699816cad8ab7583a72b6064fadb6caeb51c8625ddbf7b2911426cf438534da1bdd13e22cd545495c486c9733f7bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006c14301984607d569ad1bd774135e7c9e328be1fe54c3b543276bd06bc0bfff11f299a5eb43b5218c3605011d0ea6d80000000000000000000000000000000012f0a848022f95f4884380a5b8e3637a41e3c399a8d2765aada85dcf4b7c2b559122f792850430681a58ca153be2768a0000000000000000000000000000000016b4cb233e1bd59b7b362c64620eaaa5029c173a05e2278774ad6ed746c70a2f6e76c237182f5d9d790966ae69da5d44000000000000000000000000000000000c277d54a7a72c8528188f6cf29d934cc66471607e5e30d493cd11be6b203bdf734aaf37b686cd7101e8599b69446991c3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced432900000000000000000000000000000000169a3928266375dd5793b7504727f939ef0ed52d69e569b1b75a0e094698b37bc70472578beaeebfd0c3df4bce6177810000000000000000000000000000000008936d470dbb86db1567bb2fe7c09971c6d12b07208d9b1b403c20fbdc05ef8984dd576457fc6989470e40ebfe4ceed30000000000000000000000000000000009cdec9d80f2bf3ebfa9a3316e4250741d0d089245df2fd3c9bba4bac1c2dadfe212682166a0962f78c4bf25b618da900000000000000000000000000000000016521411286cabf3fa2c8f72ca62ca311738fbe63717fd12916a4c9e6af9b05d1f5d65cf60e84d9fc5f7b7645fe9bad570fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000006a827f6149a320a74d9d8c1ae8861c1cb963b3eff899710eda642dae6ed4dbc247a22131758d9f843c62710ce083208000000000000000000000000000000000c83a9fd96bcfd4adcfc6d5a47e84108bd763366e91bf06a7431c6c3a107cbe5647da99ee6c1e57c376d366b21a923df000000000000000000000000000000001604d5c0364afb5503b0e1d52226988d7f7f043ce95e7c0a09d7f96e24a58f089156f0e6d19022138170c1b4b7dd33560000000000000000000000000000000019a11c86f78ce462f46e0462052cc3d342596b329fb62a282a59bbd64c345bd266922b1540e40aac147681754643c2e3e538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000015635de295c16841bf44c73639f047f735175e8906301746837838d124bf0d2a1ebaee142393ce9a0d58107c7cb036e90000000000000000000000000000000004fbbd4252fb901d0737d1bf4da62010c06d690a9584c7631ef5d36f1d8c37486a83f2a1e2db21f05c993fd117c662e8000000000000000000000000000000000f4cfcec1545a08e0e0298753ebcef5f61bfdb7c1b9af71cb4c2f783e4fa3948945d357e8302d99aca96df0cb0fc01a3000000000000000000000000000000000f543dad6d4b797f6fe0b00215a5f70f6340ac6bf7cb0bdfc5bc7698dbf0647e4098413dd19ca7af01685edaaa190c6e30b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000ed552e94021d0912a0e7563462570cb572b189569eb847bd12ebf976d22343b9ad04d400ae98fa184b10ff36720f12700000000000000000000000000000000178727c3e6ff33be9894ef26347b104023ea0bcf79c1a33afc26ac0ee9879344964fada757118829214cfcdbbc0c5a30000000000000000000000000000000000b0a6a575afe5b0c1e287815612fdd3838ab39e8ee7795855837588614715f6687910c42217ad52c1b8721a9e1c908dd0000000000000000000000000000000018cdbf244c78cae1993400ae164b42c09dab4d8e3707a69e25ffa8d0b96b8270c022c0375f933f16f45c9274132a0a633a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb460500000000000000000000000000000000070636611f903f55cc9499481bc3415a6de62d5e6bf8bfa82a8ce665f85bcf01690118441961ff46ff701e361db208500000000000000000000000000000000013d22dff8f6f86f659ad17ef91d90a70c180538f03e10de20c445d22e637015d51a311a3daaed90712d04c9a3d992d12000000000000000000000000000000000db3535057db95fc262f8adfd7f08f3237fde5f0e2aab589d4ddcd9c23aadc437e13644dd3b3534dcb17936a7c610cf200000000000000000000000000000000044c177d4484c07fb04d1dd477b188a2c157973cf26075001d14d2b07ebb9dbf8e495dc23b32a2419621e1c129b08c5ac7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d56000000000000000000000000000000000e10fd069f2f5fddaa0112e70ae89d1ecf034defb24e2923731a7c0068780177c186fde92a3c254a1cbdd255111a4b7c0000000000000000000000000000000018363e01e86e2e922ba435651ad892bf9288be14b54dda821c397ae6167f9478c8132e92b1c2cb0c4037a4e020f08291000000000000000000000000000000000301b5ad2d5c35ebdcc7e7cd1ebf0405cf204d6f5e30ae6f46d20534eb6d7013682c5ae1bba76d2811124ebded0d2a590000000000000000000000000000000015fb3a8afad778031d04e094cbde5f02dcc89ad7b7d452c6c8f41be336a4c8b26e75cfc685b8776cbe5a487f09c304083563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe000000000000000000000000000000000a7880b00f6a3e959ff1bd207fa503eff6e7279e701e37b40735e2bc8bf49e355e92edcaf23aa3654bb26fbfb07b5fb100000000000000000000000000000000113d9b792f4e3dcd958664a8778dc4b177c430d8db9da7805595e40293ef2c0a40f7a843bfa70ec134ed89a453f9da50000000000000000000000000000000000d7f92148dca4a9c96c47a0eb284f1834cf3d141be7c0d9a7a060af6e28e45620d8255e465e9a0d8f78b2ffe17d6b04e0000000000000000000000000000000004e7917a8f3070c656d324c9a816236842fbd6147d326652667e7bca0666d214233ed136dd9464c4ac619d46c28e2393833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b500000000000000000000000000000000160cb05390b54151f6b154b396bb400a91fa83d77fabdf31fba349d1bf3b5dfb6476ad4d714af2a2963e41b077bffcb90000000000000000000000000000000012885f7ec8e780cbaa90a465b5706cf07d45bda7755ae3477c79adbd7956b926e0ef5303fc13f0b97349ff8b754dab500000000000000000000000000000000009ad7509e9e7f5018ae3d1280e881ec12129cbf825cb6606459211ed7b358a97cbe430e94dd9f5e4f6b74fb7287f862e0000000000000000000000000000000014d5d2ac2dbc3d5a061f4e52dbfa68e1eb1d3c818ba26686a3171e310c63cfeb188030b83407070019dc5c42dd079413d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000008ee93fc610712411634079be0bd96c3969b48955fe5478b7a31c3ba7639c18291034167eb62e6b15c16b0dd5145edf500000000000000000000000000000000158cb1731b71905d7b958c5407f090a2c8a9319017719da143a3f4f3fb3982abb83b8dfe14facb014321b4f5edb5e41d000000000000000000000000000000000a9f98f775f06055ac1f137cbc1f95f4afa0d1c4935f536ba2e0569d874d9d76b7b86f71afcea07e2e785c7a6ee1c84400000000000000000000000000000000072f8988dd1ab0fa8037d3620068b34848c65e20dfc90612d123b6f9dbcf9d9d699d5ea73739d31ad54c22116365ab983ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0,00000000000000000000000000000000161203d8db1381722644f87b04f47e4be2ea2bb105ea0e67678bc8d29f8a8a3247f8c05e057af8f98032faa93d896aaa000000000000000000000000000000000d3af4842627a095a2dca99b52d802b2ef8c8f3d09873ffe39d224333fceae84bf74780956904df6c1dcf5ba31be218d0000000000000000000000000000000001c79fae014e55e5d0239645d618593bfd5aef665b3e636dac5d191a8b88949d207cf0ae9822ce8e1997de302b386b8800000000000000000000000000000000136314cc68b372b06e7771d82b7ce7bfd0e9fd306787e07629f831c7aee853bed80172121949a940bc59c4a0b76f0819 +00000000000000000000000000000000099434adf799099f2e6e2fda4c905e1543893462133ba216aace25836db37b3dd5bd80af1a8c31c7fee56b5ecf9a0acb0000000000000000000000000000000008a6890e5bcacc13e116e3fe2d772ff49839803e4f81d6b088ecb7835b1ed44f2bfa04de1d46dd352346cdee71774e37000000000000000000000000000000000e94fe40225e863b7bdfab4cdc0c1c8d1399554ebbfa3f2c95ddeda74b3dff03d5cc78e295accdc9f02f3f89b4953de3000000000000000000000000000000000b23f2912fdc7a5fd1de69c1f479228f8ffc9f97c40845808cf17a6fd8131ea60285640d32bcd64c9be71d419aae82fb16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f510000000000000000000000000000000004c6f5aaac90132b2d0c6a4e70354ed2e724df7c3e6298eb9ae4ea92e3c7981944c89140c52e893ef2edb2773ab36bcc00000000000000000000000000000000021e813378be9ec30395b917ded5a0424fc7eab0abfdcd2328f725bbd6a1dace0a5aadebe40e10470df0c09b3f4b68440000000000000000000000000000000014e3fee16a833f8c543860ca438d763f764f488463601741a2331fa90efce9f6d54ee0fb7978460a1ab838039d398918000000000000000000000000000000000dec8bb882fe6028a4155e6e2bf48ffd314b5519dc4560f8f7410209214c4a8e37b2b36facc53f4db11ee63ff11f9f228c02014d5392d30863a12102d1c9315839b5611dccfdb489207f9186625138500000000000000000000000000000000002d107029bea087a2d53b6b371aae06c695fa85631450f4ad92c8948b09ed568b28948f80f1455cd22e2ad44697290b00000000000000000000000000000000002fab10cdd8bf17a633c8b3ee8ed2ce783f64bf978c384fb7dbd7e4f0da50b65eb9530365d982bcc17ab91a29eabc065000000000000000000000000000000001369237fb3241ac291a868e6f4610a5103d93aa915e954f18bcf348ece1560a12451723b96ad5fe162a6107dabe1c986000000000000000000000000000000000cb70b7064a2f94efc86060431ba4dea38bc64822efa73c76f3a4500ad23c452c8f2e72713b066a45bfa49559d14a719d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea2490000000000000000000000000000000005ebb9c8202cba234851cf5e060a4114c6fee0632f37e0c52aeb852637f362ce64403347d336c32617cc59f23cc7c93e000000000000000000000000000000001126827b6a0a8adb698854c0089276861e3cccfee420512f0966df78ea0d9c55e85a0536f14ad40e649b8fe4384c836c000000000000000000000000000000000998549680649b294d506c529ade746aeb087f75d62a246b7abfb69397ed67f0f2ccb4811219b35aa894b2f87e3fcddb000000000000000000000000000000001027b604f877ade32df8de6162251acf2751a9bd770c21f22dc819a4f5515bb276a246ad667fe7881965f0b083d1f76304753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000005d1484bad44069b16d1ef4e9ca1db70ec6cd82eca645c2fbd4029ab4ca33d79780ebc144d8774d82518c1fefaab38530000000000000000000000000000000019abc7063361ed64a5750b70bd59283e6a61d55d49d8c2ea2f1be8ea425f040d3865c399a66c253bf38355360f06cdd40000000000000000000000000000000010a97b13b3b579ab5f7fd9801d9e4fc40f3b2b2acb9f21bfcdc6b6a3168720fd0abc2f77ccad01be6a6e268fddf3759c0000000000000000000000000000000004126b5454050d761047e5da23c0b2f9370996589c04f255a1ce8ef37a3a7c8078788a0125e4aa86aafce8df33f322d3d1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9aab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da0000000000000000000000000000000016ca071d741363e7c3297355e49cfbdcf03d419813ed7b329cb2b2a26fc6a46cc52149ca3e9ca3ccd7284cfed97b985d0000000000000000000000000000000018da360fdee88e806ea1a61c01e86687f8e5359730c36c876ad2acb0297bbc1ae13d790d1edaafdaed65db9dac02a74d0000000000000000000000000000000005a46e4572f667b46aee36b8d377c249de25e797b31b822474aa647ee68cc7d40b083fd0a1d938e2b8d85508004c73f40000000000000000000000000000000011701bf88d4287c98996ea561c1ab2f29a5da9138338c7c7539a5fc8355efab6f58e240df4b0e0cb7f01df74bc8010501d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a000000000000000000000000000000000576e79e507d250eb4040197064b8898b0142b3a2551875935f91f22705bfec6da156c7858fbf77028d4a00957553bea0000000000000000000000000000000015d39a325181d6d1a809b1236f4a1ba66a9bfa6c448470425aa5c8ef9fd00b5481c51e8752088dad62e928b3180408df000000000000000000000000000000000aafabc2f68a4933c7d734660e422ba154e37dd90114272e948f79db4ca51d5ca75d504cf74f2dd0479871d69a08386f000000000000000000000000000000000b017c731f63bbaa8fd0b0d9c17140060429f515d2e85a938d10f6529deeae4818c29b9a628802d0ffbbff720339b7bf2cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d400000000000000000000000000000000028c4dacba5f33ba66368c19491f4baa6aea4f309afafcc8f464f2886b1d05b6397142d02f0295fd50825819621673a1000000000000000000000000000000000849e1b630e8db8ef039f280f8d401957f807ca90479745b68c3db1b5ce3a02fe2c099ddf9c387d7ed76ba75d6a9be9700000000000000000000000000000000013b43fabc3d4df82058db215a69776ed5dfd4c773d7a013dba3b4ef5cf65e25f79d7f76a06ca99132d6fd1fdadb59d400000000000000000000000000000000072cde8eb3d3e1a7f7e4a9eedb8e56f5e103db6de6ccf833f818f02a0706b2043d4ba0d5473bbb6472e8aeb28364e1d8214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e00000000000000000000000000000000007a33b95f42cb1d1ddeff3a199ccfd9a5d47c9fcb89dc09b5b3f59dde2b47d24ff29931920b76ecf6deacd70e83576970000000000000000000000000000000014c0a63e0152f06cfc32e6034b7829f9d9d09aca0a6ef821dc61ae8d99b77d76c1b2fafb2a14938a82ec72c4041ebd9f000000000000000000000000000000001433135cd913b05b3f58b2e9c1a3bbb951d2cf6c92fddb21bd5e1d9c44e464d5fe98f0791044d56e50b81a83ef6cb271000000000000000000000000000000000be12ce3bc47bf69a13762343b5e39c2a2f285896e5d1b73c55203cae2f32cccbb4f7b8230b2026a0c8b2f63db5e5bedc1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e2000000000000000000000000000000001494984d478784b2ab3ba27464109f99172033fcd5780a48fbd5a2144354157f6fca2d70b15b0081dfd306ab4239cecc00000000000000000000000000000000078aebc22025af53c6542abe56cf72ce5eb11d3f19212a0f7442d0a0df907c8aabe0ec01d1245ca237a691e685011bb8000000000000000000000000000000000415a1804a46f4595014ef29b12d99b89600aab1d98352437ab8342abf479bb2215bc687532e75f140918b3d030ad4520000000000000000000000000000000015e7b0dae7e3e80eee3c7a9ed4c739288ac2192f7d80b2c8cf9934cea5719081803b207623c771051d7694e705744dbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f74000000000000000000000000000000001470f82372e197a21aaf46cb2bd3c0b77c3428bf2ba073311e75eb65471a8164753ff1d989560f1ce477952bb6555200000000000000000000000000000000001645b5e5b4bcb5f6d34ac841e3a80f09a86a5edcb7f2a7e7bf549b022c0073e01be82e4c9e5c8e8de76ba367595639af000000000000000000000000000000000b43f6572553154e2530fb448d5bf20c3a182cc190149d3b1d75b60e45baa048f44884500fd02c434f9f7eac01dbe4170000000000000000000000000000000014adef5a52d76a267f87d9a8b5e9f570e7775ca4f6a55a5afbf80baea311b1866fa0689271799a654eddcfe36a6bb64c61ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a000000000000000000000000000000000345a2ffa21eb06fa1d76fd81b1239147688093c6a44a40cae37f2af26add812884bed3e8b4643675b1a45320c64f7a8000000000000000000000000000000000c58eeb5ffdf886d6319ead9e6e190300ceb91d58abfb79c0a322de3987eee73ab82092eea8e1249e83ab67e33b303e1000000000000000000000000000000000763a3fba513b6731fb501aab39a4697f3e4de89125c6884f9782bfb73e6e062f17d34555a04a8e2959ee4e1a2ee284100000000000000000000000000000000024180dde2d23cd88cd29c8142d32435d0db57b8ce8e309701fdb963533c1cdc2595e3bfc01d8c0d08d594e096afb34a681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da00000000000000000000000000000000022d2e7502c4d9587df7ecdbafcbb813b1812d76655cb7f9f57418d5ac83d4f60b84a0ab5b53a5eee3c3954aa9fc70cb00000000000000000000000000000000083212aa1316561a079cb8d027bc8f89161fc828d050c8837a24fca6f7f94b6dbf10d6032fed895a427f07827deaf3cb00000000000000000000000000000000021552b99dc02a051ea3af1b1bbd0a7ef64088c3aef4a58b18a29ca05e1f442f8ea2c8fdb3642ee94c5df501ff6898f40000000000000000000000000000000001015a7987d329cd1eb5f991c270643a05b8e1bc35467130e9f53c5d96fc3c8336a00c060dfa2d3165358b51b6a521e56f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000c19c3b9d7c7f520968d8531966cccbe6f0c3fa0938480ca3591b7489febdabd56a70ae55cc309e04d7acb3de6f41a3d0000000000000000000000000000000002ddc64023f0de2730d3affb695927eaba50ecb91cdf1f369a511a8cc8dae8913ada2d8f27a65e75deb9b8b648e4e2e00000000000000000000000000000000000311ef260debf2310fc31fb8ecc802200e11400909eba24b14d9500ff47c1c36ec540eb970c9262dac947b0c2053d6200000000000000000000000000000000199c19645375dea7602b74301adcfd9af259e1c7c20f377fd10d56b719f7a6e0e57d780c976124e0675c2a54aae3e0f5a8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe600000000000000000000000000000000123fce6b793de0ce2d31f2c7c4218fb20f9db68946a7d57914174ea773d6e6fe1fbb1de141c742e0a8154fa1d81a91f70000000000000000000000000000000019f75536e004a61c6d7f466bfa06ad0c9375a1028eb7746406e7c71e551dba249b5c6284f635fe26989aeea69075b3fa0000000000000000000000000000000013088eab16ec77c7ce7e84236337e395690169a4ed7e44e23d233d36d5d25e6afde794cca2bee88fe749851a71aabe24000000000000000000000000000000000e627130da43a6ede3bd6f2fcdf008c8f5c7b7b1fa56cd3b367d3096317948bda115d732346e73b731d1921a1da6aaa18823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b,000000000000000000000000000000000e3b85a3d6628e097a3d962f3c9aa37e3c5be43faf2a12cd9830ab33c4a904eda23027735bba563d38ae5ae8b302864b000000000000000000000000000000000c92be62cb091056d981ab8872292017cc22ae4eeb0cee03a60cb5d57d37b264fbed2d752ae9dfd76c0bdde1f1dd10500000000000000000000000000000000019e172b23249a17924b890cda6a65287039d0c32b2c0833409816cb21ceb73ac95928234ccf566287400a2ed7d9de771000000000000000000000000000000001276e206235392fdf65e4ea6210d22eb7afd7783caa0777ff0af719cc1822579d5b82fb4c30f07dffe7d68c649b9e2fd +0000000000000000000000000000000000a3d974770eda8c742e5d380482d36fabe18901b0217485141c3841003aeac4459ee28b280550e4643d0f50862bf2e2000000000000000000000000000000000369c2bf3beae4e8761f6c06d9bf5261bbedb177e609c81c9bd81ed0a17573b6e10e7f0512e06109cacc3d483918ed9400000000000000000000000000000000030253d0a050986f49c77ee20ea8e3e07de3ba72c39ffda877bcfe569eeb29598588f5a7cedd9e2e34491a059ac4e707000000000000000000000000000000000ce201f07353bf82ec894ec66c7012d17f3c7968b28b45e88f091510e1646380f902c1c5b036084f9497e9a91476dc2c9f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c54070000000000000000000000000000000018f493dadbcd93df2c614af310e5aec4fac9e502843b8ca8c3de739315d9e9a380f826e2470c96bffa8789133f458d0a000000000000000000000000000000001768f8c3da107b9ac30a12b99f2f3a0f21483c0be334377733cee6024d85af91b03c7ea1c548b42e7a7869141816917a00000000000000000000000000000000076cfc99c16c270d2f6e34aff84832f9ee6493ab757b6361cc921823fe9c30f1c9b1664b650548dba767616bec0fd5d80000000000000000000000000000000006c5f580c9556ed31847b1a3527ac0b5b5f15b9c9197d3cff061c1cf72dc5c96cb5fe98535a4dca8c4e20c8c02158466c8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000010e2d7eb4e874a9c72a98e4c36701a9fa11051b683ac8ab9ac20d14929d72ff7b92a9048a11bde92dc2696467fcb48e6000000000000000000000000000000000eb29e621e9d0af8f661eb1ba90b307eb542dd84a486568f85e19055bf7b8f0a76d34acf276897a01349eff2c36e4b43000000000000000000000000000000000b5f890f22658b207dea2d721d90a8f5991ea2c5ca06b8d1b293f60959ed424dbd7052e010e594a5ee0feda1e93bcef4000000000000000000000000000000000082cdd4d8452078e8b853f196dd76505ece5e98df3e6a8bbb21f422755af23c5ab261accea48d8e4193d6c884773cf6e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c00000000000000000000000000000000077501a457d5f0946d25a4c5eede1b7fd80d90d502bca47d8cc40ab2f9a6d827d7323e7d4035f3d32b02401141f0a89d000000000000000000000000000000000985410246c1db01b42728ea46758906883586cba5618b66c21a3cf58cb98e7c6e0dfbabc5505d1d11ca9d047fb6d25f000000000000000000000000000000001775f4008f688882d02355b6eaa1ab108f239890f71238b36c76228cf2f575cd15f15293a62a72f6ad0ff17d7e8ae79f0000000000000000000000000000000004b6967a5ee180d8b92e95c5ef26baa56d8509e1acc710237083d19269c1c5a1f2d1680e85f0bf040747be1d301300b0f7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe22000000000000000000000000000000000fee170589e8a3d3fdd93b536347af5002e59e8ef2ac8327a7e9f382560ee9bc36b3f636a3f99fba8be7b5ea3dfbcfc600000000000000000000000000000000032380cb6c043e3f9ef7169da12df1c6529d776b049c7061df660df841840933e514eb7ea3152ddac38daa2c52d66191000000000000000000000000000000000620ebccfd931eb70ec688110975ea24b7ee0f9937841aa1b7bf4f45af88b732b76a26299f0fe48259fdf08abefb4314000000000000000000000000000000000dee6bb8c198363fa4107996331aac07216b82208242c73736be31e14e4e04d97a56a1c22479dd94997acb0d32abd3b0557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000eb60e98d6cb4e4b3e58271d47261d05be892eebb9a37f6831ff19d0bf2fc235e655f0eb9b01494868bc082c58ed82d40000000000000000000000000000000007254a64a0d94340bcc2b0142faab2d73e8189dbaf52ad0c3a9206e802193168b8eb03cb18b0e4f1cc95b98b943910db0000000000000000000000000000000001e0051fafaf454072051d2aa9512ba2367778aa1617cecf6a7f989d69c7627c9070c349d363f56711f172d43f5730cf000000000000000000000000000000000f4141c8a45448fecce09908ddb42f7b5f6b5bb53b9e1ede0417bee327644af5c98470e8b5364642fc7435f84be1ab443313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb7500000000000000000000000000000000049c28e0bc677ccf54f4cb46e953a057ffad624752332fb9ee5295438fd5bd61abd2199a0bb729bb7678cf3077e32ec10000000000000000000000000000000007138a996356ca3f5d63bb5a36dfe901254459ed515e18ec8d91fa747a691b40a19878d9a6f1dc74e4f18374a399d38f000000000000000000000000000000000a621b36a3cf04e6a5cb699fe4ff7fb8b3361207186848e81972fdaecf667ceb35f413bd68772f7c1f77c1d3f43a3d610000000000000000000000000000000010becda5a06f3f077218d4387158e4a1ca5e0ef24d4ed304723ed5dd96da7cc9325f7e4ae16d9d6c348577697aa6017b8faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000163ee307e0d0c3b61ade05a022ce2bf315d820ee8ece60f93d63a150e02be843a2eb2240a4882c29be2c7403932c348e0000000000000000000000000000000001fc8e9ca23e8dc8457df8f255db3b434f52cddaf05819dba7df1c5bfed438f756c8b57442197af18bf83fe9ee2b765200000000000000000000000000000000109cbe5279ccb592bd0b33b1a482df53459c48cd0913549739b784ba7ad32872377c2e3924c4d45064b0cc4764220513000000000000000000000000000000000d789795d556a37a375d83120a022f57e26da6e6f9aa3e40e1f32ed04b50fafc4d40d0b9b20a26e4d230dd789e20823013994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000960654bd6e6a6b2f7d87c3c4d6e3fe6c684a50b62f7acf82a67075139a614c056a41cd49769960e229cf07468fc2dcb000000000000000000000000000000001727f2dbcc8d889127060de0079207eed1e094259b59a20fa42ab2783bfd176da00e61a65709dcd60402398fadf30710000000000000000000000000000000000c17805a01e64c320601e0ef521b6573e9c2eb354157cf0412e5c2b13f826759310907c4b77164f5899958cd30f78c030000000000000000000000000000000010fb286ce797c0429ad3385c709259b55cc962ae02c814e537e5261e897b7ee1b7c660273ec908110f997b166c14f5c181eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000015d96a0f988f4951206aeda63af85910db49ab817c83e218ec74cbbf5f34f81279d8a3f2fd1f3000f73b8c5550af3fd600000000000000000000000000000000186d2eca1cac226227d8981324859126864b84e8dac563b4d92357591c2416c93989cfd9e1ab6ad257dfeb168d829a09000000000000000000000000000000000a8a7247a3b09583cd2d4949721160573f1f88221e6eae833128914555a594f21a3fb2bfe3b1f01f3dee90f7772dc97d00000000000000000000000000000000132361ac1950756549c957c174cab9ef586eb2057a4eb22f49252cae032975f56eb0cb7ea70810afaf5716afde5b88015bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d000000000000000000000000000000001328e22bb83331adb09dbed0a8c58040a3564fcae0ec85794f26c077de69cc0a7555f011e028879cb3aafac4dbecab33000000000000000000000000000000000a93db348adb3886802bab1e993f5d7275360a5b0466845055d5274e44716f3e1d03a6e1796ed4de4c157dc8a2d92c39000000000000000000000000000000000dc0879a8e9556b7d9b6d5dffce5e648f835f10acad3afca7a73b0fdd5d5babaa74a1ca80aa4f6880d9b015501e218a20000000000000000000000000000000003f7ae8207de4a179ae48cffc8c6e926455e46ef9e109c08be3ae7401bd36e0876642ae9ac4fd75a74c67ffb7790e265a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d2000000000000000000000000000000001078f43093602a2dacf9b5dd7ec41d47bff02e0dd27a996b58c73febca06e3d977c2fbd73f63508243696ab5d8b97b980000000000000000000000000000000001841869086e850ad97b3122fa51c437113d2bca14deaef5715c354d3845f6829f6aebe668844352d5af3509c0d8da7800000000000000000000000000000000047c42e83194143b9e977fa1babf80d455fc86cf6cb491ef8306a1c32bbf8c868e11bb3308dd5f65fc2942b3e49ff5c50000000000000000000000000000000000872ce87ecd22b39b14c9036e971a562d51c5122bb10939cdfd1945dd1445ac9f5de06b70931aa5c86cd0fda51b89952c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b200000000000000000000000000000000157820de2a134081eb47b1800ec72630348583d77d512b4c6a8c8e581810471a2f57a8eb6b0af87a91960424009ff124000000000000000000000000000000000378cf11b0a2848b06412aa754ddbee5660730001db073724caf902d4b4894959f035a8838e28554b0efc2388f2b4f27000000000000000000000000000000001301d15f290dd11c3f8e53407195e02dbf8f13e4fe25fe38e84740753b5a0032f8dd07df3ce46ba424f6772b3aa66f4f000000000000000000000000000000000d166040d457187232f8f38f2beb1e0e0864105595764022c282867346166e46eb789786a7ec7c00b0446207e9ac1ec05e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf042275000000000000000000000000000000000793797c5bce4b1cc3bcd751c5ae1d293477af96a0e7c6bd392ab4410f806a53088cafeed51754ee7e60e61dc200ccb00000000000000000000000000000000019d595730af1f3039e37494b86a638a528d8bd24c429e3f8bc97076c7463e7f2618e23bd3f300bc7e7a4674f14f8295d0000000000000000000000000000000008e245c7590888fd8dd58f93332b81f48b6e3acd3cfcf5f3b28df654eae1172f52ef5a121707aa9cb111b0b402d1bfa6000000000000000000000000000000000a7c6403659e1a0c2dc7cc2e9b57a452bf553e96388676f4bf4a6e26b3ca2d3cb82006850d8340dacd65aaa0d20e6fba606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000054c37e8acadcec8a795619647d4cf1081a0592de02bef916f847936a1736e74cc3b7ee018717495def8b4ef1d098fc9000000000000000000000000000000000291d89d152b414fb5e7139d6d0bdc7b5b9de1fc44b49f895ae08718b631f7652bb4a895fa11149b9a9db30c344108ed00000000000000000000000000000000107b30992ced35e4ba874e436bed5d88aadf0a0c944ca3eb8319539017bdd652feb7483ab6c705aa17e845723b2cb46a000000000000000000000000000000000895dd8e04114fde4a4cf19925004a72f617f2ff146dd650a2cdbeb12977dd2b34ea7d655dee16ad9560b144b81212f5c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b85100000000000000000000000000000000005cea2e036a8ce057e4dbe2d9d786eb759c2a75934580480f78d2e228c3150a0a1d8c95ac5013aae3ab6e35f524d37b0000000000000000000000000000000000e18c18884209f9e4fb17431248a5f8d29c616a58af16e949f4317c2e117b80d531a39800dc70f6b161b98ba040a8af0000000000000000000000000000000007c42ce885d1bae906128589b72f2e6c18e4eeacb78c853e923e6eb785c073b6490b2f6b3dff2276916d96770ad5019800000000000000000000000000000000132d809c37c341eb0304ec933a6b11bf9ac0d2a13ead818ab6ee03ccc94160b405066381dcdb13b6ee3f5dca48ee10ef47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455,0000000000000000000000000000000009406918e2dd6f06f4782ed110e29516a911f47133ad8adc58f5780de916a8973ad60e05ba931d66de7545a92f388c20000000000000000000000000000000000041cbd52cad2a5f4c8353c7153b5711ec23fa8bfa2f34f5e1a16d8a14cfd47c237766880debb992a05ba9ed0353beea0000000000000000000000000000000017d4211c827379b310956371129011a92d62d11f0ee5b0cbad9eea2d3f2a95d364717713fd0c544747338725adf27248000000000000000000000000000000000a61903fb81064614c9c6894c7f3954aace7611cedf6bab8e751f0c203bcab827d296016947c071d7b6ccc742e28ee9f +0000000000000000000000000000000007f90813f8c3eabcef04dc1bc9bbafe1dafe220e2db24e4b714aab2b164d7ec9df3e6a3f903e8b7b96df2ad8297381d2000000000000000000000000000000000e34371e51c4c952a0f38c4aaa5fc2324971ade310af2f36ed511fc5fd7a602a551ef77775fcd0f1fccc718710239561000000000000000000000000000000000787edf7a6ed6b50afcd7c0d3876d8919273428bc49833e3503f650e48e788b15cd82eab2672f612025d796bb62d72bb0000000000000000000000000000000006b49e631ace4f72c959919df5d64c537537ccaa3d1890ea9a1d70f9eacbaaa2ec361edf2d4880c9810976c6073028bc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a000000000000000000000000000000000cb63d7eef2d6614d1f629756b3a619a221033207d1621e4ce4791db4248500649b91ff07cd2f1f06eae3a9be5b6af080000000000000000000000000000000019aafbe56da1569959019033e8cc785c9b98bba6b069603969e7ff1150f023706b461913ea7949306a44c3b7d199e86e0000000000000000000000000000000005cdc3a7004f7a7f79ffbf4c4ba7c5dc30ecc62f270a5c231406fa63d82fc64f45e94779cac851ff8443040fd3b2ea6200000000000000000000000000000000040f30dc98e8668194c9278b189e0c0f7b76a4c686ce26a4a96b93190938f07c5b813670e206eb6b5da29624a1b6314ba59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000a5a1bc231f803ae272e497f812ebb663c2ce8b43a366717fc6349264823ca93e29e30762c1a366d8680f81838907f59000000000000000000000000000000000a88fd59ee380449d632d7e1b926210d984d5298fa807570a63a63828cfa55c6e2f01b7745848281795dae36e562181b00000000000000000000000000000000025ad34537909e07beaaff09f22e91e76d93c668d1b45cf6845ab8ba0129e417b758e85a7100a31a9037e307f454bd370000000000000000000000000000000013590106126231b1c616a5dd7aa7ed6946aacdacec963b507907950d6ea11cf1f5b59f819a43eeebaf51a1faa7daa8e719ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c5000000000000000000000000000000000938d43b9747c926c3e2dfaca2d6f1e6d61d5a621ae08c66a5baf33d9241771509689f9ea7d75af607d76b66faa8fbc2000000000000000000000000000000001889a48a74966b9748f4a6128dc3d75a69499db1ba1bc9aa3a9428f0efa898b5f78a9e2dae942d3794ab3d1157a1d305000000000000000000000000000000001129c9bf343f476541980b85229c5c25289ca62173e29b75de431b572c8f01f64ec1aa4625dff9e7df535194c7f4e6e7000000000000000000000000000000000fe95c71f703dcc71cf409b332f66fd69c330758d41832236a510ec4bd9a28c4732434d4c3f97445e6301e3070153dbbb8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc10000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8dedf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d000000000000000000000000000000000b419b675ccee2509daf66e5da4031b08792e1181140b30489ae21f7925305d8cdd8a104580ae5938586d6b8e74f750f0000000000000000000000000000000012e070ab7118991a20b27f1a87fba1f5815665d76269f0d3d460a6b701e57ffdb4fed2c53fa63a3121c74f67e770f31100000000000000000000000000000000124218ca85f235eac3471e0acdabf73f79afdd4bbc159c1e34c641b97f03735e4c3430264f2d94f640486488dd1067380000000000000000000000000000000011c24f4fa1862779f22a628edf9d3cebe0f7593964b642f889201ae85e8fa01e00e48355053f5a7c6d920dcf6a7ee1d60dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd000000000000000000000000000000000266bf0d9d5a4fc713dc0fcc6ea6edae0b326e22cd97bc49c48a7ba398fc87d7a0c7141ba24d80df454de66c2b5a55fb000000000000000000000000000000000aa8f95c7cd61733b0a260149d6608a73d6c1f989afa8cb2aa4098e1fb5a66b4ad5a5c1c4d901aa79812385fd507f02e000000000000000000000000000000000a6b4929df13e1fe7f0a0cf699a7fbfaa97d7527cc3ea1f728ba59def2e75fcf3490199bd42e93b7d47985a307add07c000000000000000000000000000000001719321981d2085ba31c9fb131d6b79c7df5d10d6ad0b5015454329697860121e781093fdde1f19e897dd6f2c272f87a109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d37000000000000000000000000000000000431002c9926aa7d2b06412f544a868a7d48fb5f077dfd098febeeafc28b876c434daec809e5cbf50ff2395ae7e456560000000000000000000000000000000005a15f713b6eafb09495cfb1c89e9421515a07a99ca0f208883f11c430ffe6f2592dbc41bcee5db36385a26f67cd26bb0000000000000000000000000000000008dd30fdd7767486844967c5da0803b52282178287b8ef28e14f07b487132fea3a82d86d414b4d0a25b3dc538be11b500000000000000000000000000000000002dcee67e2d17b3106dcb9f4117456a037ae1996e8f7a09b179baab1ee8345c6d01eae554d3f40da86bd79a04702fbf76326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a000000000000000000000000000000001629fcc374e99fa8303a715fb5077f266b13367bbc0098b5463d3298c0892f83127d6b7f751446575b88858bc742586c000000000000000000000000000000001100783c10618752d25c235e1e76dc64db94adce05651fb8df0a5ee7c299d35b1319f7009b857892ddf9e90c91f7d23b0000000000000000000000000000000000ab6996e4935131becd5df288dacfad1e69b41e200ca7dc841ecc180a81b9d2ca14fc8a76a4e7bd6f924bb9f473de62000000000000000000000000000000000ae9b22f8dff29e5e0a2ec5b5641f53fb5e1ca03130b49d0c26696ca4b439a9d998d9a364ac9cc5ec52df699318cffeae005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d0000000000000000000000000000000019c557ae1c12ff8a7c00b7c9e4bc3d65c92753549c193311a38a84bccfc090052a2219461a9691affe2d67ea4357cdeb000000000000000000000000000000000cd35c5dd126bd4b90dd671f29953c5a49a14b6b3fe946991416edf235c3eb3d574613d27b05cd879518fa7dda3ed39a000000000000000000000000000000000224392063b0825fd332bbede23588c1912e7670a013a99da5507f650dc4284431698a5b4e8c180269af8bb30e4fc8450000000000000000000000000000000002ab8d3250d4bb8ceecc8ca2003f91420d0ef8a7dbc2361e5e7fbfcb59471a4c525856bf796a2c2608d219d215cf83fe3917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000000911417908c2bfe4f63a388f699b31b47df1ea0ec289ee3f96ffd0c71f3deade00d1841aa56b4bebc2adcd3068adf920000000000000000000000000000000005467c7e58e82089fa285c28ea22c759c7806d86fbdcdcc8e09e847d6330922a61bc331ae3b5acce777b7809ca98213f0000000000000000000000000000000010f376fb47933b1f701dd81cebaebb2d8d8f5510a26fb3e9e156ac5ecf2b943c5fa2812d52da542e6c335abad8ecce3c000000000000000000000000000000000dcbf467432acfa4eb9ba11a7cdf02f9110f44ac371128ff8f1f98fc70e4554f057a4608180bfa54d99fd2da010594f6f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000179aaa7119f6fb986714c03b6db16f25eca7172d24cbdd318bebb633bf08920f9e2a8136c94e3ec7c19e57ab51531b3f0000000000000000000000000000000005937c484213ab5b2ca8ed1c5c90e8d2a2f1bac044b88c04b301ff2fdbe67dc4ea42779d919ad510cabfa2ccd178cd9f00000000000000000000000000000000183cc23fd64514ead63f55d375a07af7cf2a56aca64a887dcc542f8a396468a6abc776170a5d4b4bbcd4dbac285e7ffe000000000000000000000000000000000ce12228dec2f84219904d9ac7923f122a99803a9b34749ca68ba385c178811685c19a492aca2e1123ee82a8a9cb90fc3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb4420000000000000000000000000000000009612bf9130e17110f8b15aa6f3317071daf3433bf6d008c383bd5c2fdc7ca03f25ff4cdb483de3c84c0ef9e579f38c6000000000000000000000000000000000c40172540a7e20eeedfe02c37aabac07165cbf04830f20fa76fe8b05c826e7762c9f7567a0fb972212bf736e627948a000000000000000000000000000000000f49e5b1929ad3ed5c07670c471710baa24e8478a50f72a5b7bbc23a66cff91d30a3d68961fbc2e6e8003d08196f325c0000000000000000000000000000000004ba098f915ba9e934384682648ed8d4e1cbaae60d596655fcd9c05f4b049ba0d278730dba5ce3fd4892531a3153bb955ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e99188000000000000000000000000000000000449b15ecec6d6fe5cd32437b54218f62527157aa6344c635fcec8f8305c8b6e44c93105984e0832536237606f07792e0000000000000000000000000000000011e40e8aaf75f5ff8e4040f725ac27693d7b24805a2539ff54b3a6e90c048875ea9609fb8fb3d8de63ca1118876c172400000000000000000000000000000000006ef2a24445f728b53cbf01e5b076acfa7761a84d8261cf1a1b99cc32f330f32fa5ded83d5cd51cc284207adb2451ee000000000000000000000000000000000977966380e772670447b15ad9917035273eb71a21c37607a761aaec808909fcfed50679769aee1573d73cd241de6624877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000074e475c0ff1a51a24be3c964c45c41f767f890dec82712d92a965be504fee43fcc6c0684b2b17c5b294a3eb7ceff1cb000000000000000000000000000000000597b7dd287f3fb27e35a9e4e1718b6b1a4addf9e95e93aeaa25aa34023669368b794a08fdb178d9bcda2738534d1962000000000000000000000000000000000a492d648393bfa317165ccb552e045fefce5b3444d5ff770f43a08a68efefe7fce1216114ed1495cd00f832538198180000000000000000000000000000000003d85cea8063828ff025ba599bdf1efe0412ed5ce06ad5faa841c6400e4eeb6aea1470d48f4e66fc768d7e7bfebedb37145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f6070000000000000000000000000000000008ecc3dd40da2a7a348b4817d9c84242f2f07c5d0ef810dc08311e9d4090d6d96d68b6c725ee6c24de076c71754bc4b50000000000000000000000000000000018fb3a1dc4e0dd9227fba310236a6db7953f0b716fa995b928a2a8de38edb97eca09fe2ab385037dfdcda2ee577e677900000000000000000000000000000000062fce7fe7810273a80760d9f4b3be9e7c821f38ed3e075210d3aac6aa7a763e3cda56465f88b34540b408ac850742080000000000000000000000000000000006fa94466cc47990a80ae6a310ea765590a0e646b5988925f03cc7e30f04fc0a8044b403212290b2fc46c77e84a9028dde4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88,000000000000000000000000000000000f41bad0a932e28096e51482c646dbdf294aa7b91e0ec258670e7674864765c76989a936fb440bfbf4268a49f450d3230000000000000000000000000000000018282b76521db98f589b1c14e603b6f5d27af357553bca761189a38a944a11c66480f7ddd89d17e4aeddc8d78a2b3a0d00000000000000000000000000000000007efc4a90dd97f1312047ac78a3163dc014c42a44c7054daeefd5b72cd0488832cb6396e02ccff09e4171d790954fcd000000000000000000000000000000000e790fe8323fffc96705a42ca071532d5359641ff7cf8714789c9c578717a054c811cdb581df8b6a43729c6c3e3255ab +000000000000000000000000000000001304e0ce6a4baa6e0545fdb314523fc91f73eee157249b94f284ba7390b12b23b1c849c45a563ac82b62a2c48aec24e1000000000000000000000000000000000a2d0e9e222db70d49d1e85f587d35bdf5e8328aad14343d296f95b152a79c83a4858cafc350a5df1ad0194c49bb929400000000000000000000000000000000199efb09b34d0699eb4bc1c57fef9cc5d98453bf522c504fe7897e22bd0596a3a6c310eb351e15e3f6609b074b240f7d0000000000000000000000000000000016b69f12ce30ad1a65150094e29d4cd82fbce5dc343517ba9e5d89245ec083c44af9a3dad2169f713d3b01fdf70d20642576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e0044839000000000000000000000000000000000b3ce4ac12861052c602e71906a7c9f3e2186bd2b6eaaf222d8e80b48baee537065ce78372ed936e6728b9642ba1fdb9000000000000000000000000000000000e8186561d23515bc58c77769c93db76dc9c62bb715b283cbfb71462451120a6ded736cd8a292a6799fbad7617d9aa84000000000000000000000000000000000368a6dbc7daaab0a786257c813b1a25c97468732c27cc759fb921cbc3c9a37a46d7dd0298771c447d36ef0a10579ff5000000000000000000000000000000001348d5e34cbe54e3a6b357c4e651acb82d2dc40ef9ed8bb89f0cdf0882ec6a737998f4e4dd61e296d101cbaabccdc3e779f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000004ebb53c462239a78bf13f29856ddc4d78645c457a656f3cccec9d3c032ec19c26488f39e0f5bf0d38424f9e3a9bcc870000000000000000000000000000000002fe1949365831f7c38b1cd6cf2e22345c4ce40cd73def77889c214d1077d70e39578e8be4fe5998f59d47cca7917280000000000000000000000000000000001152f2df1512013a42ac056b75802bc35c1883efb345cefda8276c594b061a0b0f4a49d8bafa6fe251658ee76b2493cc00000000000000000000000000000000094f90cb386f7933b2ffcdba5e46e09cbd7d537c12bc223e76d3a88ce9063a7b3574d3306365d65dd4c6505f1dceea53300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f6571500000000000000000000000000000000169d9469c53e55768c9312680ee82ee581727e28cdb1d6fcdca25d0c03f3da2ad6572039f12c90b09cdb843bc506e07200000000000000000000000000000000174528257f6d3542f754ecbe97eeeea7d196ee4dd01852f6cbad87fbeb4dd7d3799588f17aad129a15549bb787468772000000000000000000000000000000000c9ab635bdaca1c488538c0830453ec6ab3b2b62447c03ff6ffd2712bf62e02a63c76c79d41644ea412e733128685c45000000000000000000000000000000000172ef0fda359bab149c8c04f583f4ace4d1b148426e993996d278f79ed2c6d3933d6cc5fb62ec4869aadc773d3084ca0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000017060fa73b58957d12b3996d67b7baa8b7f0943ad52e80e5c4f8830d33dc74c0a39e08594b647945b402299ca861f7b10000000000000000000000000000000001efdc7f783f9977392e2797a3e0bed222d5b661d056aa0c7e04a493bb9b18048bf72aded134941ece78d63df0a0868d0000000000000000000000000000000011355198320af05f2121939e6489f31e9e13b3cbb2cb30c9e675854cb8ec038f80aa2f4b6f995774b36f5f1b6a84298f00000000000000000000000000000000172e18c490d0cd5ba2449362c0ab296212dbe69ac25515d0f91941d300051320f067f946dcaf999554f55f1f616adc0f3685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae82060000000000000000000000000000000005854f4dba62d1dbbf3ae16f70792f1bb39f111309b454a6400d2916e619d4f70764ecfda7eae5c28cf1d178ad53fe6d000000000000000000000000000000000ed0bad1f5d69a0e621d137746a9ecc764931ab89f24ca827e0340ddc03571ed697f63e79cc58b946e8462099ce4b1d70000000000000000000000000000000011de76edd1cc2f9ba06b98593a24a7a011f2701b451ea3ccd04361ddb678e06d91a676e3f11b62c68cfc05242cb8a859000000000000000000000000000000000599726b5f5b93d414f9310383ed9414e4675d644f83ebaa63dceb2bddc7dcfcbc17c7aaaccd0ee32b0875952554b4e660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc25400000000000000000000000000000000031110347cbea2756b5fdd549d6c0b8f4036f5718d41dcd6c854a91c9df29bd464774be479d0efcb8a3f82cc7441a6c8000000000000000000000000000000000e24a52dccfdda3689c87395e45dbd46156676d9eb2cc09dab22ef7ff0acf5ea243ff117c82b147994d65aee8605b2fb000000000000000000000000000000000e0cd6ea0bffc591c13c48bca0782fecf8e128b0b842aecb06f803a223d32cc350db869b7a77f8e31b05f36bddd587ea00000000000000000000000000000000042ff4ab4596d610638ad23eea904a82701cdf61f9e2dc5832a70e11e717711a2d0e72f32f74706d385a9567426b4713addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001517efd853800946aa37868b525e58fb488bb69755ccec806afca2d21bd3a30ba46c39cdf694ad0ca92841760437c3c1000000000000000000000000000000000e5591c339e88544660380d6362f4119c5596f910d4ceb96ccd4c4d9672efc50805b6fedffa0a48d126aae69b241d3640000000000000000000000000000000010ea5babb0de734641f63eed2eba6124377b5c55e429987917c0bd109d7904766a10b0d2dd123413816d0fbabe25050b0000000000000000000000000000000000efc89ee2ffa56193129062ca55a3350bf50e8fc7d586fae3636a70e3577987fb0f8674d383def4b41225e490d3d81528416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae8000000000000000000000000000000000dae4277d62e3f3dfffb80818a5ba5c371a48d73b92d69a168ebab897ae8be206fdf776e9f955136d7f7f7b2903040270000000000000000000000000000000010ca635ee2e49cd6c951d75ffddd11557432726d26564239c611b139329a28812afe21f094c0585675f4f233233743050000000000000000000000000000000012378b2ec31119e508fd9ae0ccc4c2603b6820283284a278fe16864e5a18cf7992d850c1d6ebd1253103c219bd95ca4c0000000000000000000000000000000018cac4f0660240045214034cfd8a7e40bf0aa12f97a23c4e27db0e05bb25f4d755276a91a4e882a0be63437a522943ab78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000002fd5571c818322d207d58fe0a898a045a26c95c2490765dc9ac663a0de78ef5fbd05b20ea96dc5388d5b2ccf13a5e320000000000000000000000000000000006ff29ccb768da45061ba4e01c90459ededa5e79513917401e7e37151095ccd4656aeb9cb7c083cf27b69377295934cc000000000000000000000000000000000414d34eac47430495be735eb5c4b1a68372abeb43658f27613a9c8b78f17d9074174a8deeeebb1f9cda5d6198bdf89d0000000000000000000000000000000010b11bf63b8c39c1370e8fdbfdcd149fea88eaf1c0a94a51bdd061e4c41abc626a448030bf9ba880032e9f1642caabae871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce00000000000000000000000000000000023eaa08a44eebae674434b013ae9992c75690a3d0de53e4b05d1c0dff249feb24a12432bcb5defe25ee4e44a56b27eb000000000000000000000000000000000f146ac27e685cca04afe8fc58fe853825f5b0009e8831eb0d0121decec23b25bf8521da2fab1508a3ad8254865fbee70000000000000000000000000000000004af1a525d3c33e0b1629cbdb90c56a88d70a28037c87db81c59bcbc811c8f0b98aa9dd574436c9f600c0e8e2d194c0400000000000000000000000000000000170efb5e0e69e46a21ec3b972265bc04b9d5ee926254f61c0e18fed013922e00f1897cf69889576bb5d54810486e7f2776ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000111c832a96329d6db203fc8b6bb5b7db01521529c91c74d9cd71dc78d067b36cb7eabf1af80129a7a3f44b719235927400000000000000000000000000000000097339c17816795238629d4ca6c243a14e9e227e9bfc30370dbb9e1475f6d03020dc35559675121792436bacdf9eac4a000000000000000000000000000000000805870a1efd1fc34c9b576b77418ee8c0d36aa9caf9994a051e1d55b49275f34cdb55edc74ffc267c5776c8d0e113ed0000000000000000000000000000000001513afdfc2f000e3b725fcd0428fe72ab2413ff2aa91b44458a5249c9a160ee27bca01d2fc2e230f4a80454769961af95ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c4000000000000000000000000000000000485b3b1f812b4a28ac87d16f86d8d634e85d49d6dc460646e1224de662e906002c44a1a269c3bc011fd22afeb2d58df0000000000000000000000000000000013ba0752444a794cd00c99eceae51e61c382d0abb26e5e0e595d59321447400e8a8f7d97390bd217fb50bc22cef34b2300000000000000000000000000000000184515a36024d0bf71d9fa4cc5165363ff94ee9f8579bca653ebc0620a9d3146fba70a2f4a9f6bd3777101de0d32e327000000000000000000000000000000000e041422088c0343f7704e726d65ccc4216c4a1bde3668108983643663cf0249e992f9acde2dd8ff478dd26cd8d9434d06d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db0000000000000000000000000000000005bbb0c55fdbc59992c83fc0ff03f677e58b6de6f8649141d88963ebfead9383d692015a7b765b727eacb6de250351ad00000000000000000000000000000000183057eca610b8e07fffb60d21bf2eb87981e6e881bba04ceff420ca38228fce2f94d40a993e2aef09e209f3990dd14a000000000000000000000000000000001231bc55242bea6b589cedd1d82621fb71c606ca9306b268379dbf83ddb1420dea228ffc05cd8b67c38206f3f006ef18000000000000000000000000000000000f2c943e7a8b0ee00fc4e4ba912b94f68f504d2783babb90a3781b666b31bd161af2f97a77813eab9ebba76040b04155257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f09800000000000000000000000000000000054bd97b9cc979006f734ec433e215a4e8afe468e69173384bc895e10ead3749d991ff8ff203abff30bf5cc0d2fc8c6c00000000000000000000000000000000066b73a98d5f5ae140a5784c5594892c849aa7f2db3b5798643f755743d401ca745d810fad5f4a33e5b3cf0fd7d96f7b00000000000000000000000000000000007caea93ff5cc6ffc033717220a215ac4ed7283945ae77e62320a0bde13f2153dc8dd401297cd124b4c67a4f3839dfc00000000000000000000000000000000094568035ffff439e3d3201466f3a1d43414e3f6455627c5479c8b7c55130ccaa5007ace7ef6a2b3e2e5a4c9543dad9163d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc30000000000000000000000000000000015eeef8bcbfac04112931e186f6fd48b7a8ea891ab364ce8266c5fd15f072f08fb3655e324795df182a5ed1c917a5db000000000000000000000000000000000028916fcb3b30a7f95321a0998e544f9f4f578be7a9f866cf72d6b8baccd93f8935f105ed26aceebb3f9c96073a8be180000000000000000000000000000000012b11f356a7e32f3d9281a8999363aca0ae5c1a058724cefb51583e5f217257d47ca76d21e54ab62260796b95f9d3ad0000000000000000000000000000000000d83c75c36cc8dea4aab47823edd26b4492da39b93a15fa454aed4175f28a025ad2c576ef2d76a66e666bedae95cef1a7a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c,00000000000000000000000000000000059443f363ef0c65973d36469ac651eec6e52485a07a6d28112f4d0711802d182b7e6fc56d4f1aae51fe1c549247d885000000000000000000000000000000000d22118a6f1cd06ee14c63f0e005076bfb061bb85ed184b5444c08ed9dc35f77217b6daafeac89a973f2c73f00e0d3c800000000000000000000000000000000180430caa9917cbb40e3ada2de8d685b4daa99639669a643b8f5cf9a4a55d6162e9fd7f5d4989a1a6588feb0273669b90000000000000000000000000000000015d01fba1192f0f1acf8fb32fe790f0448c6563cf8ef5505d9378fa2fdd38bd99ba938077f71bb8eaa91a99e787e840b +0000000000000000000000000000000012d948b5268524659e29cd407dbbe8f529e608193ab9452f936b2f6fc0b81d3a63a0e929329e2d89b5475dc2d73ebd8a000000000000000000000000000000001219e20a081837f4d4e33bdffda08a946bb9cd876e42e2f561ebfd18ec439e0104b43de61f47b8b7a0c346c33e632be60000000000000000000000000000000000a135c72c45f254cc1c260af803e14cd0f89c2ac3029629a86b05acd3440465aafa4cf84e69551ae772bb55802a90ef00000000000000000000000000000000052750c3a99974f9044531dee9129110b99572cf283b61e6606f1137a87de7344bf01d2ac2f8a1db8d815b6d9e7511fa26a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c00000000000000000000000000000000136b2f21aba94bbc8e5235951b1b186fd4ad221e6ecbea5c7279cc8ee8b01edecedddf48cca47624ee9b155a4c167f140000000000000000000000000000000019852d2bc9c8abc92503f3e7eec9fb20df108c23643ba8a2fe16c2cf085bb4ac079d3f065a1241067daaf401b662288b00000000000000000000000000000000018bf1a4e74ac9507b97a990f3a41cbae3f32e263e9937a8a62679bee93296ee5cd25110833eb5d136425bae0e9dcb8100000000000000000000000000000000096ae4bfaaf4f18d3e987d9f287fdd3dc9b497cc84867e757da52bd5f58688403e1c9cb432a2eb87e239879d52990ab5f1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b60000000000000000000000000000000016ebc2ee18515354b7af5d924c895ffd5556ad088560f89c59a4ceec229279d4075f732b884a6ef2bb2eddc11d27572500000000000000000000000000000000110282084ab6f3e76eeb9e5e8c56749992913c2404b003df9c2d01d72751f879538d23f612c8faabbccff45185f4c6a40000000000000000000000000000000017476677ebf052d13f60ac0ec5e572c398f1a478d60ce92a3de88a74a28688d786d30b1ea8008409e45697db0adc628c000000000000000000000000000000000a5e4239d938bfc7c05f3b3a850ebd5f7784eee7aca48c861eb4bdb1ce6321fc9c6bba997e143aba13a42f69ea14937397fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d670000000000000000000000000000000010c703e31f2d488812a387596c797d8d414e406bd82f238cea50a459d842502e11220ad82fce5dd36635792ff5770bc50000000000000000000000000000000010c11caa640708850e1dddd48bae22961a45029971d823b53030979b7d8ef2eaf2ed055436105697c5b0b31b1a9d0a7a0000000000000000000000000000000006b98568b2b7f0aada97310f7e14084a14bffe580ec65bc8fe5d19c6213c45dc1b8e1da5c6c1b8555729f6c781575278000000000000000000000000000000000f2c506f3e41c28a748656d1dfd87e812b3ba21815637e497a30eca4fc5de18257846f12b67919dd2d739477cf5ed0ae8bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af400000000000000000000000000000000102b6d561172adc9316b3ec11f05e66e7affb1bdc70a364faffa57aa5938c2ac08863be8fe79ce3f627558fcb2ab1230000000000000000000000000000000000c5e72c271a1ee186d443a96d53f0ba0ce226c76aff2a7c3215c2110f96cb3301bd586f509edc45cd20e662756897b78000000000000000000000000000000000d546fbf485bb283a04fa05aa962ae8d77ec4d26f749d83b208f77247778e32a9a2f1483bd84488806e27b13eabf41d30000000000000000000000000000000005a42c6ce8d43d122bbf984e9777f5d1c15057f27e70fef44b97c2c6e7e2e303fcbad643027b7ff3167916f21a723ea98da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000001788323aafb95f8761f87f771fa05a8e49be71e397849daef5877a7f486af13fa651be7a93bdd9465df7be4ff65825fd0000000000000000000000000000000014b7a56f3f7c12e39be76b3872c1ee648f62f9cb6a1842d869e00a5dc2ac8cb4ecd96ec2483d5eade5b0f9113133bb050000000000000000000000000000000009a30623632b757ae8d03ced0c1fdd1877718f8d84f34ebb42426284f73bb7e8abc31a5e5ded57a02d08adaa90abfb2600000000000000000000000000000000020b47acafefce7f617081e22b2bfc566acec6d2cad5063a79cf33e02cc8931bb698b72184a11fab73e0bb0aaec76c61d6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000d1190466f0e8f03d2cac4a5e63a13d7c6d0cac9f2065295e2de818773199d731f8cb7b2be5f6ef0a246401b345a2d560000000000000000000000000000000007d9c5d187494df79c25b6292527b0d6d8c50b6467bf76a1a1316556e48159a3b5dbdbd9fb0bb901d857f61f423d15db0000000000000000000000000000000013e4401fe76e3f1ef73bd244189cdc81fcc152f71449c11aab24c4fa1d123c5aa8c68a2d10fe88c1c6631778dc0bcd420000000000000000000000000000000004ccffb4296883b8690b2f3fe17e4e9ab24390084ac917ed28fa1e04b9758373abd348290d24c915dfcaf0649ddf5a87e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b10000000000000000000000000000000000f4baa5e531ae462b95362292d5366daa89f2fb2707c58568c094c58578e84a8d253fe1de26b917b84635c0aac3a63300000000000000000000000000000000109057e5c5451eb9f85b95aa5ed2615d2faccd0539b1e4481923e04cbdbd2ea9290969022cfa508d3fd050549c74940d0000000000000000000000000000000001c3e147ad9c31927207f2344fedd541316f4010e3de194f924c4a1450a221285b76ff1894f8b1670731007f44965100000000000000000000000000000000000909cdf5c56dc177daa1f3fd7cc31d79a4f6dfcd462c07812cdf629426b75bdaa297b9d7e67aefdbb58175a21e29edada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000009b4c6bd1c460d2e93febfe523c1d54d6bf6af50838e7a10b732c1be8748a0752a517e7103d0ffa4507b086626fbfa8a0000000000000000000000000000000015bf2c13891dfa8dba35b5da1235563d4ee1dac33e89006f5c9fcf06f2fef7b31ca845bcaa8ac608046e8b01c8a61fd2000000000000000000000000000000001898dfd6a0618df821474b90542f261c1febbf2e566978b0fafca44f6dadc57202f88366b19d2c955e4291ac21beab520000000000000000000000000000000019287e1ac6b3eaf412e58511b40d87558e7cbf90dc8af2f5d33825b40fd2f2425d0be3a05d0a49076f4114350dcc601eb929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf0000000000000000000000000000000000c0993401c024d32cecc0d86d4cc52c200e59acb34fee2ae052837f467905e736a1118260ee12a963ca2df6e1a6c9d0a000000000000000000000000000000000103f78f0e7c9a5628a66efa91f150a87e67623ded2560aef278a8caab017fdcf181981952b450c67e3b4d3f362822a80000000000000000000000000000000000df01ff335f23652f1c34480d23c62d705572321c0e7fe92556e033dd3cf5b78a3d554585403a7f3c71744c20d17579000000000000000000000000000000000a0e2c9e2e34e5cb36e96b29231f702abb127a011c7ea3e21d59e5c55f745a02039a68d59ce8e29afac0752d1939106936999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f000000000000000000000000000000000eabff0e6ed9dc358881796441c48e722ea171f26011ab898c5a06758f61a629ae21d5a2595a22dc9855fd2e516b30fe0000000000000000000000000000000002732155a7a2791078dedfedfd3381281554c389bf9b5baa47593153a2acfd22a08557d7a1d49be298e416051b9137dd00000000000000000000000000000000116faa2e2a261e6a3e4de6ad80d75ee05aebae47872e2eed9cd91aafb94a706de673a05f1b86c0b0131cf148a90b2b7900000000000000000000000000000000009a04c09c2a4fce22d237bbe930392dfbbe5c82d480abefbb3be876015e2f5889a0922df6d00d4e94be0e9fb8d2f4a1fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000002a6402848507062e5c5d63b1207a1a41d3b941d21792391f2feff95035f1b4625541770fa5e0f87585cfca670976533000000000000000000000000000000000904095ce640605c957715e378ed733ddf1f94d3beb63543a50c8922ab9f8092755fcc65e2a1ed9232c8cddcb5816371000000000000000000000000000000000ec62b911b08d3e8618880c3784685b2c6cbb07a4aa4e348ab72e4f918152622ddd7748bfcd79f35675cb956d11fcd650000000000000000000000000000000013f651e9104d48a081cef2ae0648816b2b4b5f644a791514e94a8e3dd3001099c27d1f9860337ced1b177b4ad7cd5866c36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000016334abef2a21b9c1926b2086075471bc2d2d2f66b963a41623af91fd2fd50f254c008fa3bad6b53658c2486edcc94aa000000000000000000000000000000001063002a5d17aab2bbb5da49e8bde63a1f3c4dcbc8800f9487f47c6d707109c86d3cf7f9171643418b195e50d7483af4000000000000000000000000000000001213004f31fdd0b0df5d8e3677c4f48624691e2534c02881c6cc6875b9abaee56ed5739c2acd66cb1b10553ba066ef1a000000000000000000000000000000000fb7659081cfcf8beaed9c1daf9e92702977c37a54376597d897082a25f9882f1ae14e7724c0aeb9e002dee708c6b4eb0f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e400000000000000000000000000000000078f06bdfcbc7c0cc491fdc8069314c8a395983f9a2e5c2d1bec360f36e365da377885f897d8d711e33270e3ef9dc4d80000000000000000000000000000000007d43394d5175e020b3a5d768b60ec763d60cb1bb37c0343930fa82e92fb1becde0a178c4565df320824bdadd54ecabb0000000000000000000000000000000012f9fc96355721c35a6f5439065d89cfca5345622b3f38041b41c036b9bc6bcc980498ddc7bcf807e1b97831c099505300000000000000000000000000000000105307b482467b881a59eda1434e31dffdea531603fd3c460aa8d4f58d32668228bfa585bbba2dae7346141af59190e2c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba000000000000000000000000000000001576521fb3be8c3178549969e54bb17b0a3546ac4aacb470e935359e36bea4f43dacc06c151a527f441ab9616e07f7b90000000000000000000000000000000018dff940a21768ee9b9450fee7259663bb29af645bda2acb4d43f4e9d631e0127073f2db04293266e6fd6fd3d005e3f0000000000000000000000000000000000ca6a977016c1ebf52827a5ad52e5efcf7517ccc3ff40df8141f6335fb6c77c3fb8f6b0dcdba2596ded7c3838577e28000000000000000000000000000000000150cc33b55586fac30d316cad6580cee0a070900fe7d540167560b79f4cf9690a5e02cfce9946cf67a95dedc9a7d9aa35398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc,000000000000000000000000000000000adf84ea7681c442731be8db9508a050d4689c42af8f7472491655356a86fd9a0afd91435bdbaee98db3b1d8f26203fe00000000000000000000000000000000090a7dadc0a14df481e44e2005c9ddc6e026ce2afaba7badd18492bd3a338dffc349b4a339f56221eb795314252d53640000000000000000000000000000000007390fbc06077cd167f53a16f347eaf50ce8c9d111afeabf3a672687441b80e66a66ba3fdb28e2ca8282f3ae3dc81be80000000000000000000000000000000001998f98e85285a428a2860c22a00d0b317854da4190dcb4dcd182ac249e13c51f5d5df5db6a0fd61d01232cbcacd5a1 +000000000000000000000000000000000c868a2cce65692f83eedbfeef6f9823ae9382fa5ed23395ff2444807e72403d4f6ac861ecd3a51db841889fe22a033700000000000000000000000000000000111c9aa53da85a63ce1870b963415f0d5f812e061aa6bff57425038d1b65fff57a78bdb963bf2450001525a93011a28e0000000000000000000000000000000011770810c16367d075c695981dfa69b072b82b034f8ac371f26bb157f9f9d667aa555a5c6baca69d08f421cd569faec2000000000000000000000000000000000df6146b29bc8226dccfc95a325d791b30cba8ff2495434d75622b170a634ec7995c5b4c689c73582ca861dd21d8e1e49f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000000fb30aac6502ecdd3544f1879bf1b3f4c19fb897de6c3a7cbf08f36244aa8e9dea8aaf781f7509d3ece16ca144a601e40000000000000000000000000000000012304be931a1d7440d67740f50b1a281468b412e8b6c54c62b993ec609012c7056fc7e62405c7530e8f5136cacb5926f00000000000000000000000000000000182320f5d9211c08f3ba5d40ccca45cb0060a6d362b4422084617b9d8212e94a9b878294ac176b8f0e959bc124a753310000000000000000000000000000000010be6678910072ed9f932ab01a2d72f7374a2cc82bbd86a6006a495272aa89fd655e6719ab8b3a0643d002021f7b7ebb4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000f1ffed9514ee81e9b3fef4162c8f4980fe0429e57bbc224a9c9976cef7d26ab61ea7b0cd42eda30da97e3f8f5ab5f0600000000000000000000000000000000035b9b349b531d85361a4618a172b510dbc924df671b3fa707b474d0d8b17d30dc8ed208d66be91dcb7632d2f05ce31d00000000000000000000000000000000010030dcf6695d44ad3236032e47f7aa25b9f55869f5207e7ac8641db8c01f5b59627dd3442a1834b8b1fc595e47cdcb000000000000000000000000000000000f91ad5c923572a75d32962567e7b1b0eb84a91d485c968b5aebf8b3a772c2f94e47bc1d5b333fe43574308a78e768ac7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000000ae11c60537bbcfa46a08cbc219122ed66fd0d42f90e68243c32010eb99942554c349c021f0e3635bb50f7ca3d106a3f0000000000000000000000000000000019a61254aaa5b51b4d354f444706ebb0bc3edb87ec2d83e830ffe0282bcaa3278e947d053d6678549a098129bace43da000000000000000000000000000000001100f48a07456f01e16bcc833ae0a2835c964e9b0aa850574dfd8b4a7f06d03059e9b4df8931740ce0621ec7eb31218400000000000000000000000000000000003072392a824c386859735e2d203c9d52c19796ccf8538bda3b1436b2f6815bc86d05287f29fd0bb0569a81a57f0c22a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000392760f98883f9cf6c0f0a324b9a645cbae12b780896f6a3eee918c44a815daed156248d6afb25901521b323f6baa240000000000000000000000000000000006375c6629f30b7a36785269d691772afe1b95d6e1bfaaba9459c31086c2697e4ce77d148fe2ea166cc330373583f4730000000000000000000000000000000000aa8e338df7eac5a7b070a69d3ed1553a0c52fcd894c2bc8d1b8cf6ed38983c6c392a9a045ffe8ff40b39d18e7c87c9000000000000000000000000000000000cbc73b589cba1bd47161282642fe6f51f2b3edcdcad6020bdaef369d3f2c11ea9cafb9a7fdccfb89bbbe13560d42d1d97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d0000000000000000000000000000000000e71692cc2342d1e93e0ce72be69013023d012dd2294249dfd69e1d610e2236ee2cdef22446f1996bd3309825989930700000000000000000000000000000000013a1bbd3237dcbe44e05234f7e41982f4fd951d3741a3e90345418af1c922d35edf776a27bfbeaf7a15658db67164bc000000000000000000000000000000001197a2ee5c2541e19b5368c97abf51fde3dd0b922c3d701d7d84552c9f47b38ca09a8aef8240abfdcb03292ade1ff04c0000000000000000000000000000000010ca3c22ff8a47b1c683a58086ed9d831a5c25b6ce5a1971989974b4760cc9e83a1bc8d819825989751405b242eba379efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000001306f8047ba1a3417e7993bba0dfee9077eabfc275af91d0b882a53199874e0777d8dfd29767186d922d49087fff38b20000000000000000000000000000000005371b760380a6d287e129b329e735413447969eb9048def44f5c5987a64323d2a5c81484c40b20206832b86a4af9c4d000000000000000000000000000000001552eeae620c42d0bc4593d7c8e2c8fb4d6dbfcdde68d57158a7dfe837a1870a73b45a97b02abdea174a475a7061331400000000000000000000000000000000033a6dec61540a5cd5773b76847dc5016b309c5a027639598f51ae5b1067b3f7a02f5ea11b0e1be77a3ac236cba15c929a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d4520000000000000000000000000000000012ad5ff49459fd3a7940a69e2a78919876e9b3a4f0c142499e7b5dbcadb5c2b5d79c5dea972f0f0acdfd10ac53bcdd92000000000000000000000000000000000ec1be9cb379bf1e24bd5429a4a91857bc3ad45095d15bc5537c2ba39407e9f2edc5fbf711ef4287a73ea466d4f53c3800000000000000000000000000000000173605df66aaf51810793db1cf2021de6a7645ae84a5d439ee035b917d037d9f9ff072b5dfe8b9ac69feab60fe2d70bb000000000000000000000000000000000d0bd336825381ae1e18ca37bf6160ae32b653ec9f9dad159006e92c24b661f22b5629ba323e9e06ccc5887a962ec23fe9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000000f5e593c6588add92cac2c9467247fc6d900f20b4d3216c258f88f3334eecaccbf3eacda227e2da46cf520e5102a9cdd000000000000000000000000000000000458177ad6c190222e53e054546413c13216286d414e3509b7dc794dc0704afd26bae93ff630c6157d05d46d805a04470000000000000000000000000000000015df8a7720d389e6112707e37694afac2f97282676a89964deabefddbb3a0f1cbc885d4c875b945b8303c1ed2c0f46b8000000000000000000000000000000000e3c7f1af7cf5923dccfc1d25bd86088706a3a44f5fa7f97171228e8f2a2b18e9631b2a63bd5a75ee0bb83fcc91a45c30a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d40000000000000000000000000000000017823fc8a56e6e5cb9924037ad6ad1b43237894a877572dfe3d3cdc1120fe83e01de112b55f7f334dcb5c6247c210613000000000000000000000000000000000daa01f90cd14d82d4fc40b60b463089fc6c0e567fa46bae69184d0e3cc5acdb1d759e3291e2781fe0b65c734ddde28700000000000000000000000000000000164e742b123c19e52e2d7a6727689181f323990a3f3238072f7cfd7fc0f55b7be4274c0df194d85060a81f3744d3978b0000000000000000000000000000000007c03a1678b6e91c1bfc66ce8fd419cea13c7cda3213856ad21823b06db94538153a15d43a9d4270edf77b9a5ed490e6cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb000000000000000000000000000000001865bc91e645e2e24c3efa3afab8b0e278dcf16b29831f75b3eef0b342479e997b9c5f8ccf67c789c830609b3cc425400000000000000000000000000000000018dda7857f919a6a49f6bb465c27342c8fab6afe6350c43b98e91a3105276f3ac27268454e9a9c6dafeb2218ddc7d3cc000000000000000000000000000000000b098258ff8b185a5c59b46150954d52db5a5f68bc7975234491406131e4f1286ce79156dd1290aafe688f936ad34e31000000000000000000000000000000000b294e9ce904fb9e243d0790147b6070b10ff611a06e3f639aacb744154d02016ac08f6769732d4f6944ce9257680d49e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac5768000000000000000000000000000000000e541a22a7a36adc06e445f42497596e1017a1d99de85bb945a195cb3cf0c14d39eb7a2aa994cf234eed77f6307cf6410000000000000000000000000000000002de753e41a16565e5ab1b61debdad54950e9930e04badc6e356f10711d7688befc6827040356c0f0a8ce4f8d7121b3a000000000000000000000000000000000f2202e34ca164f1a6c0afbe179b714b303d87ef14534fe3f4230180f709dc63af17f04487264b3dee6b24ec4d0a423f00000000000000000000000000000000004044d9e3b3a77d6a309780c870a65e05e1ac531c5420f6ed0056f5e728e2b83a968ca90d579db50c2dd395f7e40beaf26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac5794600000000000000000000000000000000166d7692fd30dcd06b9f01ba2101870ed347840509b3242f7cecf91fbed91abc24b08b08cc39c508e6499a2f8bc3637700000000000000000000000000000000076ce6dcbc77812b4d5b44a50edba5a082cc36dc24a5cc348283a4ce1518198b56134c9807ef850edc9e36e9a282b9ff000000000000000000000000000000001261d9412245abd7ba3fc1597f34179e54766c49306725d42588545e14f4e450ee1c7af913ad7225275c57680c23aa6300000000000000000000000000000000096602b4eee053998555ce522c060d5e04c7961eeaab0145d38c9b13362624f54fcc8d0b77f2bbaf8c312a3279f06e4eccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f940000000000000000000000000000000012be34c18145aac51a1494f4052edbeff14c2812ff494cb78198cd7d9db9e951aea80490c55c4ed926f6a96a2c337c880000000000000000000000000000000000536e46a63ec5ac0f2f4eaaad6df98322c6a981cf2fc8ef253269cef20a76ba1ad089c24cba4ad4680dc4192d66595d0000000000000000000000000000000005363b9acb66ee95713b63dad076529805c0dd8921c738e205e7b1d0410a3ecca0870aeb2e64cf45270d49b473371ddd0000000000000000000000000000000016749b2b09d889b883b6fdaf518345d4cf097a728b833e92c4d21b5c41c8d5cfc0758e895b60ad101a74bbb6be6ca0c5fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb34000000000000000000000000000000000067a684b55fdeea39a29252b355700a4810f083909cf2c07a80b362ac1b4d58f5900c68d266f7ad81ea278c0931bc1ec0000000000000000000000000000000001b1f78d194d77cfb4a2116ce9e29438dbf38c52733b0295198159d7cadb2584d86a75c24aedeb36234a0becf9d38a870000000000000000000000000000000011fced2244cd959872a25c0c7bb4af6151d99e1aac079c606db4987b9ba111261d4a16e7d82362b865324824445a946f0000000000000000000000000000000002659e7016ad615ed80ea1ae020903431b470bc0341f8e0918de9b8d2e933dd9f2d9123e9e9d20bfb05d49f71c3c454cd94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000008f3c5de8c94a98dc5ad7846c53980384f997d1657f7349ad9b51376d41f4b21861d212fb6428bcf2347d8774f44156d00000000000000000000000000000000110b245b1e788da41dcbf60a3ac4987c1925696dfca85d450107f654fa1230adb9436d60c9e742dfb4e453ec4944c56c0000000000000000000000000000000011043b975e01df36a36307ba9234a18b97aadb9da509513b13e4f3c80432b0cc5e69a3bbb3cbab8df41bbcc92cdbf60200000000000000000000000000000000120aebda10c52a67d23842e2bd9a897cf38c58fcd11e4e8c5614db5e409a7c03111feebfe2f1212ae753497dc59d6ae9122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2,00000000000000000000000000000000021067690e6e001e3d0d01449a7257348c4ef68e66dd47b9014d7687d44749be1f33e6be95df6a204169ab7103dc2d3c00000000000000000000000000000000062efa0c36462ab0734128dab5da8635705bd1e1b540817c5805ed9417f176723eea92425db539e8763b1c79b9923e9700000000000000000000000000000000176c9af1970f026bcfa87e6f85a20ed498c28c6982e21bc050cdc29c0f0af832ed4424082e4862d985b78519cfa75b820000000000000000000000000000000018718b0d0fbdf4783cd0b01524ab153b891fbf08cad60241a3f3163d2c3496c36afdc6de62ab3c9a037f88ee408ce5f6 +0000000000000000000000000000000018adf92d8da050c76118a3a3b2ee43955ae8b14ddc8ed64f5672f40de475f7e0ba6ff60c4b6ca3e863d7914e6de2cc330000000000000000000000000000000013d1e19011a1ea90389480d14fa608985d895e05edd9c28fb34646f70fd7bdb7857fa785b1e3c8a2997da6c3b5337ccf0000000000000000000000000000000015764827d9838c2b011660230ef9805af388fd997cc229c939bc5f4213d517dd837328c45b0b8ee1d6508cb70629b7bb000000000000000000000000000000000d58fa30a2d095ee8d946e50a027ac4cfdd557b3fd9c82dbf1536ddc0f42491a176ecbdb026306e6ebf1bb182a4e8199b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb000000000000000000000000000000000cacfc8d0bc6f9db737c8a316043a6b52fd5946937467afc09ddd14e509a89f2445065ac8a8c56454d529d67793edb0400000000000000000000000000000000148b1b941f159d93170fed949d5f53bdd2603d78a49443ac0e2353130ff914376e018c3db3d12b807d105f2d50eded8c000000000000000000000000000000001382a3e98cfd072807214479900a8602bd666cac7f19be0443ba1354bfc05666f40384e9ccac314b5d0a2bec1c90ef0c000000000000000000000000000000000c12c2222f67a5adba78f2c0be5be95ed743e835857f4204cf47b67fa2eac45cd5985fd82c7a3904944e7b84737374b17e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d31000000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000137b12f731ec925dc51e20a9c90323d14e1497e16b3a4b5651135054ef0e58e9b18167da15220b9a4f7d81e9a7648fc20000000000000000000000000000000000b2d3ac534e1e5b2c9ff4092c2d8dc5efd99121de7df953e5426eb33934ef07e41b196eca50f5a04a936881a05f2b2a0000000000000000000000000000000004feae2377d950717695606844a4873ed7b5f6703d7a63dc8b960b99b68efbba710c2db0f1371acbee314875b97ca054000000000000000000000000000000000f49ce3061e7254dc1bc8af3636a05e098cb96d81fb31e25da97c6266adf3c41a74d46ff32f4fbdb4cb7e4a3f69e827bb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a00000000000000000000000000000000064ae3fd67250f2c6332e1e149ec09946147e12e0d871403e559b49aab68190a1454b3ae924727b6dcf6e1ab327c9d7c000000000000000000000000000000001131f91c7a0e1854bba3958b36083c27904cfbdb8b8cb3fe68cf578bd1cb6f7c6eff91d98e4b99086926c5d4272cc1f200000000000000000000000000000000071c6a92a8d460ff72d172c204c8a69d6b6752b8c1f731ec63f7f394c0c3a2a1bc15e865172f693f523c11cd4ab1f88e000000000000000000000000000000001193876df7f4a1cc9b337a41c9faebac2f209b9070bd75294c2a88d3091a1e55b51fad482fd2aee8f90458eeb7e981fb8902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf2062800000000000000000000000000000000025c20ed5572dd1c9a098f241d2965d8739878ddc57c017632afcf6e54964894adbd6d30f62f316c9c3ec7a08268afc70000000000000000000000000000000013bc3e930f4fd5766db8f04e1ebfaab2b67f620119c39d687c68619b3564f3e8b74666c9f8bed6c1f080a9e23e9c0f22000000000000000000000000000000000973a3cf19312f90843f1f013b05484064032557807ca67b2ded4a27fdac12d6cd0e1416c8998cc8635ce10046adfbb900000000000000000000000000000000108903617c78fc608eaf007aa13861c970557f2693b24e8a278920897be9694570ae6e6c7749c3eab84d5fa3af5164b1426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000005695975c140fa14998e5916268bde2135cda80a45414fa85193fd6e13c6b5a6486898f590d76175d8ec2629c923e33600000000000000000000000000000000033f58b1cf67e51e9ad817b31919530cfdb5db5ca4a537d9b006b63399da49b2a5077bf5c3b3b4fb10b2478f466542540000000000000000000000000000000015c532e40ec04d9143e308895b2e7e3d3daee093a5840e1e76ab528fcfa5be57d9796ffd58ad5ab7df6f88aaf34706f2000000000000000000000000000000000b55747d1e8b66e2b2fea67229f2b7b17d58ef547ca841bea8db5b53fafaa18390f11b8170c41a5dd29331917fa2e348de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad00000000000000000000000000000000001ee5ebf73bb40a5c0822350853bb5aeead3262380dc274faba6b04e58e7fb9d5a4ace109ffa5011e73e3d89ee6fd77000000000000000000000000000000001427659e5ab1f8b47edddd27c613b578890d4c66c835c0cf8e8daf19d0ae842f0bba5bc83ed7248adcd75cea5d222a270000000000000000000000000000000001d4560185690ac05e56c2d629d599bceee3ed2919c29e3d1ac54e80ae99b5eb2f93bab865e8c1eef7206f96b2bf4eb20000000000000000000000000000000016ecd3589e3703e5b0ef53790130d5074d2bc0fd5839d9c6ff905746a77e393f73edf53b98b99d9c87a1fee1086aa8657431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb600000000000000000000000000000000163850016261f34de2b831a0a8dd3f224adaa3cc279cdb40e0ae976bbf736dec26c55a6c79cb1c623870b62ea216274b000000000000000000000000000000000a79af5c054cd08608d4be1705058ef7b4ec38a8727560d960f0325d0ef915c049a89e76956d0296bcb6c96333c3470c000000000000000000000000000000000ca89379e558c7308edd25bf06dc05db857204e9351299ab66bf050c8f051341a6c15a02864c679f07373038de3fe87c000000000000000000000000000000001929f42ee5d9dbfd1f6656f61e6243ebf0eb491762b7f3608db3f3e9abf565ab1524f770cd2ade334885d7479342c92c6745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e00000000000000000000000000000000193b6cf7300e47ecd21a05a71b13a8de45418d3f67931789ce6111b8633b9f44063ca13ba8c8a598ee0725caaa3f277a0000000000000000000000000000000016884d982e2ec0fa7e59fb34ae8708d0bf4abfc260837ef4432e8e04474e504b85450db8af8e6809413c90268801fb3b000000000000000000000000000000000fb48a8331f278845979beb8cd21060355566af215ba44029455a03d0c016daf0f6b7c5773d1a99e893e76b4411a53c70000000000000000000000000000000007056e30143058eaea89a3065e1de768d49860b170d4c364a28d38475f90711fba62c1787adda90dd2d347da72680f4eed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000501fa9af88e28d4f0c0590a2624239bf1724ac7174b0f1d5fd7527cff1de9971d6aaf28ba4005e88e181daffee6b20f0000000000000000000000000000000007af5e30b5aa9ad206645ace12cb2b36cc1c6068e604184ca8bfaac5a4ca327f7c43a74d43417918da7df84e3bffd282000000000000000000000000000000000bfc0538d52f277d54749ed0b69697b4c60ef0c5483d21dda76533e15efedc9e2b2ef07618457d64bae8ef922c0b41f600000000000000000000000000000000048935cd352e999bffa613e3be0a9f9a063d5b5eb46cb5056e41ba214e87f871f216ff41ee297aaaf2994a7b6433f58d81633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000016ab1e8b6f41891e0b65f14397c0887b27ff27e7463333e0938a7a1a181dec603056afbefdb23b41bbfb2c05807289b8000000000000000000000000000000000980d0ea9ad5c87bbe1aefb708061f85faae1e1e3b01c55bd577631e5bea2b5ffaf5e2478f5a8df89447fb8a73559729000000000000000000000000000000000784d0c5fa243bf0125cb2c83a4040715197e99d507d71a3bd9ca396074cfda652c1ad0dd95c3cfae369e68d3431ee7c000000000000000000000000000000000e533bb33e6d269dfdeedf7d17c3e0c19f694d151e8eef801c326cbcbc463a42558f58cbc330bdff0d8d91e2974eb4cfc6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000050f005b00f371a7308b5d7d7f67f7c00bf15acc518942607f32686feab5eb503391f964eb7ca711aa6c7b4e494d7eba000000000000000000000000000000000e2ee5092170ea3da0b1397023b2386c65ec8b090484353f2e5d64694aaeb8d5410ae22c92662fcfa21566d70173ef36000000000000000000000000000000001549723160fc7b8f5ef9a84bd1803f18b76698aa7a663d9c107c9ff6c6d02894edc80fd00d436f3a942c05593c5464ad000000000000000000000000000000001032f49e3527cc1f1355c65edb21220c6afc88919ff67ba99c65645cd3b8ca6662dd0146f6a90d92558b3f54815a361d6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000170b317e49f1304570a3a3e6bef78fcf8537a451ebcfef5afe3eac4aa1aa87dbf95d0f870fd3372d37efc9e663621cf7000000000000000000000000000000000269ae0677d71b2537078e96d2593482e4d41b6d1d2cbec755f307735faaf79c01fa27f1103cdfae1a9bdcb665f592c9000000000000000000000000000000000b115d5a9fb9fd9361d0573a8d68c5193f02edc1cf3fecf004c6603f118f28ff394220f6a9e1051a5d9d4b417290b7f800000000000000000000000000000000107b45614b18c2513f8c42a0032cf0f3f300157b39d2969ef7b126f17a9b5e8e9ecc5a61a2ed4db92134b0797f6a0ea35906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000003de39b056f8f0248b138437db1536b7bfee29af00c37fcd14c25c88f0f051eaa07c763d94c8ce497696311736c0b7140000000000000000000000000000000002b52981e828f8dc1cd371e6821d001e1f96d57a865a3c0a255298c43d52741b18fc60903d1a5ef6227061dcb243096c0000000000000000000000000000000016b5335f0f9516f52f2ed45fe723ded427206ba96af0879958f1f22795485b2867e953de3d9b3a9eed2c37f26838e1540000000000000000000000000000000004c860058c7ea2e6e4eb2a65c1dfc20b3070f89ff58ab99bb51a4eb9e7f0642f7b32d1d9f27c668a36a9e053a8d585f394ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000019cbbc125ca1b89330c21ef5b42fe0dc1e795271ce4a9ecabff04eec9029f756f180520f0e7b84be2e9fa4af395536ab000000000000000000000000000000001630cf0c4f3282689a3e01b5c8f9be3803f60238bbe9fecbb0d9e8e49f4ec9f6123c44840acb8cf55f8f6bd15579e6830000000000000000000000000000000012afb848bc0ade8f0c25c6c342bb651a7481be065a48944bbedbc14c095af8a4a048fd1e776126e2128f904afbcb17ff000000000000000000000000000000000dbc984f9ff907ce5553bb11a458deaaee0efea49d6816ed7abf1dee7b70cb18cc669d4808e75678bb898359c7ebedbe6feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438,000000000000000000000000000000000be6dee62b8c85e36a216d16c5477a7c58f03b992277af83d9b53b3b2169414b72bcb4a97e3667482e888738ff17c94900000000000000000000000000000000067337c69c37ef6f0ae59fddb84c46a2afe7fe047ddb57b3b80437609f1a21fa5a73420fa5b44704ca1cac6c7a99d9320000000000000000000000000000000017fe6f37d2410159e533374ff3812714dcd07610d75a53a5d502cf2f51e750c48858db1e109f6aaf724292c1402382f1000000000000000000000000000000000b8ecfe1f5f5d95777b0fe5d94fe81b82656e6e5a62b7591788baccd251d93e4bbc6857cc87cfe6b4ed470c33631ae22 +00000000000000000000000000000000126d4a9ae3550e31185aac9011e3f086517cf79a279326c264f51bee6615dbcc730d78055489b5602e91b08f96d23882000000000000000000000000000000000aeff5fc04fd06c26af8b048fb2d0d493525ba5c2bde30664e7371812d529ec7dbd584c056b05fe02179b7eefbbc45fe0000000000000000000000000000000017c6538d2801947cbb646d4ec8b70b1e24453f7a984db7ba73e3a5dcf595bdbad9703f2d846ab02491e5e3a5bcee0762000000000000000000000000000000000badf551dbedcefbe7c303a5c8a52151b5460caa22004028893af4d8a3fac30cb1da1e986f9124acd5db7a634657dbd0cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000bed71c7d878e7ecccd8233e3e604e564cba0b1ce75f726f846f3a6e2f3b4f5b12a28b8638be647f5c33226edc2bc7fe000000000000000000000000000000001914c20aabaf1f6f82063223053809622ad82a3a54668bd600db1aafba22aeee5c8a07584e263c91cb0fc5fb809da63d00000000000000000000000000000000056d9cd8f79a90d16b36bde77e546f8b3064ba7dd0fde78d6bc538bd6ce12a4f32860205d5d396bab3d70deaaaccf9450000000000000000000000000000000012f7e420708b66132157a80753678de292998cb6c4f00244d3c47a6077b3401132b73c7f52369aa2a6a90892f7be4ed913a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b000000000000000000000000000000000339aa1471eddee8cc0a4e4db5a29c3e4e92cfbabe023995a79624614aca522cd459dfacc0cab346b1cedac347e1df100000000000000000000000000000000016cc4ee8cb72fe09e65616fbe9bea1a0077114ca841ae335f1f9eb5a0b129a4bdc77cc6dae8727d74fe21f0d870a43f2000000000000000000000000000000000098a21da6e983228ebbed0ec3704c9d2521e935506c0567e3bbf9b9c379ce6d33c3d0dd8f5e013b431f740964db634b000000000000000000000000000000000a7a38abe8e282544ec6c8740dce8559fd264393d0a5c9af9813b2430bdb92b3150eacb6732b9cc278d0d0e622b263ecace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b7740000000000000000000000000000000019ed305bfe8d8bfcc20794832b3c117715b6a658c0bfeb629e5989f265cbb456e857e53d168932589e4ed2806db7c4b4000000000000000000000000000000000e2ffda25fc316a38f556b35a7a3acb1a2bfbc1f9469a1b6427ed1f216e113a379932b0547f5370be1017a1fa0266cfa000000000000000000000000000000000ebc493c9a79b8ba58f48b90b9d287c74f505dcb484eabda79ada987d63a4df04d671d4c4ae4b32f8ad5db6a1b80f37f0000000000000000000000000000000019fc715d26c0c7a0c291ad8319e2e8f2920c63b4d4ed3f0e2f376aeddd4f7bd9269175ac8d0f421b001e2e48634f3f238d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f536000000000000000000000000000000001285ff533da833a3daae7d815b1b86feb6f20b7592af8b0eb76240f390ea48b69a75547b040e7282b71779f450d3510c000000000000000000000000000000000813d38fa21c1f3c87b9c97ac03e6aeb8fa23e0340a0dff4e3892c774595648743d0b8980a7bd21648ce9b16a245ac3400000000000000000000000000000000020a69dbfb736c64e4cbc800aa415729b24ec05e901f2c7ba38e49a21c3851dc03bd4f7ec829d4326fe6c13867069a07000000000000000000000000000000000d518f3944053c8f74c0aea1d054d89106312880de4479b3dfb45b00945ff8bb58b12f9a489fa9fcd87194a71475d0a1abeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000018c82052cd483eee7aaa421c2b998ab0b4b32326dadba03c1d923726697d3940b40d5109ba34de09439e833ebc19daca000000000000000000000000000000000e4feddc3eeb3fd1eff8316d5b0cba554714713e8a605a55909889970ea2c8c58bb6c568024709def73b29a5a76563c100000000000000000000000000000000098da4cd0281a16e2e3e542ebb92269c8208a3d373394b0af92dc8a2676f9f0b6e85fda9161e32558e0569cfc7b1f3df000000000000000000000000000000000b7b54b51821fc037f02167d2e640f8dbfd1472407278b4bdf47b958da39f28c64569c3199846c293bf60e86aa45f205adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000003058abd4e3d49c86ffac9c95b1f07b66a22c42654dc4a2e3b07b87c22024a8bb0ee084a558ac22cc9fa286861fd77ff000000000000000000000000000000000fc9a89ee26c323df22add487a6bb278ca3f4c9a91eba4e067d5abc9dd3afededb4f98263e10083cc7ea224f28d3bbe100000000000000000000000000000000058eb015f1e14da860215d59165e12feb8d1317f652eeb76b3f08b38ed943c94e632dbf8145233dc93755e44e027553e0000000000000000000000000000000010897d5c2b481f9937d830b333e7649931e801a6bbffb7d9a3ee28ab1e27889691a9f0b9616a8437c3cda942bf07282206e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f0000000000000000000000000000000013b88963296d8c8197cafe160846ee11365b7a991b35cf5613dc57714aa48307f4dd9c6ff9704b29905c18a41a48010e0000000000000000000000000000000016a97fff65fca5ff282a818deb8100104308b8d9dfacddcae32fc2b6082331b44fa70580018930fe1ab9d9c1b13a59a20000000000000000000000000000000019cd2038acd84c2db1f0fa1b7eccc5f7ae3da803cb72c4a1e8390d49e0adff1d88a85696d9daaebce9c6b8a2f861fb36000000000000000000000000000000001271338587f06847770c72dfb3d9a657d05f8c7a012bec77a7d40a98cb1637ae99281c82668486119608b01feb25e6dab3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c600000000000000000000000000000000173ed7c70f4683102cc6a276d192a8f3b189197d5ea5dc813c7d0162a1649e906f76a1c9a1cb1ace6e4d937934b72338000000000000000000000000000000000936d260b789b1a2a9d04388caab364049395be61d320aef66ce50f052eb462faaa2017731518675bb0e4a2f050e4f7900000000000000000000000000000000070bd1254cf4b209ecb40afe248f2e53c390636625460439952ca2977be021d93fbec264c31ced2a810e8a5e54d750230000000000000000000000000000000016ddc3312f8ed359792bd213d086a0ff1540e3e5a2dedf6c450fb96a9b6d1edff9bde31fbc04de382cf44694a631178229b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000000aa4bc6e1a3e6c3c45a29db74b27af27b61856e2cf385ce0e5094ad53db4d31c4af45b5b234c66a21bf15018c13ece8000000000000000000000000000000000188affc993bf6c99103029c1e406bb1a693e4f1dc650907809ba3de1471d41095dc1866578962c72538ca85d09fcd22d000000000000000000000000000000000e487a7151916694b980e62b64ba49ffc54aaccfa0b0fbc5c14fa4a50d1bfda55698df5cd8570c07030f145c49a4ba9000000000000000000000000000000000084a05dced107d29a0fd4cf817ab67017ca33018d5c7302167d08c64c45c5c455fb5c907f21c39b8a86d037a126df4e76b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e000000000000000000000000000000000ea7060a07dacd84287007a05b494bf19a03e5a759b0ba67624c54cac3562c0ca3fa6e444206614d00d6d6684b86bcb5000000000000000000000000000000000eb2f332f4481276f931d2192c1a9f6d7585e85f248a8ac95aed398cb61bda05230bf8b9c041c6f78be3b34668a9c1a0000000000000000000000000000000000faa038219f844e379d8cce55cb8f0fe2b55548a0a0e1e37e25ba4f432e6b1a6451b8f081c171490bf055f81cbfe5f8600000000000000000000000000000000037c70d4e8befff257c4bc98a4726a961f3e2e68e7e02f9f2c94aa8f5fc67a1da44d41394dfe376a6c04240e4cd5825f3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f789700000000000000000000000000000000100a24d21c0ddb20d76b6d9fe642da5ac1de28afd642ab5c08574206b8b64d1fd822d295476bbdf2ca7e9267138034dd0000000000000000000000000000000000aa7e4f2f77acfe8b4c8f3fabd56b17415ee9bb182bca1db15c399479ec60382f980067b9d4c4ef7556d621259ae9110000000000000000000000000000000012f7a7f91a988fa661c661013736f0ec92b40f571ac15a47067bb847b09ba128d1dcaf8049b941a51cacece5db4e1eb40000000000000000000000000000000007528b0ea66b6ab8d5d318f5e4d1c0e9a4f504057dbb0397b614a1adb160032127f2ac35a1a98da70f023cd343a35ffd47944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd0000000000000000000000000000000015d72b8d4e71cc092c2875de80f3d12e003804d980a4b1dd13cff34e9336397c4533b6ae3a03beb2f09312a605947a270000000000000000000000000000000005976027a98f7b0caf4cc7d0d71440d3e4fffb1ff65fbf32dc890b275b646f2a32600a6215d6b2f999eaec8e58cb6d5c00000000000000000000000000000000111583b7734be53a7d4d090486070cd3d9622156c52871ec79c83ca024880684eada56a36b58cfc3490e65de41e10579000000000000000000000000000000000fb670b553c2ed4c81962b149efd4b0c77edf6ee70eba88300cf264dda98190e550540fb9fb95748599bca3abadd752030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac0000000000000000000000000000000003900e7cc0a8e891dc4dfc45f08d97e73ccbe2021a560a92c493aacd9c0614ad100294b5d7ebd634ffe4e5ea301a26170000000000000000000000000000000011ccc136127189728a7036e85d233fd150d5483963c48074f9d8ff83a0791c950da380e717f2bd0bff8fc115e9e886290000000000000000000000000000000007d3e76bd1f22679d228b4ee50a60cf1bd1fdaa171372cfa34bf4136a091abf7e5ef3c6b3446fd41d5de68b563fc7ff3000000000000000000000000000000001107f636d9187155357bea75c943dafcfba2394a9300054026b46d6f9db31eacc06d1f64c2b139af297dc4783026d98f4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd9786000000000000000000000000000000000e3a4101f6af3cf0d5d5aa5a0ebc26852dc69f91c06e96c5f1c7f8e4528c3dd92cb6f629620136ec356f0657fd9ebc6a0000000000000000000000000000000008d34dc3e1fa8bc22258e23b504d442a11938370325c101f1cfa52f313724e0894be722646195fd078c1a49720cde8c900000000000000000000000000000000163730996c79787e7ab89030de2c26e26188187762fa128ba4378a398ebd906dc56d99cf228591f394396248665c196600000000000000000000000000000000008f0a8b3d003b6727834228798950fb7a3cb6b931bced4540693445a007b474f7459ede17f87158e932e4c9c094ab904d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e000000000000000000000000000000000cc2d30f7d3869abfc34719f40b0ddaf00f52bcee7ec09a16de51785d55531fa7fe3ca1544d7103b9caf7105d60d9e930000000000000000000000000000000002ebd8af0bd3f82dc9dca585feaa83071534b2bc2b3d2aadbe0d01d759ade77ecec3b3f7b72f82087365a14dc205add80000000000000000000000000000000011aa3734a4b9168d3c46944cd726bcb203b94b25a97437a6aaace9c84da708bb073ee10585f28bc41e0601567863c193000000000000000000000000000000000ceb4ae5a8b506d31e77e2a43f3af8ba9459b887a927ca5287edbc2ba7c7cbba85a6e4d35c099b7ec7bf7eb2814cc38ae140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b,00000000000000000000000000000000192eb406b52075513584ae3c6093fb534270d716c79961d0a3c4bbc44096a2e8d28228363e2c8da54857945f1b983569000000000000000000000000000000000ee0d95748b13b531821ddd71a15fc529a2ce2c99a66f14e28f97478c3c2d524cb7c4cd7e71a1027030765554b8f50f7000000000000000000000000000000000610ab3e064532ce261aa2ba4f78721ac4f78661cc13fa09ccc279267e6f703f1bda17265a5eccb0061ce24d31e000ec000000000000000000000000000000001966a334b16e64e4dbd66119af97bd2b8d6afec0eb1b8207f437c00ab134ff369b3b3c1bf51b871a7fe8ad1ce93dca4e +0000000000000000000000000000000004c22bd94b82ed3b106532a58a0253daf51f579b9d746c624bbc6b58603942eb139c1b576241ca8fab5bf1c457112bd80000000000000000000000000000000010c6f7551d758d1128add57b110227296e060074e4cb934132368f079a794770ff406fc7717867df0f461f5c9fe56960000000000000000000000000000000000048f88afaf6eee5039b76c0c5b4b49671f6fd04f38bdee1b1c8f347a9dd4e6aef387b742c8f9a8aa387ab4d01fe4267000000000000000000000000000000000e7be987d0411dd7138e47ac00f9f07c4737d93aac501edd16362ea5a633c9071a6bf542d4db540d75edecdedc3a8f0ca57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b15300000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae8fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000003e33b840426a6bbe15b23fceba829bda9a5ab89d37e60133874f61bf1b10e05d460bb5d228cb178cfae2a5f41035d32000000000000000000000000000000000a9c5460c6443364d9f9440d101d92a0037343789ca0aab6dffcc2bf81e1aed312299a21556d16e55b1398334d9061f00000000000000000000000000000000015db251708253f7de13a5eeae5aa76fec415ecee1ffd88d882580da5da8d9f96c6ff90d920b329096a103dd71e7cfa580000000000000000000000000000000014c3a004cb6ab8465e05d965dc720b37084d98de424b160062f225dd0b67a8e62ae11a3c7bacaa129a568f3a243357ebb061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d61000000000000000000000000000000000c8fecac8bee21d916cc47b96a66b7a522ef4fea76fcc86ec490ff44b46fc01ac0446e3885e36ae7ab62a409ccffcca60000000000000000000000000000000011676ccef54bb27ab7db0b5ec025a9d1f29217030f3686e71564fa011d9fb598f44a8bed3da8fa7fcd10d01e3f66d86500000000000000000000000000000000093aecb91956215980854c6f19120777983a160e16026560c8076bdc4372f53065f9fee0f5830ea192aa5637590a745100000000000000000000000000000000035d773ef15d8d99b600a6a575eefd661aacb49d6540639223a454594570d0f00ba37340b63a2c8a0d4e53ee7dc2dd91355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a0000000000000000000000000000000007b2891e9cea2a464742c7f962deb1566c9d4f9e4e7cbee1912a72c5b064211c39801bf42bd888bc239e6b4ba71d700300000000000000000000000000000000169cf5e706dff2945145d5ac14bd5fc8f7e7c3e5f7ce733c865e1882d236926c71853efbea26e13efe4eb0d0e7ed5db6000000000000000000000000000000000de9ee19c4bc2fac36debd4c91317e54f57e761866b134ba9a0e84a8d268b11674110ee8f91aa8a6b80eabee2e5e75ae0000000000000000000000000000000016d91408a670e4ee43ab8e21cc341596709113950d22bdf5073cd90f520667699e94f64f76290f1bebfecfd80a9e051430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a7720000000000000000000000000000000001982744a15e8163a6f2ee681bf27a68996682216037d67d91993fbbe040e16ea21a9cb600fc6a40e7289185393544c3f000000000000000000000000000000001131d7dd5a5b96ac1f4c4aa210afe7af8d371cc16d32289aad38c93afcc1d3be53716f82e9d14ce6b1c833f7f5871ad00000000000000000000000000000000009adedaf19fb8823ec55b803c9509ad98217730bfc6424c8b69a071e99d026492e7c8c4a06509491a3bbe5893988c357000000000000000000000000000000000cc60733a783c7df76541daddef2245e6d2b694b94649b13c21aaffdce124c1cec3fd8ed5a5d4d4eff3115ac933e5df989a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000b366a80247a8e3797f1c711aebd60c99ec7caffda34514a3716154e900f2387c46f87f81af036a383e3f9234bd1b50e0000000000000000000000000000000004608b7cea13d08724a2cac691e61255ea7472537f7ff59894d511af7fd99ad72f0a7406271576300a7d1d56aea17bdb00000000000000000000000000000000141abedc914d3d1ed587162acbfddde60f7dbc1ee5e07fdb5f3515b87d1a29024c9e19f24e4c0e3979bd938aa4e798270000000000000000000000000000000010e72c6c0510495dd2c4ecaf13c1c6404654e1be369d1ca485c76d8c2304d60d69b90c2e171f18bf55668232e747825820b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d56200000000000000000000000000000000081d5a229481fd297363e8e217bf1f94a00f54eb6e8a3f95f4de30081bb2b9edd82d53cf287e37b459afabcb73fea1d1000000000000000000000000000000000ab55f52ff7dc578ae8267fe3fa09bdb8174dc30bb835cab9851dbee7a1aeba82e83e07d5e79aafb34643d9fc9a0d1c100000000000000000000000000000000195245c7a762776bc1e81d7111e3b814088f1e0e7d686c3ee3e500cd0a7ad4015851563a1b8b592e491e00078187c66e000000000000000000000000000000001850c1e8edb0d6dab973a9975833cffee8b5243654bc4ebe64972e423799283707f9ad343bfa86548cd2acbe04ede5da3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe00000000000000000000000000000000000007e9191fa9057cd7df8fb83d497ad774735c242bce9bd34cfd21d3f8f2a8e37d1f38b592a61ac8a8d22a4287fc5b0000000000000000000000000000000010e36db1460fa65ea229402f558397c6fc57e9c8a4b0b9e85d9ba938196bfeffc951587353cb7c7d84479f60c087e3660000000000000000000000000000000004d86938bebb850fea82acd336c3900b241757dd937f831dd909ce548325955f103dd57611c0b75bf71412a6ac3d6ed30000000000000000000000000000000013990c82583007b693c1d6271c1e5820d7274c4a729da21a76eccbf7abab1f2bdd6c5d26e78d51476ecf154e4fecd1b87fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000009104610d5887fb7cf6a866584cae30cfeb00e1241083b017ccb82ddc9d72fdc0d2b1d227c22ff6d8497495f44828efc0000000000000000000000000000000002235f959b071f21fd63282fdbb46b1dec27cc193f3e9988def691c73dddd789b6a1adb977a68e2661fb41d62280f229000000000000000000000000000000000ccd46984208f183f0b70c9152c01fdb8ac078ad1d85f41e3a24819da321d9dd9321a8d70103282abe6d8b981447f202000000000000000000000000000000001711057042a54ca76b0c3e7f36f2fd49e339b76cbd2e053d93ec2838848d359865fdbbeb9e75e408b4b316d60ce2741ef0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000001481684941fea0f66c78faa40aeb4b5254bf78c44df7e37b191c095ff12fc94248acf01d2aac5637e9536e73a82c9f0000000000000000000000000000000016b72eff2830f49b24b1e1317c95143cda8bc11b9dc4a91ff22a24e0bc1a244c7215ab1040fcfbc292ab236ac73cbd3d0000000000000000000000000000000013535421771fdad616171f7348cdf32bea7486bf4d836b8b95c69b71ea9915c099e256287aa119af53cf6320ad86664f0000000000000000000000000000000019ba0f36dc556fcf09f0a4a6cee53de485d03d846af7afb792d16220551fb5a42a4261f936b008babc096e6f8f68b63af8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000014b2da0add872d6e61253d6022559f668bf192b4aafe0acfbbf341ada55b404d42b2b31182c1ad50c73673494ea5b7d40000000000000000000000000000000018b76b74e9e6cda8466a354ff66baeb935b5645cf9eca81f4b7342f7914c9bf35c57be402458c09781e66a89cba6e67e0000000000000000000000000000000019bc8c1f32ce934b7ccae6d8ca39a263939585d8f94414c3880fc7bb5a0a27d728708e7ebc42c5a935f769adcfc083f6000000000000000000000000000000001636b62bbbe34bec06253887b78ad5b3ccda1bc5d8baafe450f2d1a8e07334ca79a40c5c4a50b58aaed96408749e6f68285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d325820000000000000000000000000000000013c0fd7a8441b6eb2dabfe8c152aa480015f81139c46440741f3da1c50d18c17526c47e8b8c2fbcfaefabbad5f8a0b000000000000000000000000000000000009da839802e7c6759a87eeae5a05146e1d226dd828d4ef6d908b4a0431008f352539f3abcd3e4c532a3d8204e350a8510000000000000000000000000000000014709634973e4554d2379e439d099e9be8bc7ef031b6ea36a7a85d2ff5090b0e0de7cc1c6b6a004465edcf868ef5fd5b00000000000000000000000000000000146779393d82bde1eaa6205e69907a0536c782fa7fc6e11e5e62ad5468f4422b3688f2ff4da2af396741ca5e0f97de3835bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e44000000000000000000000000000000000ddb7d0380370830803a7eda2e9b694af71381990f182b5d1223992abb5afe9531bbef8b9dba239f411fc422210fdc930000000000000000000000000000000018b685009d012d72193043d09f8968f9a41ce2fed598a20536fe54cb26db1733214add38f73148e754e632f6d78f524d000000000000000000000000000000000b967a7b4ed1bcd9f3da16584b08e0c28d967cebe7a07069abfb3bbce94d26b6d95d8a807879b24fb1f5ea00091d6dc300000000000000000000000000000000039349785fdb7d38707d8136e9a8f650c4491c50d7425388b75fe30da56147992c3d662f22131ba7173b2550e613477fa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c8000000000000000000000000000000000455d7799cc1c2af1e219b23e8683113fec126bad1dd7a441c5d113b064b552ccb1e7314dfed1b11f42a18acace706e50000000000000000000000000000000014d2400aa3e2270714b656bd755c4bba55866d6e313f619e10f94de6d82b5343ae9a9483dc10c1a72a5a21e619a20a8b000000000000000000000000000000000a6caa6cf8609d23b7873c908e5321d064a9c107b5492d296d04f92c308ee705229dfecb1f908bca0024ca56bc125126000000000000000000000000000000000b31c384423c84316f65e03ba9e01a8f626236f76e4df4b8ce2fa053c1c1e6a9b8f0afbc253db8c9c5e2ce9f9dcf05c71155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a6400000000000000000000000000000000097938bb53db8d0aeca3f2bc180039a5dc5269748e9cf065cd88e59b30733d527e54cdfa224e9690581e8c7f0881241b0000000000000000000000000000000002d52d97d4dd415fb18348f4de78c65e2933fc45d5e5e1d8f0f0ca1cd52885704ab12609b91d6d2d1ce13eecc7fa0c2d0000000000000000000000000000000018b926a37a8e0ad836846d06c03a9b84db795fdfe5f15d1fd3e0f8fef1b2825b29ee3a503ffb2f75765cca49c2b3d4cd00000000000000000000000000000000073bac093e958a3a09543e060c81b35b6598521a8685629f77200cdc73b372588e66c247097e7c03492c0943bfac4d6bc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045,000000000000000000000000000000000f79110c74f0e983f3d3618869af1d9b96dadba61f1d596294ef8a9412f946fa26cf63483528a57299dae48f85ada81e000000000000000000000000000000000e1a9cea3af1debcf7d6ef6f7b8566b5bb52d5548d4caf85925109228d7c9b50d65a1b24f089631e75a694f8e8dcaf040000000000000000000000000000000010efc1081f079e841eaa5a65cd7c945d4f37acc92c4ace9ae6c69a9a95d8cf569d604376b1c7e63558d022da90d269fd0000000000000000000000000000000010b7f55ffac8d57c89b664c36c20b2988a493de32f5a956c91b16ff67cb806298a59adcde12ead42d598b6ca3e1b94da +0000000000000000000000000000000017b139e5dddd53433362c49403838b3e2ecdd850a8df12d4dfacc0bb98f79d40966d62dfd0da1e721e7c0f298457d590000000000000000000000000000000000fa35e9c2e37bee1020ed99516174408ba2cf443fed115fe3a964ed86b5e5369e40291dbfbab477e339003ac85eb7405000000000000000000000000000000000e8fb87794860237066ed1b7ae7c2a783c48c52c2267f3e7295d1f17598b96232954e1eb6d6e80e716628f1db8afe48600000000000000000000000000000000083521e3a6d6e3f99570b747498520db5c89092b0077519c8421f9f41772c7a6e177c9cdca52f89a26c6036cadfafa8b32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000b1415e1dc2d4c1f5619b40e616d258867493d8624857e41d007f82ba8dc53f7ebb36d06f8348b94eedb794899e97df80000000000000000000000000000000001c01656fa47d62b4372361b80ea61501cfda47da5534e3e2aaa27b1e3c4de0bee0aa322e60c476fd4345340e5c00e130000000000000000000000000000000010caa407d9d265721d55f01dcfca52bde851ebd918e8fc4c752a41875940709c64599f36fae5e3ac7f211e1f67890d1c000000000000000000000000000000000b54a86474dd5f410290e4b4ac738fbba5e88c6debec17e38a52090b17ef371dc8feb0573e76c4b61d7688547a89f6a36583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e8630000000000000000000000000000000001649c78147fefa91100738e50034424244d22d8e1bb6a2bf471e4c9b29694a5c9476f4b129912bb09fece53aa87deeb00000000000000000000000000000000117a3e040c1f54b96c2435891a45fb9dd95774b5a55cfb306c22517e4ea72172332d893047f7eaa665fcc58dd21781f400000000000000000000000000000000105e8d80d46e6bab2bb9ce0525cbfc82e8b3320ee4a8b9c0086e21cf2b5895cb35abffedd1b5a9eef21f62a0a1dc48e8000000000000000000000000000000001437ee33abadc8ef6bfeca16c3edcf05480c3dd97db06e396e10d5180472f50074f43f9a031a04dcd11d803462fefadc5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000003b51b10efb54dbc2973e001f0bb634e36f689264484eb128de2882d6600a43ad548bc7d1def4541f0ed88a1fb37f3270000000000000000000000000000000009dd80dbfe6663ab04656856f192002593df9ef7f792dfa81f6a51c658c4c9ce5586a5edaffefd507f51ccb7e8c8101500000000000000000000000000000000144160d5ca6b2ad626e6a3424ff5139adadd3319940afa9bff7dc409ac1fc3775d5413ef4612b27fd22c02c1fe57bb86000000000000000000000000000000000e375ff490a626dd1d933a5c751c88cbd61803986fa8dc089ccbdeaa0a922758afbcdc30d29268fe0a34b7b79d0f76c139c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b000000000000000000000000000000000997ade20fe9c0d3eb79e61a66a5c272d02af668b0f3c8201a1ea071737f3d2ee3b0764f859480e95be75ab8845b407f0000000000000000000000000000000003215194b6a363d31ece09b18700479e6093fa3472a23ef0133e3dce60a3d56b6fa984b900162c4ad56a6899aacf35c3000000000000000000000000000000001647647bbc399f40124c43510469cf613732d0919e22b478b2603d7553927584cd4b3a407e3ec6387c4a93e9e5373178000000000000000000000000000000000bacc8afdd70e927e21521b3f62264ad4f22adbc872439ff851d3d169a1c79a0d02bca2aabaa0b9941ab1c71d092fac12196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c565000000000000000000000000000000000b0c5981bf6ef5b85bbc504fb0196ba442fe87302346688165aa7df8cf2642548760e11daf5b3fe2e37b43379afbfd4a000000000000000000000000000000001086828b9560aaee5e28bcb50db8153c40e632b18c61ed4105bb7f472b6a69ddd8a2836f6605102931ee66b2f07e441f000000000000000000000000000000000f4d7aa3d1a281af6f8afb3d886774f4e4a64490232f63dbe16e3b8c4f626e9d07f7c668d09cadab3c92d6fe852427af000000000000000000000000000000000d92ea3318779b532cd81c9be44b1abb179a8411319a6f8fbd7e3f158bc970917d3e0b25f3f3f6c8e0764011f9bab0398df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000000dbd83910f304d0fb2b6d8619c3a308c719f6454a357d9ced03b2882a50692c06cda7f4331f54eb293ed5aa079121fb00000000000000000000000000000000019c33ec829367dfd2610ccef9842ffaa5e4f35809657c22134fb09b024e07949d8370ba8ba1e9149060e9bd3babc19c000000000000000000000000000000000ac468b42925d2daacb8574d40064d393caa643f08767d20e72ac0fad1447a64d8743523312f3a91a118d3e51e1f52d7000000000000000000000000000000000202d1971fef2938cfd10bef5900b91cc4811939f66f1f5578a8ae0eacb2538d2a51c1e025449e1637b5173ab7fa3b6f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef00000000000000000000000000000000008b6709123b9bd501360fa463dd08076c59177dc0e8035c49fa2f541eef3831e4c584c5a9410c68999dddda6c86fd9d5000000000000000000000000000000000fb94eb34355c636dca909cfa71f52471217b9bc241cd3e98907d4a5c7eb67d5bc9cdb0c73c1369d7950a014fe6069fc0000000000000000000000000000000002e2ee515a5dc96a664bb1f862f21a8d3b7f903fb87f6dac41c3541f3d83633f351ba8dc4661607d24b912dd1ab097da0000000000000000000000000000000008bee545e00e3fc283185a85511e09fd0253e191f52d5c0b440b10228041800c013db3c9322a835e4927c0ae0b21bc1e8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000006ba635e74538748c29aa7c5690a0530f2b1970554598a432d4ea6d2713a4d26786b6e80f67b2f39e218b19323654ea200000000000000000000000000000000133ca9e5e0d4a8200d3522d8e87dec3c72edc1cf16b7305af4abd466aa7a0e30159388d34c36ea030450ef45b7940ec20000000000000000000000000000000004724239afc773688ea92296bae8845f20793c05807a18d6f35f03bef295da06f8ac9dff438b720dbea7ea93f3ea9c4500000000000000000000000000000000149c12922fd69e1960274a8b91384e929fb354936c020911495e6e3c49faf16899ec0c6e87713ee2f0149bf808ac8abfcd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e5066700000000000000000000000000000000148a4fe6ca67b6c785d5d8a784d5e68fcd2bd08294ca37f296b6426433b805507b554eb9f0fadfa9d293e8cdb8547d4c0000000000000000000000000000000003700600c2b7bfea54801ac95ff7a2c069bace31ceadab2947a0641462089fb43f0b9697acc005a23007a923ffe97360000000000000000000000000000000001705a769ce3c9a7a91283e4068c602d85808980d6fb457345a5f9b2499ff8fb3ec8383049b9b7cae96bd2ac6106a07fd00000000000000000000000000000000052b1f4e8a48a5eb2b2580614c656393819b4f0ffea874be899e4964c7e32d54757f2d48ca7b50e47e8bf6d6ab8ee7572a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000012ff8ba50d587765e68f95d276e364c8c40c00b55abc929f9ec240985269eb096dd3cef5826cf6269ecf54bc67773510000000000000000000000000000000000959492d74cb34c8c9ca4a21ddee97df99c8a6e627db3ef72200f39e0402d56f0a9709596189c80aa3aa50793e0f1a68000000000000000000000000000000000f7e5dbe884597054d6dc5e80bf4d0d333025bddebc1fdb1d61482cf15bcb4c8a95ea29cdd0925b5b816cc0bb307387200000000000000000000000000000000194e940c041d71f43ffaa51fbb31eb63c23559069b42dbf8777f35eddf14edbc3f7762c7b354174a584507ad714948234a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee2800000000000000000000000000000000101a8e54d1fc2357df60b0ef8872b729295218f29ff63f7a7b6a70b3ecdbfc6809eaa8dc1f62a664b9987e8e86154c6c0000000000000000000000000000000015b5ddd012b42e1a600d738e05b551d91e7fcf3cb36018ceda9b689b92022224990c11a6fa0b421d5610b7e59b7463c30000000000000000000000000000000016130be17fceab55387d43179cd943c85ce1ff1881c07c937b2cc0645ec9ebaf0e10718ec7fe0d720f49bed2b8caf15b0000000000000000000000000000000017d73650680856bc11619e6acc139e137f0a06476f5f8979b5ba7fb8123d85916915da60d1f2e8c84197eef518b350c2a62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000007d26bf37a97d532ec93a3eac00d9d39b064ecd172ebd5e18228b1601eb7a2c272aff9d88d63781b4a587c2c8582eec4000000000000000000000000000000000108000e850bfbfb02d7acef97592e15ca721334eb51197511b0eb2bd3bb647fc8f07713487b0a0bedbafb106992de4b000000000000000000000000000000001868c0b2ba732731f7536851f8005e8bae7b16545b39190251eb2bf93dedbf0803a42ec24cebd151998b690c38c0346c0000000000000000000000000000000016faafe909a1f926333b12f5463231a71058aec31d73893687d3169c4c3588436f6178447eed307b642490199c507d63a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a6000000000000000000000000000000000331338cbaeb8e304fbb9257bb80aff5d3e043d07dbc476dec2795347e4c25248caad06ad14f56183d2b6276c49ff98700000000000000000000000000000000167e9578304a1162de73914b02791468e14faa2e0f161aa57818b8a169b5933dfcab787ec0f4b23737011163dcaa02750000000000000000000000000000000010aadfd5cc781e73c31f2fb64e7981b2e28614aa18dc7b2d96d2bb4ed8c2ee9089d6ebe0cf85479b272cb049e934739900000000000000000000000000000000128d7ea54f338064cd2f041f42a1a1e77d8b9be4ee55f568786a36f87f965d8142207e518798061eb3e32fe3b0f1541d1f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b400000000000000000000000000000000004539f22654b3182d4fda5ab8d4bce6f1268d4e402b6c29a4cdff3b5abe0618d33db55ccd1ff12b27b2cb0196ac53e0600000000000000000000000000000000177e80ab6aa8512cc9e4d65b06b2bd76e33bef9038cdc1ab97fbb9d896ae2ad884ea16407490653dbe972b14e9c30c0b000000000000000000000000000000000c280a4431e41df6515979a694ce292f220278178f7f36e23c8a4cb2b8a7ebc520901ebe34c72a26b2c8a60aa1a155100000000000000000000000000000000006a0b80538a6c8093f3655905af1c59c235567d22192758c28dad1b715045189a412e4c1edc26e1d8ac95a584277709b3bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000eb091007672a212dc4937b314576963d7561657cf1103820ce9bc34e4d46c24f4891a4a4ada648f8cdd2c30f670b86200000000000000000000000000000000166389a37e6e3c02317d68d54f29cc98d1d1df5853940555161d71df791cd92c483eaad87dc0e765b12408d6ac344f31000000000000000000000000000000000affd0d5734cbc27b192c0c0e464db48d3d76799d2c6a493b172127ef2df6ea18a33898828effeeaceb7a203e35ca41800000000000000000000000000000000155708b9756752c9b44048c91d71970fd2cf2a4cae6b0baec00629c81387c8261150e78f856093d81e816be6403f1ee91b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d,0000000000000000000000000000000007ceeb14945414d96088a7900c1120ff182b2a93b09943c2fd1dc2b0b223f684b0d4c0b1b5803502582f2daf16d81d2d0000000000000000000000000000000008df450fb25534fdc456a8f41cc143a84729ccb082aaa2243c8f37e34a6670f5195750f8547444c49f7a898aa8567d980000000000000000000000000000000008c12d360078d5645b0e095c90d4fd37eb20f0ebbc6fa93fa5beda7e7c78eecc06e0d839268e2c303422ab1769402e0b0000000000000000000000000000000002bd594a21153d7c458b9f804050d05caf2d90bbf9d18def79eb8148b7f89e3a3ac21f84b87fd13c39df5b91cf73460d +0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc00000000000000000000000000000000090b834a587521729426d5b134c6058bf7999f4d4bcc0812e8d8b3ebb050961321b5e93356e87171a6f12160749394ee000000000000000000000000000000000cd5148c7eeac4aaea4288b38a02b5a901a6e2805e2b1695ed98ed86cfa0d259d87b65bf3cc9d00b8548100a60a371d200000000000000000000000000000000026db1079b85411dea0b9fca383956af50b938a465f35347605c01f3b72b297630ee2fb5252da20ee0d8ba5071974ed70000000000000000000000000000000012ae26c193e02d7ae4a7a01181551085dec9fbcac811c45d5cef19abf736ca2514e1259811970af5913891abe22a75ecf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda99438000000000000000000000000000000000ed5d8a609aa4f3c65a89b8dbc9334bd3cec6c7763bff298acd6c260e4d3bec0088e15c5d82618571d13b74a2031eff1000000000000000000000000000000000c28f92f018e6f822912b6eccfab37432ab0ab9acab751f848401791bd2f16e32ac6d97948bd8a0bed2ddc1917f0db3b0000000000000000000000000000000014083be2539d914883172cdc70950512dfe7be8893b1ecf085d837c2e9ba7f03656c5a0e15373e04d300869620eb66d00000000000000000000000000000000002561b77cc2658c54d29f8d1988dd7448f59c80c02ee9256404d8ef5536ee50104cbc11b6ee1ab9ccbf0ca55e53c52aae45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a6700000000000000000000000000000000000fff05aea33a9d1e8f7b227c80ae87c9e7589ba2804904b7d8386b24b0e5324e718f29531251969a972870a30c310630000000000000000000000000000000016ecb8f27a369df13e122c981e7ae37882b36d5492fccbc86d606aa1198f3e4ee7bb7ad0555e11949e6c1783d8f4cda100000000000000000000000000000000187f425b675cb12719a01ee3b78ea73d88f70805f72d6cabef6372ccb9d99008bdd7da54f155454c4c59f041deec86f800000000000000000000000000000000151c272d5cb67b3f801e103ee901deb4b3d3bef76ee4e1b2ce1b5e663ed292845ba012c732d38f9209f82e77f1f73cf354868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001791bd59815309f2aeb7b07df8afd89a288eb6f19c7e613f394353ac5398267e1388c97b17d83104446e57f94581a79c00000000000000000000000000000000154cc72ada5a9c99dea06ebec143a14271cf332b57c631725ab30e2d308d6b688ca08a79efb6fce632cb1216ac3d077e0000000000000000000000000000000012b4c6fe8c17274ef57539563a736c2f83c4cb473e9d075a976e18e193255057340f45de373c7d6e3fe5e08ad0dd97d20000000000000000000000000000000005aef16e11bd4e7787bd5ab4427276ecdf9c6c134b9fdb2ec39e87ae4a5b3b674b5ceee29bcdf804ebd7e83960d8d7ef7068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb0000000000000000000000000000000008e57f905fa202c7640500746b590791cf9d0f160a77e5eaa5a30280e513e8e801c4b6b04cc3f80d9403388571d180ba000000000000000000000000000000000da3c128ae234bc27824062832ac10aa9cd4978f37855a8b4cde3822f5b485fddb9a475a9805e795519d7f138a8199cf0000000000000000000000000000000000ec11b7e07710161fc557a56e04337f71aaa1a0f070cd84525965e53a1fe445c91ac07c618ec349997890ae893c165d000000000000000000000000000000000406b0eafbb8782d11f5dae2f6214282252af9ae9ebc5c17a81d4ddded40f05d0b534d14019bcb6cf4e49c4c182b90f00042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e000000000000000000000000000000000b1d456e66671dfa72ef3a56523eb939146226111fdbbeb697983928aebd5f50b0518db841a3d48912a7a780785c1f180000000000000000000000000000000007a15b2253496b78d270dd55b80bff90583a95283a89d40f6df71fadce56d103f0d365fe79256fa4f93b2d2bf4c06a2e0000000000000000000000000000000010829223166d38fd2c3041dd5643c9784da366a2ea8cbb3abdffb5fe43e975318c86de0ac9ec77c0126ee75bd209f7300000000000000000000000000000000004b124018e83e1e5e77bad42eb831798d450f8ff4a79c9b14f67f080047c491fbba45db79b2cf6015188f9fa6329e8be0a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da0000000000000000000000000000000011119be42b90c7857079a51695dd5be08e59374b0d1c7e12d0ffe870202e1f0c62bff84c9691679a82e610e788b7b5e1000000000000000000000000000000000c7a64524c5dd1bf10d16da7f15b39d05c9ee1620d4dcae79c60316a1f522b238e7934d1be897a441d0c8e621b67d44c0000000000000000000000000000000013045613a090d05d07310865d977c8e0bb1caa713b2249d6676e7cfd6f4e3ba8e667deabf9fdf7fd527685f7d251b178000000000000000000000000000000000dfee7f8259701b5726b6439a7ce77b92245499906502c7dfb384e29cafea61f3b1f21fcd7888231569ebf29d3035a61ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000006d77669207bb2d064824cb56fc786c631936d30db630be3c08e18d7e95b1c26e2d4e7b2eddc2f946fba6e99acb2198a00000000000000000000000000000000168bd8f291f8bcdf8b5e9fa915f7f24856a62803bbbeb9bc38384149008d4e3129338035061631f1fbaceeccfaeef4a700000000000000000000000000000000146bf2dedc262557dec2b4545c94a37434e20e4900b1693e8fa9bda9a94dbd07e0a3bee5f3bedfa42148791f4951db7500000000000000000000000000000000138467700fd5088c76af2f77fea4b746f98701fc0578571997b0ac2fc343354ddc8b2dc57d5298dd4daf767573d8bd3d5f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000001536da0df7c91687339fc93608eb404c5f46adf4b9122b99b1e5cee0012e27ddf30934d8f669bd39091f8673aa3b3c490000000000000000000000000000000002deaa8f9349e7c551e39751b1454a00f8f7896d63110e8e42607e8023ae3070c4abc9885ed54ee37a82f6e5c68451e900000000000000000000000000000000079a62eb17f7b07d4117956d3dab5d16a7f90e98948d5c3caa124fcf755c73f060a90d002cf880f5246a87342717b4dd0000000000000000000000000000000001246f0f3ec2af7c0250ae14cc67b5a1d42309f06c6f47b89178ff7534c47e8413a26a43f27454c0f946c66634563d41cc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed216500000000000000000000000000000000046e4a08785de985c66c7417f9262d363b9acee07e250999a4a7124f101ec4d82e3e4b2b0d9736471329fd61d0cff13b0000000000000000000000000000000017bf1e20ac181780ced62a18c78b378fc0dad157cf30d6026680560b681f5755183bd30b4e454764c08edb93297590b5000000000000000000000000000000000a57cbe93254bb0796eafc0a57330e38bfca37f8b94c4d21ba656e5616239e1e18ba6d632c0129d30291736fe37a4ac90000000000000000000000000000000007f31df7dbe9abe15f4024d8f6bed93c92ff5bfbd7835e08e870eb1bc4a6f62b3809b922c6d5a7350e2e5a978c80a67397b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba5916727000000000000000000000000000000001577abdf6e915c9c3b3fa50a4601709cd629397f2f91784528e4cdbb140065fc2a6ee3830983dcfd49a928e78cf530aa000000000000000000000000000000000d6f98df9e41009837cbb05bc3e3340d38e56a448fe396bd48acf03f061e7489d1402b36a84b3c56eb859437e9c406f1000000000000000000000000000000001912afae5361c3d8c6141755deeef26d1fadf6b0036b9d05b2e0c4d50f42328741f0423ac772fc66dbc922bd4a837ac40000000000000000000000000000000000616661f049b5c784ba05334b2931509e1e033bd203fe17f04cfe12e80e73eb7075beac9d379fc1c457bea1b6adf365752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000f98fed7e4d67a513c746d2fb188597a605165d5d299072aad6d621e077845f93804d575a5796bfa726f529dbd90e014000000000000000000000000000000000adb2d0b6c02e4e8fcab11c7c8819e87f73aab673ff9dbc5c50fee751bc7a6a8d386c8f9fa830b5545f94a73ce6e1f1f000000000000000000000000000000000f08e05ac40655cf59ee3ea9f10fc900315c6f06ffd3b80853560559f580ecdd65aba5ba660c729e0bb9576eee3703710000000000000000000000000000000009da46469f4b8fcd8d2b016e96f6e6582fb01c75407c36c7f87b4a1cd8f08ad06e962a0ec2138ed6fabaa1cb0115f97e2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee3000000000000000000000000000000000047b45ad2ad4f7b5b72194f98b98b2150b5d73a9df2aeb2377beed9a1275a882fa2d849037ddb56af632489f892a48a7000000000000000000000000000000000e1b0d9b52c0c5324067857ba4701f5f20eec165be418871fc0f0adbc3a0bbdce5a33277a33b79013109b81e006c621400000000000000000000000000000000179c471e01e340d8e6fc0f737ec09f0180bd2dd2a86d0817f753d1e9a9f8cb18178e9de68c596dc6a824e6c3c151d8b80000000000000000000000000000000019405c1e571a9b200ff2949aa74647dae59d92a8669d4876ba23f1b4a12a1f9412412503c68acbd619cae3ff056bd346f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db4458000000000000000000000000000000000ca8566b9bd088c471fd33fb7b1bf760ee12cc8b0cfa9ad92b45012cafef5c0772d9bd3bd9b266d6c3e3890c8f00057300000000000000000000000000000000055789839e786ecee7fb7d10f3876359fcc1bd6f2c5cf25c8337aff7fdeec9b43ffbe932cc4936bb708571a59e4339990000000000000000000000000000000013cf827bd57d8179d105f34c147665a072714ccbc114aa4e878d04ce66ca78bdabdc4867b3968c75dead147257197c6a0000000000000000000000000000000014a8dc5ac1858442ca627eaa194e1ba64091b5f9ace551338d770c92fb49ee12449dc200c8c35d70f9e0652b4d9b90da1c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc000000000000000000000000000000001124ea2b97a6d73c81387a51e814b9bdc951a773db2a32d50691be60f1d397cd4aadd9b06e4f49c32b12254e9f824fe80000000000000000000000000000000014cb365e9780feeeff3548f34a56548302ae0dc73402c40317fc819969ee9c4ea2a181381b94f82dd97a236671b456a000000000000000000000000000000000064b769c4b785d45472038aeeebd3ba9b28b3132d72023640ab2d7512cc6e31296c5330be5653ad6902e4e15e57e2c3e0000000000000000000000000000000014c7bfb1f142d69c17f73e23011aee0063a97a99d982d25ff72791a65c7a68941a80fc216cea8a49f3df2d0748b1f95db0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4,000000000000000000000000000000000fb1227806c750e0eec0b865daaaf51afb74a88589d1c035c60dc1913f05c8ab18de24903ea876fda27b97a5eaa2fd7c0000000000000000000000000000000019903e1341f0285658164f9273b5c958060bf836264502b9dc298f75d4104d7a43b8d5dc0bb934a506ce1273ba839d830000000000000000000000000000000006e791347b54057195189e8b9f10fd42d170f37d455c0af5e92cc6a12e2c23990253be6855f4be6c84a708852c19a6f90000000000000000000000000000000005b72c361dca430fb2414b9d5a326cef8b77cfe5310153d6994dc1f8b9e74e8fbb43079e21956f428ed8aa48d6897e32 +00000000000000000000000000000000052acff88605f33a0cf1201e8101c95ca0befd443c087265821a7d954917a026d41ab24d29bdfd972bb52ff4ad6de14c000000000000000000000000000000000e134b2aac3f6270e43afd994302426796b1e362031638fe0263c0ec212b828a30d8321af34ef7bf260652150cf2293b000000000000000000000000000000000d6628f675008099e9a75e1294803e86417ab22670d36316798680289ae61a26821693f2f9efc8468721a1097c3bceb20000000000000000000000000000000006d66ffad1a2e0f39488fd3f6e0214c9407528c8bfb8d1ebe6d75596a3e3cc844d00fdf46ce7ff6cd6d67732874a24a484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd000000000000000000000000000000001160bf0f7f2915cfc64e12a5a91b7e2aac78d4c2ce362e7677dd0e9c0172b37fd1b52222a13c65819b87593ee32a9ba6000000000000000000000000000000000c8be2cbbd302b31b1ab6dcbeb57b4ad428447bca9159fdfd007f5375218d121673a010f2c7fdf83fb45883458fb068e000000000000000000000000000000000363d3d492e6e6901756bac13b5c32d55aabbedde878115aa41b57d27b49a0f017a61fc90b13a20e009989374b82f5dd0000000000000000000000000000000009302fc26e6d750ff9441d7471903cc296b320128de71f86c4eacc80ce0725e8eea6acd2af056abde2f61e0a349f9bb5bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd1000000000000000000000000000000000b12aca17efc103cad500b3d773e43cb24df6be651963c0f30bca489f1dd7911ffc7204fcaa4511e161c6f75da4a5ff600000000000000000000000000000000179a36e9292d3f78a5fecbec1175f001bd4ac0ff3610f596aacdba29a12ea4844885a7c465e66d3883c7fc99d4a7e06a0000000000000000000000000000000016bfd0758b31f54f90eb8562bb693c45a92a297a3d302279c9e3cb8263efc0f31579a3af8e8f5a091d9a6a36776f445d00000000000000000000000000000000020f6c66fa554a5cb610ab05d194e7583e6798a113b8fff336c986f7358bb9fa6a7aab0b04be9b5c44a6fcfdd21999e83717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000004bf40c1d2d3574ad7fe128ee376364591b6f647f939b0b556ac3fdb5a92873f17c007e926b8a39a97c814728f355bfa000000000000000000000000000000000b8669e10e0a538a421b717287455620b82574b96ed09f64db444ec73a67a3227503e1b4fd6869314214071399eeae0b0000000000000000000000000000000006ddea4adb703d7205b6d2af436b41b4bde3a8c5dbed9dd161c9b3b466ebf06beced64fca25c3bbb97f232315daa5565000000000000000000000000000000000d97248a25ddf0ebd0200c6abbcac9ecd9775cfc5ec8da91634e77488bb592e5ff277a9607fe721990f403dd73f746e622788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d94000000000000000000000000000000000135bc4f28663a6d7d995f6b876ffb8e6ef1d2d0f232388aa5f390c57e8c48cb84d370ebc4bc267eae4466a019c9ed56e0000000000000000000000000000000008b6a9d13dd9d7014df6acb59f80b335a751fa2ba4dce63467aed18f68358f5cb743718112b3cb2d0b5add34bb6989000000000000000000000000000000000013a5389dba4da195f34fbe798b254403f0bc5632ed98bd6017ef24fff33640ae493c1bb7a77a0d3c97649230e455eb51000000000000000000000000000000000a69803a4cc237ddfebc51df2d90fa1ad03359f9635ac1646bc942546575d1558f5f2c3010f6e2207849ee697be41d093c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd384000000000000000000000000000000001939c2431f8ac4ab19d2735f122c0424af2ef18c0028e155611237e86648bf1d74fcba3008f5c6aa30feb5d4a14a3f3f00000000000000000000000000000000174473eedb54aafc522973244ec2feb3f7e95e50a1e996d1100c8da4fa59428c280f76e9e7364906662c4d2802235aa5000000000000000000000000000000001021d15f8ae2f62dfd3862944bf3be88d86d8113f4be22544ae5e925d450044279c5bfa1bfca44cd5934b42a27096b510000000000000000000000000000000015e0f20efae92e1fe8dea2222ce808a7de9e9e861c333db139f8ac11d7c4fa9ae6e49f51095f6e16bc738dc6d094b4cfccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a0000000000000000000000000000000012d70691721f5787ea2e2a652f9c65edaf763637f95c285a62d32dded18579b7257493e01eda19631d00ecdd4e27a9ff0000000000000000000000000000000014da9ef6076e646e7d5b52d1803d3a6d897664851c6de2a9b330e48695737e05f0369224c3eb357bf557625bb94e6ea2000000000000000000000000000000001554f68124a91be5b9f325394db23ed5db8f6c46eb46cb50e57947bae00819b151afbf4ab4949290ad41625499f42dc00000000000000000000000000000000009fc0d459e28cd1239d227e1d2f7d530b9d14ce5638cd308569300a791c997a51dd5a98aad703239a23cfe7cef7f47f6ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f000000000000000000000000000000000452580d6a37a07038ce3564a12c1c7391fdb002cf27a6df7e194b38f3c12a3026f2a8acfe5e634cf89140da256d0a420000000000000000000000000000000004b73c9a4f9d41b8b84e53de538e4b15198f50247e75c274c14f136d7d91dce4a62c5346bf11a105f035e29ccac3dbb70000000000000000000000000000000008a8a3b2705a82b551f8913853f682253e7f1f68c8e42f349337f4f1eaa5103f59430af0c4a124b6a739bf88298c5f6f0000000000000000000000000000000012f4220609899e8610809bb3a4da46e0688c285ba2e8750b4bf44a849cf15fbf5c016e8e8f9372239bb562e7f38916e921cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c910000000000000000000000000000000001884558e709635c046bd6ea8872bda936ba4d5ebcf7a0208cd0a4ee08b69f36dd2e136ce655ddfd89a5b1cf8e48f5ef7000000000000000000000000000000001357e2dd9fb603e5190d7b7ee105668bca2ed23ec6a248aa71aa430c2b2755747b8dfa3b147eb51ea644bf0354a61ba000000000000000000000000000000000009b0b0a76c6980e62e4893157b85f59345e1ac81e1aad1e48acec44c4803e2a9080f0d193fb799e0277ae6f1058839e0000000000000000000000000000000014c984ae4ef5d9d319fc89895f34a7db02747f57b206b0b30e8c9757d4b47419e6c0c8378fdec5aba364936a3b1922ca2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a050000000000000000000000000000000003e1bbb872db172a1fa615155f81aa75ee9760f8602e4135ef9f1640b7f9d54bda911a220d211dc4bb767bc2b5e6e23e0000000000000000000000000000000008464f23cf693b1d4545b6ce4aecdc8fd182cfb288c5ddb1f78ca578e9b16342c8178d886cbb6b8086c0fd1318c4ae09000000000000000000000000000000000af574c4d0fd86087e23daf6d9ce98765e1e445ef9152dbd68152fa4833ada0be440de4abfe7c07dbd4ee67f1a4aec9a000000000000000000000000000000000a8227b982f9286b03c4d49766687622206213d88cde007360df9b4ca5916c44ce44dbe6443577998b4b0d527d22593379cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000012e31070a501a7df7be43dc23e23dafa32ebfbc10ffb4c53f5d36bab2af69db5a05ad64b9ed116560e40b71f9217189b0000000000000000000000000000000011cbcd38ec3c6a6d49df6a8d6e1029a0412b42bd3fe8b42ed625adeb5a2f631e97bfad302de82ae34f715962b5ba0289000000000000000000000000000000001019b1b619fde9fb885d3c5f03a4373358107af7509754ce1ab2deb67df536d05e07ca7d60d927c15b549502750054f90000000000000000000000000000000018f1768b7140484105cf3ad2daa7c565e18eaba834db3f6bdfc9ee37445f2d6f7dc2b4c986b7efd5373224d2c92aa5a81973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e4000000000000000000000000000000001847b14146cfa2e1700f368f414b6a66ccaa02ca2a90b40a8e2be2ee4eb66af77ba563d7507de63362fb18426b6149610000000000000000000000000000000005c028d2b344ccb6400b53134bd179028b8774000ace89369bc655bb9dcd1643aaeec830407ee941df5432ba27987e8f000000000000000000000000000000000c4a680e2157dbdb53ae761209d505b4cf6b18fef5aff1c5009ab41295e0ce2ca23bd7a4f983fb9d085e1d0dbc75ffe40000000000000000000000000000000013c0cc77a5d771f1df99d1530e65ba782604c1ecf67d08572609de9f18405b9b817c2643226cdc7c9ad35beebf87dab0a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000009f32f2f83c21875963818872d243cc8c70b75234f53490eccffbf060cb3b9c53545c1c32025b271514f500b20b00ec10000000000000000000000000000000002491b571087a9e89dbdd039ccd2c37d5d8d25587495b2d7b0066e9dcca02d44b2c134b0128a9a1527396729f069df83000000000000000000000000000000000264e9c47f72b639597de8f26a42ca7d77324f8c0db705986fc3b40dfb46f47764b69c70037a68d76a5de49a278779a100000000000000000000000000000000090614b3bb302ed9fb78b8756524fb78d54a4390b27136087181342571f994b1a93faee28256d765a8ff4f448cc357c199ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b0000000000000000000000000000000012c716ddf17fca0d974e8d6003d99aa90f06b201fd141c74d8fdf1167030d14dc732917d3c6f736c68fbde9df50c098a0000000000000000000000000000000000261ef2b47de8e1576aecc6e19ececf80ddc1f4e28b2ff27953a65199f65a6211db7326632cfe04d543895c727ef8b600000000000000000000000000000000044fd6b9b4a1bacb8b7d4c53c106b025ae78f17c3baebbccca4e18cfbdbcbf8b3ef88ed5bd9bb36d9aea9e24f4117e760000000000000000000000000000000007721612515fd075811ee804314acec9d389900c7ef883e866f71fba00c49d5c4dcc7a2b8e2366f5a93f4577926ed171fdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e900000000000000000000000000000000122581659ab1712afc23c23c2986394de8e155bcf722e944ec05e7e42e05acc366d9a7abf2136b5dc68a8dcfd4a640bf000000000000000000000000000000000188842cf4ef54cf77c145acb685d3187cd9c842ba6705bfed846ace83dc4400c45120fc1d6a633ea879840d3d0c902f0000000000000000000000000000000005c8966862ed4458a753155ffe2c64655779860149641ee5511a46ec576798fdb5cd9521528df77bfebcdaae2f94b865000000000000000000000000000000000cc10d888d2b7a97666de99ac14a501b7e2171f074d30d947efd67d85226c312a7977cf923ddbc88c533f08a99f2045f786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f285d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6,000000000000000000000000000000000c9be91da9bd8774f18efa3ae9248e4b03d11c49b377c372613b7e745882b2b23c49d518672e58eabd4d9b510a25d8fa0000000000000000000000000000000019687b9eaf5d68b0e795cd57055a74e44efb3e997cb038b7f1cbf08ca70e80a1655cdb04402c542a92ae4e435c22d0b90000000000000000000000000000000010aa1514402ce348d1d61b8d38b53017cd3977a84dc14445db64799cfe822b56a0adbfc5332093ce7ea1f0f438bf15590000000000000000000000000000000019ade30ba0faffcaede95aa272be042aef090f89d9ca25cb825846c4bf9e4c1dc575f8968c88ada51fac71f26fb01517 +000000000000000000000000000000000a1346771f8ba25fc44323d5290068e46b3f756de6d97aa934d511979a1486bc32173575639a7e54aea8eeb60f32a8c3000000000000000000000000000000001958ae7fce87db47a65a03402313b99f659ae02e8b62db3525d48dc9075aacc5e5abb50156e704701f3ceb18747e0431000000000000000000000000000000000f98778311e28b4081aa76a3f9546b94c29d86fe8e66b905265d74ee21928dc3ac463049f70d355d8caee5b59d65e07300000000000000000000000000000000185cc233ce72770ae26406476c1779858523e7c940d69adf2750695cb12440440686b6b918f4adb3b14aee9aceb6422119582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d37000000000000000000000000000000000ae2f565a44c8e07f2a136368798a44926cffd3c3a6d4c2fbf91763c20d2bd959271343b80eccec4d59a84394c7a3ce70000000000000000000000000000000009481a5fb276c938801133adf10dde3e7da2087d0bcecd3c9435b7de544271eb3b07a69efe7e168869e727868f24b0d90000000000000000000000000000000011774e0197866b1c8b3428d353d2c9f6326a77ab30d5595e2402a0486f03ca6ebb1e8dd335a60a772dfdd9a3dbdd3eeb0000000000000000000000000000000011ed2480d79f73a67a2adaa6da3ae4f1e1c28feaf0e4cb9aafac658901960129e40f6415ec80a31d72004899326f714bac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d1000000000000000000000000000000000a73b0d8c31af2deed481faec54095875639233bb09f31b1c7c745cb54778d1c8bd0a230e963ddd2ec8d178d31fc14740000000000000000000000000000000015a889b16be93d0b6dced01f5e2278ffde1cef0576d0b04b49996cc5252854f879e04b1ffeb90e222f4b9d5fa350767c000000000000000000000000000000000b53dc4d72e90330ffad17012bc7dd2e497cf8aa6ec73bf25c10427e23fd28137631249eabe9d0308c956dc7a9e92047000000000000000000000000000000000930cdc5d04ed2d1eb62937d9f72fdd733c07a5a0e392fd5216100216b1a2e3cde7053bf766f046cc470d92bebaf6290069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000131c4e590400b69b3657f7c67272b1e3491983997993ee87c94043001d78e605965abf3c1a8c8c39cc08d5a5ef05520000000000000000000000000000000000124f71c136dbb032504da910958e8a7949f1dc5c061f21d50e439e01e67919891633b3bb84fa8a54c69b632f78560ca70000000000000000000000000000000014a4b1a05f1060853f4294e669a20b91f939793a6eada6dbc84fda8ab11509b256d8b785b252a3795f1d2b99a51df05d000000000000000000000000000000000be2489f1f91d7adff356236859679c46b6bf8c1b375e8bc8bd1e97830b5ac223ffbbda60ecda168bacc2c0b90ed25d3be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c00000000000000000000000000000000111ebb796e8770d5a69e724a8d3ca62ef1f13778baf4ba12bf462211d35e325ff8e455c85237a73a3046e531f2e2255e0000000000000000000000000000000004308b76b06067e0a07bda143341220809b481b40b78edb2e24e83aa0f003d209198825b5fa9bfd92597e27a4054d3ee000000000000000000000000000000000de74485713f5c95653e98b96aeefb79b59911a610c2a848a807653c19d50394fdb52178947c779134d24b6d396ca36800000000000000000000000000000000069f47a71ad765591f6335b962e7c2d87b556801e1e6c25b449edc83432612fefd405c952397a704e9aa5a924769ad4e9b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000a3f89408ee43c0ba6a7c9c479327ebab426d430e3ff212c65da6364b16195619d27eee83d701a2ec50bd4b7acfaa06300000000000000000000000000000000092715831af983f740ca2c673e7c9c47727d64165c59fce19dc3fbbdd0b6a7be66288ea1f033ebb5ae2b38b3762edaee00000000000000000000000000000000071ca6fa9e546d4bce965b2bd0f0fb97e6833f05cedcf66d43ec88aba411dc4d6db9f1591de22f493f49a1dab1a2701e0000000000000000000000000000000018f89932ec032fc28775d34d588169a1435bf4ad7e2ee11c9d6934dae31324ddb96b3ef88f95d1bb2e52c3c8d9c01516d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f0000000000000000000000000000000011b5cc382164fc21c9a72cd85acf61c2a78d00a16a2dff938f0b36bfb3bb7075845a1616001ab53271a9a257a38312cc00000000000000000000000000000000139ba2f27e545d45027a0b11253532e28fa691170e08608472ce3b3f9a3e9398c5ee76953b1a1d01a5e79f194c32d1f5000000000000000000000000000000000d875f44829555cec695f3f4a28078b0a6f168bb0985793d003443b75a141936f3c7c633518890e0f7238416d46573cf000000000000000000000000000000000675420ed817ecd24bc5172d3e7df60ac4281b24ba91e8b5ca8bd6a8321f5c7312a6ba043fbcdc467c8a5c957590a692cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c400000000000000000000000000000000107bde844286cd3958cc7a1314127322251699b51d8af8e6b57495497f21a84e05612b1569b54fc5639a75e9f9deef750000000000000000000000000000000002355b1a60e24e4879448437d2c1b12e58f02d7eba88583e96e9634f7e2c8c6886132ef0488918f665ae3f7b6977c7c4000000000000000000000000000000000fed531e437b70bc4a19ad63c61ccaab49afc50fad1f156b1c8ecba0e1b703f8aea61882c6327d4d8fdd072df9c4e73500000000000000000000000000000000182177409579ad53786539514753c696c8757b8c4d9b8360392f24b591e43ec20e84c0abe468061a9e5e879c5c81314217f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b40000000000000000000000000000000001fb25395089228772d6000025cb0356eb510c964bf7d0c12d47a6608fc18cc448e44880eb5ba8475cbe6418fc9d8fee000000000000000000000000000000000f3b9de9980e5afaebc59c56e02fd75fdad13013842ac035f8d5569a46cc67f0cee461a939aa5a3d8fec2966294207930000000000000000000000000000000009a223ac0edb164845eb8397e0cae4363fb2c8c996c3c5d722cb50be56cc3789c732763cfd4b61470886dc991be39f57000000000000000000000000000000001909f17b229eb351dfe8317a8273d846edf14ad5ee0ebe8cc2b595ebfed19b73983035e19ebaee3d05b1dea35968586961ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb500000000000000000000000000000000081fa9eb8ca7d9db52380e4c408e6d5d668471bafbafd62ba9023fa08f6d300a45295b583677824c29ddc3254439cadc000000000000000000000000000000000e2e613043b1566674f791dca9d860a49a75dfa24dce3fe18f544a9b24ec5266a64e77386b672c93fc4d079eb8e76a01000000000000000000000000000000000f471b86ac5783d720e7d73e8871474c8665e8a109aba27c1172ca24217eefb0f66c53232df1672dc0af6ddf9640e10d0000000000000000000000000000000010667cb22a6a818fa7c729e40a7e70e1f31b0ecd568b54a4d352d5c9df8cf1072ebf2ef1e612efd96bddcbeedd8566430fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be000000000000000000000000000000000a0ed87b01f27f26380c6285e82bf2f12ef3016c7e7f3a13041d465825664573db47be6cf099cea615e21f6a5d759b6a0000000000000000000000000000000007afb2a1bd50fa0fd3174d70f1c8d5c229627a496bc9bb89d4f52d47b1862e14d704dddd80045e58d00336e898a996eb000000000000000000000000000000001698f30f824ee5cb71b3f2451953c371987433d2eda570f2a13262ff9e5e529e316b06ef6aadffc152803b076f22db9f0000000000000000000000000000000009eb1d5f3da7cfe9b40a70e1b3c3dae36436e8d068a79dcaa283905614676645c99a5a165630ad46b70bd6be8b1f21a8d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc40000000000000000000000000000000005c4390b8f37cc3fb9f248470b505a5d9502d44e4a4459d1f56452cd9aec89d114f1402fa45935930fa00888a4860a9900000000000000000000000000000000163b0ca84b5cca4f124bfb5a13a4a3efa677a84dc89b6a61e69d0aad34fade528614e549a7b2326d1f6016bd0d35465a000000000000000000000000000000000bf450dc8af483a9f993a29cb47d5362c9f5ef38afc2fba8040e14514eb834fec6520a413fce5868aa9a2c7c3ff6617a000000000000000000000000000000001063619f384102949fa1f8353f0aaa5031234d736c54103df6ef6fcd0df02a19c3aef471f0413a1e19febed6395459a0c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e35db3017963d3a9d62b7e7fbfa13ce4f5fb46a90c1285ddc0fa481d9379b95a77e8cdd4aab5c33059bfcdcd82473fb0000000000000000000000000000000004fa27c663c8d21f041d15cb199d31cfcb96a56cd673b730dd111bf03cd954cc33799456674ed4d58e8e0dfa826a6b26000000000000000000000000000000000e0df4e7f943db5b5c27bafc7e1ce099b2caa64642bcd6336ef926352682fbe81a1945b266cba7eab52b16f4aa63eb8500000000000000000000000000000000020167756b8c68f535c4691b1249ca1ccf0a539f7274623ada824d0ba789ef44ebb20ec1ba51d46c0a42da78653d287e26cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f00000000000000000000000000000000178037c6b5fd1c6c396d8aaadb712863557feb744d2cb9165ae5c36376d2c066f7b1648e083f81c2c96da6562e0b3c20000000000000000000000000000000000b805b4e1cd5d45d8b6ed9d4f604ac0b40f336b8123f7281df43a6e803f8688bd8087fc4d5fbae695d06efb0fa35e18400000000000000000000000000000000000a947562dde45f613ee1d15614940a2edfc770d733a60374f8e9188675d4cf973a5c1081c11fe5a1d93bbe85e6f47800000000000000000000000000000000059473d80c82c6ca06b4aa71d072f4751b3b053b53ffcfb4a84906ddfc36ec5918668a62f07054af1b241bdd4485edba699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000008b9d0916a9f5689b8fdac84bec3a49d0224dbadca6329ecc156da633e1332bcc6735ca3ecb228c22032dcb7b2f372d3000000000000000000000000000000000cac0c264add10bdc1217384a7379f65b93cf822418f7e4e2b48eeac45f068a61f805cedfb1665dda06e04cb726d245c000000000000000000000000000000001578e98a40a64da59154b1c3d757d8f1f8cdc500482c7b7d65b9997576f745442fbac654c19331977bd210df440372970000000000000000000000000000000015ef69f82e85c81d28893d94927068f14c6516eb7d09898d5d055cbb7a9b55c6d7f686f067ab164160e6d6a8f91ea19d40e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf95230000000000000000000000000000000000a6a27b498285085139b8dd0c37b700997134337e696c84b5e0cf70ea3991cfb40ca3a3098a3b3a2fa31e91aac78eb2000000000000000000000000000000000bbd7ebf4301c5eabd4f448b89f1b227415cede3247a1c8dc56a02247efaa99dc78cf370f644ffc06cd2158fa25197dc0000000000000000000000000000000004535a402540474d53c084d4fb6d9e12dba6716ee13286ed758aedc1ef911b55c572640180a54cbc084ff57ceae8a4b4000000000000000000000000000000000759de2a9e0f3c04b4f629a682dbcadb2140e5b935845cb55bd267e230e08c6e8cc5426057473aa03ea2196203bbf6dc062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d,00000000000000000000000000000000134c29cc5c33c10f04b6c09b5db71b10304028d06ad6acd4f4b39b16823288085a84a0380a1549f04b3dc692cb8216d3000000000000000000000000000000000a0a9379d63527ab9b5f9c00be4acd54e5fd683a0a2f37c85ba570171c705eaadfb0f4e4be1a8836c9de86dff46138300000000000000000000000000000000006ce78f135dda5af34a0e069d7ef13fd589cec5a6128512bdae7f45f28b09c6e4b3cf638628c9f4783097cc00082aeea00000000000000000000000000000000141e710ce7a979dd1772150d0cb2d5b269d5cda50d1bf7bd0cd827b24f9cd8c1e2775f495cfec0428519627b7fede464 +0000000000000000000000000000000018aba8353cc470b287a163fdb9b8b4cc46071543ee8261f928a7b856287946637d9b36b728a54e1df5f185a47f1556060000000000000000000000000000000001129541b2e3b2e1a553995b603dc3eee44a5ea440e687739ee9e1339dd79bd96c67231ac753d483e0ca96b27054997b000000000000000000000000000000000e1cdf3591aadeb56dbd80890ff7d5639a64847cec771a19c769df7da732a6d3179d3a89ce0052bd7c982af0304408120000000000000000000000000000000000f5f5f0ebfd2b632e15381ccbabfa88eb774f2c61801381ca73e6970965ecd54f5f3a9af7c152186af8fb75ffb5bc25764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff2000000000000000000000000000000000e527e40c311edc5dcdbb4d0b70497eaee14533aa8ec57dc7cbd7d839fe6c6ae62b1fd0be2346a038de687d5cf5394d60000000000000000000000000000000005f9fc63027dbee5e0d55cd6c57daf5df7af0d138393a2dcdc71ef9aeeb204ea347f7d574e71f2ffdd37d8f05dc7979f000000000000000000000000000000000f8788034c9f1c9c2018a52326c046cdba8997199732152963819b663c6e58e9d6a0065289e2e25a97ce5627505900f20000000000000000000000000000000002a747bb3bcccdc6ea0af1bf1d0ce55de3f41b93060361b30c76063346b606322a76ed7eb260219c83aea0806ac7d8583280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000002148c043e065132e978e89f018a5b728d278c95c9cd1a6f276bd13f0cb708422a62fa22f7b377adf33055fcb09a6a8100000000000000000000000000000000024c4c721a0574e53118bdcc3fd41f73176bc8264d2ff39673210525166bb3513016b5c9af67a47a7032b74a62effcef000000000000000000000000000000000797dfa8cad94896916b7d55fbbb3eb0eeb74f70231205388d0dda69dd8abb436c22addd22c1e3689093739af957b65200000000000000000000000000000000010dd2ea2d45528de8bf1b5c5dc3267fe8951e48ff5987e67ec52d58635521cf1905f1688894e3e23a659764880b2301d4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb11177000000000000000000000000000000000eecc0a4edd3cc3f70d3e0e43ba56b04cfb3f1ac23c657048a94318e622217572b0f929c73f545d6f5f5613920c0580200000000000000000000000000000000137a098ea8d3aed32c197a2d244a2e18753045b55cfe16874f79c728c664b7f23b10476f20dfffb2f80417c26dff4f860000000000000000000000000000000004a7789b02d7d95a2ce0c7bac39d5b057509200393450a47fd9d087a353f866921aa11185550537b98f3073650d9a1370000000000000000000000000000000006ed63730bae06403baf705da0e30c6c00739799eea4a312d06b8d7dc35cb43a4f1e941a69e55ddd7ab8ce42d8629fdcc66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000ee7ddbf43f17f722dae84d34d26add8c1d732918b8c75c6b295f2f584075cea0c655911410b32c06868c1acf36aeaaf0000000000000000000000000000000018775682555d9f5a576cf9462170910bcdd083671ae2e4c8c6fa99a702548f1ce9afe90e681b00d194322b1a2a3be7ef000000000000000000000000000000000f3935bbbf58b91fe8176f3e25ad3fdeffdd6b369ae70b704d4e54d4fe32fe5987e73aa5aa975e958497340274577cf3000000000000000000000000000000000c088bc439d638d86aba6bb1e6e9f7540ac2da3b96080aed455edd1fbabfc141e26f125cc3a9cf72070a24f298dcc3ef4b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b8450000000000000000000000000000000018d8a47d1a13b9b8fb5a1625f9616ba120d5c677bcc996f694b7e15d251fc4bc938b0a7cb5b70f22b8e9f5b416c513210000000000000000000000000000000003d0646458bbee7ccab27f858b8ab0af0cf583da12a40ca5a954d7eaf97c897d379129a63d8131036f29c30c6e644149000000000000000000000000000000000d5466b50975c5a2dad96e4e24339eafc8c85c2497a6f19e12d96603596498654cabea6995a92c91b8319ce06f18d56d00000000000000000000000000000000191a96d62139f8219b9e4369a783400d045d72ab2dd83fd229e08a4ca73de59a11a5add86c739cb3bab4adc5e9f79685708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a00000000000000000000000000000000032eb1f7846b563e98fca0cd44ede4909b6e16a893f5ef01eaccbd7d8aa11710606bbbd0ee6480f7cdbdc9ffe66c3a9c000000000000000000000000000000000c31bb6fb537cfcbffe815d86ebfae1f5053ceb756818ede8a58cd84cb34d0eacc70ab9095f9db1691e4fb4bb816d570000000000000000000000000000000000a8fa1dc2f28277a4bf8fd9665d4b5c3baf1352d89890d4af94a3657cdac7fd72558da1e65cbc5bfac142f0e817be74f0000000000000000000000000000000005ff65c22ff0abfb33518791823c5f2202ea5f7258c0a507ab84460335ffc2cc8d7c7f670752a7647d6a6487ca0c9adb85ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000190379b7629f74bfb88096dc9ffcdecebae0d653410f032a35a811a09022679c9be19f3790af95c3205a396819e068e1000000000000000000000000000000000b6f114fc277ae8f0b5374dd349985bf955dff7fcb0095e0e1e137fb539814be78c924074bbab54f29dfb42f3e7df24a0000000000000000000000000000000002d86b0507c147142d03d3461bfea4c3af7e57a6edbb372387de24a27cfe27c44ee4b9571325a1b3f5e83eef450f2fc6000000000000000000000000000000000ac3b226d5e13c36c3a8ef0c8896d9af55bcc0cb67ac1cee57a5c6519617ec77af9af60ed44e0a8284a2d59816ebf848fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee3147800000000000000000000000000000000079cd4511e953e4d1b3f4f3bbbc66a62772018e809779fa39aaeeffac737cda9a6116293848f967577f03017f33231d2000000000000000000000000000000000ce3cf48be423a2fc0188b94f2a22579872e9ba140798e560ad107f63ab2b8c601831f89d06a4bb8e7a758cf836ddfb3000000000000000000000000000000000a6a90f735f215a79216fc4e7daffbf74775f38824952af72ac38c38a77a277483e34bc95031679494d76f109c0aecc4000000000000000000000000000000000d55fbce780d885cf817cd2126e7acf115ae9c72843af23c376f3a5d4307d1eefaa0f4691e7c09b5da1707aeaa5b675af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf200000000000000000000000000000000008924efbdb46b9324bfb79b922ba8b7d83f5e5e4b3b736105e5339805838171801bcf17208f3dfe5c7475d4e45b6ad970000000000000000000000000000000007bc0096fd23f0c93f0dde8a3974ea3105574e031202f6316d5940c85164c6d6bb5b86078a0c68dc822c0fd1b3dc8cc10000000000000000000000000000000017276b3208b347388a5657b10e3c8e4a187b376e42352f76ee3ee88873217b6b8185022c93097cc116abdecf3cc64467000000000000000000000000000000001915ff932acbdeb52f07b664bcc47c3a5b096c6cec32da4d7044326dfe84358e49539fe50782538a901b99428446b0f50ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000009e295d229b543a17db1cc85c846111b7097bd169d19b410de78f8da9684e664922eae77c64b0db430aeb422016cfe7d000000000000000000000000000000000e29aab30a1da56b8590e9df67171cc1b9c847696b51147cc57ed6c3b55819cfa0992c67e15e4ca6de2573c9e16231c10000000000000000000000000000000007cc9990c6722645e320dd16a4be8adaab41f958f769ba0d22e235549a7457778cb9b14aa6ea5caa9e0bd43f8d04cacc000000000000000000000000000000000b2dab5cf37ae8e76b71dd8748c86e8823142792445fa0b140de31957d35bb7267e3d94e0dc92f4342d9f8560c5d9d86dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d09953900000000000000000000000000000000128e2aa795f8479da3ea2a4efd12aa90a6fb019d4da89fd372e6848ff7ee17da689d766c9e49c88c962eb4f682c56fff0000000000000000000000000000000000fd68bb80d6b2200297aacae1174275f864669e962d85c9105032d7a352fea548e9fa0629a6749c789fa0827a40190700000000000000000000000000000000175bc3918dcc972fb728f1d8cc30ce9887efc6e0b254d8d22af87f95cd4182129d494c43d11b028c4b9849f5520a4fc00000000000000000000000000000000007c5363f507a01c0b6935fee0413345bceaf1336cdd20f69060bdba2e411521a61a549e6159b2e006ffa16e3bd77e998e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f90000000000000000000000000000000000efcd782b89fee74ebc037160c6653ccc104260b5f8989545b40d51ead6ad6ce6252e1232281c813e3c883af86e68ca000000000000000000000000000000000b68ed21f76ce131c089dc454dc48ef948cc7c6d5fd87d647db954c9eeab2f7f76ccc51a1cff8612e89bceed16ca03ba000000000000000000000000000000000cd776670d5171610046fa294fecefb42f9bb4d71baed4af65a09018b09ad9341789abc23c9feb85adf96b4203b0c0a0000000000000000000000000000000000ec4ca0091a28b73c9adbe7120f2bf1a84a62ebba1e86b1948389b1a1966c1de4c632a5e245ba634b53cb932f5847f6ecc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000022f319bb5167c2b945a69a438f712df8975a0e262438ea687e2b0d824e2d1d14bff1065f50fd6ae92494f6f3aa9472b00000000000000000000000000000000198ce9e4ddb6b423788dbea82d75513f43cb43ecf1b27c8788f041248f01808644f60fd823e5862cd7afb4f7e8b6b6a100000000000000000000000000000000119dc1be1bbb7e678319db73055ccb88ef7efcf6119f8a9c43c69247ff264879a627f653a10a950e0dbe69155ebca4f1000000000000000000000000000000000692a0ef5a75d42524e3fd52ae073b0f2ddf6378f18a5dcef05af4868a899b93c7f1d2691883e5c85f97052ef1f4177d09a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000001423d1707e49d2215f639df75ee0e13bc724efc7d099259179260ba0f17157c4efc4276844bfdc46c61ac2185f64beca0000000000000000000000000000000019ad06d215d3c819311938f89609ea7cc63fadaa11bcc86cf5f26370a966eaed1aca312c18176674b5aaca3ed8ca876e0000000000000000000000000000000013bf3f13e87f3ce29f0524094e2ab8e39679566add32e779256006dc92ce09f60d5bb9cf0452b90ece71a5f6981d77f300000000000000000000000000000000112e4901efca14686c30a883ecdafdc389303f4cf46345e229885c76d900b0aa084a957076009ce22ee36d4e285d410c9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12,0000000000000000000000000000000016d1fce53fc4cf40acb0347c0983dda46383e4828c16985459ac89a2ce8d3c2a26cd9acfaa2ec899cc63b4c6bc351f560000000000000000000000000000000019c9626363b511a79f297dc79c5a3b7a2e5127fe49a2fac5bc43a4376f170404f044f9f84b82cd09a306012fc81e3bdb00000000000000000000000000000000062e324f3d7c5bd39808b762a5b009cb30bec14a9591477959339bf2de9ef27eb42a0eddb95aa5fdca9bb9d89b278cc20000000000000000000000000000000000f05225a4d3bf910b0ac0103594a90684ffc0c09e2c21744032e30470d5727be3c27621dc2377e9845ad78be67b856a +000000000000000000000000000000000c64577b78ff306186dc44131cf8bd946a68e65502c4af9613154a7e3ffea4fe4e59cac4a500894b470a74e66db1834e000000000000000000000000000000000b4311c295bd30174f17b9ab3544f1496b9655302a4b6026a113b1aca31b555ce7b2d812bf8fafb6b075f67cdc59b77f0000000000000000000000000000000012d7dc3db10ae6b4e3e99c655aadb71124a0bdcfa6e30ec13c7c977d39f83aba4979a1f45881813a3a68e149a40120a90000000000000000000000000000000001b958c47cfecd619c05a2c54315585d15fe377beff889602ecba6ea3b29d51f6480f09a0a8490e4c754045f0bfdc3eb7b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d00000000000000000000000000000000010db66653f2ca73e55d85254f62e8b558584d546913e4b8f530968088d90cd5c4bc164fdb77325fe0bb2fb1a5683896000000000000000000000000000000000a1af9bf84f0b351c8e8c082a79c7ccae768bd2ed40eede38666aab1901a6eab0cff258f440c8c8eb4854a824b9aab4b000000000000000000000000000000000444fa654afb57f1b01d10be08a330f23667af58e3a40c630a2e01c1809125b3ff8f76e8a39418688a023293ff1a45e90000000000000000000000000000000002ebb117ea107a3598b888dcbd5918dd0ca307b21d65843c6c28b3efab89d0e276e5d92283bbb345b674d4a300595425c6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b48000000000000000000000000000000000105818a11efaeab4801d4fa7558b09bd56294be7f58e3e54fab1f4f7c243ceaf08c99a14c3128ccfd866b6f93caf989800000000000000000000000000000000091ca092e5f83a04e872e621e71d54dd6886d088718f60ed5b62d3e4c165d3ff2cea1e50fcb94fff251df0f5ee453cfc000000000000000000000000000000000b42051a1ef52f87536b9bca459fa7070ca17bf825950b13b3bbe9db183ef722f62af6c05c174c65d59b722e1d2f5b0e0000000000000000000000000000000002fdb4a5480418e43aea28e5041af6ad55a6c226e1eea1a0449a99b5a937375285feecabea95c2652da5113dc17d8ef4410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d000000000000000000000000000000000d8f93b589678d4e93bdf8da7834bc8edab648ead731b7f5f0cc299488f07262877ee9bb1174ccc106204dcd3f1f416800000000000000000000000000000000160f740ffca48d3a10c43e591cf46c129507f10e65d76a031fded2930d6c2dca4c79d7813f63e4ff71aee09d672173680000000000000000000000000000000013c768a4889315faa3976c8e43b4d466ea594bd94773f270a198f2571ba9662d10435d1e541a492055c333eece9bb29a0000000000000000000000000000000003dcbcc9e6a0cd5741d77da88fbbc269202e8f944a5df5dc4f9145758654934d5e1eedd596325080382458961ed3d21ed97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b3000000000000000000000000000000001344fb37c1d7dcab01a4bf6fa50c6bb7606f7db22b85a3888ffcc2e9f229f196881cd7c82160730727e49b9e6fea04320000000000000000000000000000000010c7b15a6355d3152eaada7a606031f28809f278a1d0e04d264b563185ac7d9e351295191a6a90ffc9c6dd33995265db0000000000000000000000000000000014438086226a061a1bd557dac24d9333e14cdfa3a7bb00ded4a450e8889a3028b174bf38ae1347e6aad19ebc1cf5ff7800000000000000000000000000000000105165703c4592cc4f1f489d78426a56434dc77327c13221b582dc25306f4c5bfe596f3e47abcb741ab553fa14cad374bdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb36000000000000000000000000000000000ad8b55a198a5e788bb54c32112761ccba9863cba16d95ec9e30181376e7eccaa2741660f2c5f708300be058e566ae27000000000000000000000000000000000b9bbca7db413964d2ec113cdee2d7a7bcdb712d285655f6b2897dcac61456ba4d08e25e8c28627231824829bd7d13800000000000000000000000000000000001ae49c10675256651e3e038a2150d85993fa6f2a97b9bc02c693ed97ad52af34015176258b3b2546b81010a4381d85c000000000000000000000000000000000c8f9668a0a497420acff5207a07cf780e0b2ba78083eb0ed8eef76beea996210541bae2e64d825000f307d54cbe3b2b43da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000012990a66c132a31d60d182f729b52d9b57d9d1eb1988b6f0b4d9fa2847f26983078ef9bbfd0e888e14bf7d1f92d09e54000000000000000000000000000000000585215ffc2673a197bf9cc6c6424547886abc6ef5c6edfeab2ef0c42034a4a458fc7155c35c84a8e9e9d89fbd4aa25c00000000000000000000000000000000118fb4fe0d3498dd2b55e62272e95a1203f9fd22314921d3e044f1b162376aaa7e8154a4e2184b66451aba98729330c0000000000000000000000000000000000364b9032ab9cd9f599979c8a93acbdb831707f1f84fdc92844b58bc7e4d72472ca5b09c51b1b04271ed9f0e391552463c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b6805893000000000000000000000000000000000ddf9e4e302169e195f4f88fed06e0c93fd1b542abbfeea5da5d47c662ad9a16b8f4aed7874354fb9008d073920e1e7e000000000000000000000000000000000043fd1a4b781f25e8747ecb3eec45ce90760e0d5dd701e8193a7e726684ccb8ff21f0219ba15e9e777d339a3d87a1ee000000000000000000000000000000001117d2ca429048056084e4847c7169b4c8ddaefe1d48079949f9f14e4d74f0e0b38a95d0f17389f61da7b2a6d8cabd1c0000000000000000000000000000000007adfc7d87b1c533b4439f6b30d38c694b65b3b321f3eec13cd7d923f1d5f117005be6c3ea901a9479d79fc553e34e6c51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000115a81aebee0329b174c01458f8714b13ea3fb2dbfb051b27b29b940652f27e01a84e522626d12be80da7e1039e2baf6000000000000000000000000000000000d9e37d2e5e7160db30acf5593d1c282541a0d4ac0482f0759fef8704b9ec3ab1e3ed248e37c6be285e890ef1a520d0b000000000000000000000000000000000c198a22c2f590df2902c8dc2bb1ee427b33e9687767666140f9d3b51d73fef18a259d43d86fb3559b1ab0abacf547a70000000000000000000000000000000017e705af54ab76145a79e747167a4fec6ec3a16f3ceef86b1ddd1be144e616ea7d686bbccbd1c5c258e4546405be023d6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b0000000000000000000000000000000007c4c31287ae0b3bb90475f84abdda36610f887aae311d8e97bf97bbdbdfb11d38c7de331cc9dd022926678e5180c0770000000000000000000000000000000017f4afe28adc4b24d16b9cd97aacd171c2104b13b079c643d664a7c924151a401c352832c4967c0e5cecec5f1d1dae290000000000000000000000000000000005a8aa8a3a91461e0ba256e44af56875f8d07e24628e738ffc057249d8380417884f40c84e76dc6ce5816ffc05c0d686000000000000000000000000000000000f84bb7385a6936b519e881a708541570a31a9d7897ab8b348a350adb0d30522567fb917c9b6db661b6f53f98b5e68aaf4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb1000000000000000000000000000000000a8726ea352582ed52ab4e440102963891f059cf5a3f4901615733ad560a99930efd8692f3c30256d58e5cfc4f0803bf0000000000000000000000000000000016a623dfeae872639d99e3b8209748642f20af796325630596b6ab6146783bd4f2247c7ae38d53ba9a3fc0cdd6f29087000000000000000000000000000000000e40709656e569e4fe90eb85d8761c6ce44a4272793ccb7635ce75d67d97464e8dcd8c32bd4ac9a36fcce706006915b20000000000000000000000000000000019e64802756896206a9600f2d888f3d307ebf958492b1b5153c06a11231e2d7d2f1786368447f380093a29117cc25da9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000019c7409cda6084edc6e559da9b361c96cf207f1e2cd63cabc9b86c5bcb07a59b43e9c6ae3e90a61c872f168ab89cb2c9000000000000000000000000000000001101bb63a452b766a085fb788937f6b751417dd8d905ee50ab5bf96cdbb9d7b68c1735460a71eaf9e9bf662734f495c20000000000000000000000000000000014a103871fe523cd01053a992eb9884ce83c6023bd6a8c2cd9ca60b8780118c88502c6980904f2d2bf9ccc9fb597d535000000000000000000000000000000001929f25d52ee6b9a44333237c732a63ce2abc80c5510bd67faad1d7adac96eac5449823f3a52ed18bb90b93d9640d0d1283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e50000000000000000000000000000000015b0a46692f57ccd2b7f53040dd75f30af0196aa3c5499049eb172b4d927f96a59c42a129117d6162a1bb31d2e8734a4000000000000000000000000000000001366dde2d9070a2c057744fffe78effdc328b122e356a6aadb10c3fd2e8badc0ff70bc6d18293b3c52428e2ba78766600000000000000000000000000000000016fd48b067b949ed75bae3e4db29b5785bf672bd01032a925d653f8a605998e1eff6c77ec39dcfccd417f1e0a9defa820000000000000000000000000000000004cf22bd706dbb1cf8b97187ed97636380871402b3ba9de58f174bf50a7a0b528749762c3f55f5f835a276e43b46e669d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b0000000000000000000000000000000000ff424ae9372af46de34210bb0bd670eb173bd49076df5caca4bc4293e742121267a20506f931a4ae77cc36fcbc8df4d0000000000000000000000000000000015a6815b47966fb84aad5de62e6d4280f9135e129f33fd01e667f4d6e1bf7204317fa7741f3cff3682e251437927131c000000000000000000000000000000000639dca43483b79ba8043130e508e91fe3f43bc362fd1dbb135a2eb8f3b94d5cc4af70f1101c790545a0eaf2408706e1000000000000000000000000000000000045f0a04a642bb6e4db34fbffc8adb19a24648554f36ca371fb1a851384a4516a57f1850f7d6be59ff67029ec4002de310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a99000000000000000000000000000000000e796fd500cb1a25b834baf7335641f34ccf04ccf60f82367f0e5c8c7fce8e3030e7b916752bac8e3adc01cbf4b319ac00000000000000000000000000000000142e8bbac9cae69ba3dca48aec045e0c4d7028f73c254433f921b7240761c661cf8e774a21da249f7758234cf7607fbe00000000000000000000000000000000045a3d80767d116e89bab0e9de812ffe7ffdbc41b61f5f17ad16be5bdc9968e34f46b937c5f94f8197e21b358f44b5240000000000000000000000000000000006978b93018bfdbaef0d40f1278e831a1fc50b44fff39b7c93820a284d90b699981b1f422f751a33094ae7b5cedbbb2691b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab98500000000000000000000000000000000003c3561f5d255cf1f83cb5f4df8e3b8d5655d965826d56867ae66da631f8e7d489f733f5824c36652ab00586d9c593be0000000000000000000000000000000010b3adb0017e2cea1b71680ca33aee368429880759660dce2d3cdf57b6cd7339bd8853e5efafb9a5aec3f7e22da676c2000000000000000000000000000000000cdf976e4c65edb79ff15178f6ec5bf0a77a30d97b799e433f216a2fe3eedb10bc6ecbee2974167128773cff43f1922c000000000000000000000000000000001599b60ee70d927849764880830b2e7355daf95eefef39ef61569a2b83b2bcced4dfb28047a1e5350cc87ef3cd5cf1d93e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8,00000000000000000000000000000000123af49ac2981e14a490a4a6792f21343497b562266a47487cf6c650769c810852e357445bc68f3460e7822e8cd1e3f000000000000000000000000000000000143e79853e4bf6d031e1116dac2c6eca4991b8a1f822fac1d352d2cf1b88df239d82df886f0b24b3e7f305286cc1257e000000000000000000000000000000000b621056a9de2d83c946b1e2c7a07f9beb8e053202407323e412c0d8f60123cfd5818067f57034fe1b1b5a3d1bb285a50000000000000000000000000000000001642fdff2c52d58d38201cf44c31e00df47ea1439e9896b5ac5e9372482f4ffcc698be46c2d375d57a72fc677a9fc8f +0000000000000000000000000000000011f78204afa05c3717d3908dc7e4356ef96c426ef380b6abba3a5616d9ee01addeec3369967ed42e030c4b8ff9939c4e00000000000000000000000000000000175a19c86e7eff0f4e809a5105503ed223fe327ee4617f7f51257426fe408373899f39014821292a75e4cc4eb9f7f31e00000000000000000000000000000000052130dd3cd17840385db424802d869d7eac781365be25ba401b7b0e4025353c8dbf59e5997b5aac74c252192061299d000000000000000000000000000000000457f4fc7ac5d7d4fa07e8ed125df4c4e950e6ea926be9c04b6df3c3699a10e99af7ea8546b8ac70c3003468a75c821ca2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000005182a3af2b52102e09214d048a1a29d1e10b7ace6afbc4e6b1ebf16790be372dfb6d65cb8fe08c3dbbed8c5435a59a3000000000000000000000000000000000f2a1261463c09a88edac443ec3cea8aaa19659e8b7ec2e8a403dcffb1e50ebb3d07217a9ab057d8d097c075609c13900000000000000000000000000000000007d6525fea8fbb685fcf89bd772d48c406aff7377429fb199f27c3c3337f11f8e24c4d81c9026b469600d11e8cce51be0000000000000000000000000000000004b6d6102debaec16c34fecfeb444e7ba573b13b83ec375f14d2c541a0d1fa528ed6599a0eff4f8ca527c5baa579f762fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af000000000000000000000000000000000ba1f2b2c3f1c57afa0ab647d32af5d036ef18069a4abc9795dd9927ea274a718b67373230e337cb9374ef73b5e2303900000000000000000000000000000000016458ff2f5d600af9d2983f535c965a2a8aee48c0d95095bee642bb7bbab8bc87e3e7c3b52a787c53f0d1e00cb4ff32000000000000000000000000000000000d11324e812cd4fa65d20cf58f88a9bc9407657834d7a92f80bfd32c7085ffa2f9f78d7e18c1405a03de939bd0dbb06b00000000000000000000000000000000144a0f4d50bcb16942d22a12d28bf34d2b4c51512a3f11c130f1566aacbdb63ec3984df5569f41ef621f50d138883d0dda609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000000d7b138fe445d7b7e130134db653022ebb389075ffb62ff9faa544cd0fdb9e78e313d0b1cb19bd812421d38d1e9996d00000000000000000000000000000000084411aa2719b729a1e299fe8a710f767009060f1b2becf2aaa92efdeea8c91239aa5d2504c6e7ad2e3f39d89ef00c1e00000000000000000000000000000000017e86dc0146c9bbfa5ea1e48f49918167dda13b31ba73311fd5cfdc12845b95b9e90972a9a4d36203be8c5920f8de5600000000000000000000000000000000150e4b6fa9cd9a609241d1de8a637c6ab25207bccf8e5eff4a97ed633b67826135172880b118037649407a3e1b1a0661bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000ad7430b7778248d63a06e26119e5600ae97071fe8827b24440587e8bf6887b646f342741af69d20e243c9b45d7dcf24000000000000000000000000000000001230cba1a5a66197875240fe00c59b796ba1db5ea5653cc76bf43d6adce0db3a109168593beb39bb45688c1d124b9eb300000000000000000000000000000000144652474c58413cadf9b31715152052b7618e7093e931367a7ed0340e66d84c0471b6ec178e1730cf10e749e01815780000000000000000000000000000000009abdd0210f25d12146f2911a60035867f59cc341b35c73bbdf8f7a5a90d0bb6566c6ba0e868a3d62d3557436190f3f63222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb40000000000000000000000000000000018ced3cd0c169693368fcf9c3dfc49fe248f0b9b5511e9407b8634d8ed7b54ae2dc4ac6ffde8b3dea70ca86ddc89449c0000000000000000000000000000000002f6b227e699dccf7ab1e0b1cba4cf9f05c4dcaf9fee6cd94bbb79f42bc9598fa23eb2c653a7654db73feb511b24829f0000000000000000000000000000000019785959766eb8b00ac2600d87240f2876e049725680f4504f59db6642ff8f82d4e1b856929643906c3be7807a2443180000000000000000000000000000000018285acdf25a475b37ee4da872debba4297fc8731eede6b22be3b0dff12117634de44b84a18042852ef419c3ae18a46b7431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da10000000000000000000000000000000019c7950b01e15669cc1f96fd94957535f32132ff6a5ae788f6f660024c332593942bd3e9603f862756edd4f3ab17b20b000000000000000000000000000000000bf3a6bbe10ad91d687a135f4863ba0332e9b04271d437a6a4770056e6b1ca34319dc895f9186482bbbc815aead03392000000000000000000000000000000000a3ef4d4f7a15da04a91ff079cc40040993a90e9ea21f53e31f7dede52dd513a97ece780374c5f3aa8c8b2e525ee31d10000000000000000000000000000000017749fc7761b06432632ac686d93484f08407504e58b04b3890cc2101f15d21f46ec0dc1e9028c8ef8df10f9ae929887d474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c730000000000000000000000000000000005a36af876edfdf26175c185c3ef005530e02474232ea659f5cf251c5de5721f1b44a25714967d283525632789331d2900000000000000000000000000000000130a6f5edf94736477143b1efc316f131b36d9658c484821be08e7f5b9c93f60cf34042858664db0ff0240addad8782f0000000000000000000000000000000004fedf49e6d49c074dcca96c01607da2105d8053861b4c677a69cff0f82e66a2a63f32f3d9fac8e6c844a1f77055bf31000000000000000000000000000000001528541de3a9d4a216c0e60c31d2b7c7cb91b839fc31307cc70f18e9b87b92bf5b9a9dc4eaacdec6e6bf7791e547d8a2976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd10000000000000000000000000000000013faf7b2b8514f77021d8927a3b63bb7c57785e581f40ca82882341c13a9daf062a26b668844e58291366ea6ae2f179f0000000000000000000000000000000009060f9e1047f15f175fe95cb0914f4941bcaf071f24e856eae6f36263c812689a9217da277613c10c8e254a0933c80800000000000000000000000000000000154619e4ae3901789ed3ecdfc76069d8026a3e2cf142a144e8b58482233380690e378de6b81af0ed9b6536da1cc2a30b00000000000000000000000000000000040c1bce922503699e1fd5ac67725f11d7f9bb6903ff9204412f65355be69d73cd7330a3f7bfcacaa9b078ba6b9a9f839db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000103d91916d537379d6d8717b17ac5b7e9fedd98c24890b51c027cc086458259767d989b3ff9d6adad72bf977e4d378f400000000000000000000000000000000159c01ee371622378339518217dfc0570178aecc938b4a008dee1a6661ffa605c0f1472c107558ea791e0959d7dc1c70000000000000000000000000000000000ea3e10cbc3a55ef2dc7bde7a2e80666557e9e8fd9ce77e2e92c2c70777afe43c23072e263e1def56cae4b6d3772db96000000000000000000000000000000000cf1db638331c47f9080c04117ddab4ba79950563810d50e04af819f14ae0981f6e1e94a635fc90226c8d7beef0844354ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce19970000000000000000000000000000000000095c82c58182ae9a1ba14421c2966d687f7225ccd192b24097f997b471d13b46a048202712cb2d8b1be0ff40755dcc0000000000000000000000000000000017410aca05935a06942f673d1937a593423cbbd226f6707c5922306d28a60396baa08a941122dd4c583331c9481a734f0000000000000000000000000000000002c1d3a1262ce8aae42a6ed10d8020c31a468127e1a59d57d2d409ca9d14143d9fd21353b260edd8b387840580698846000000000000000000000000000000001512e29256b6b9f5a7ba4f79dde2c915b162e4881856258ac2050f02868842381518da4ed824243692b131710d7201f8ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000ff83bf1d50fe35bb3d1bcb07b02d78a1b44d2e0c6bf82c600feec3897fae8b93c0ef05006c1322af0a732392dad86e8000000000000000000000000000000000d70c4957cb3615027cb950e4224d41849b9ff1b435ce936384fe17c4d7bc2883fdbba5123ca0c0c010651500557e1be0000000000000000000000000000000008b16fe9af45fa913aa7e5d01b5b58f946004eaaeeeb493759a5bc2b192d2dd71af24ecc5c6838b5e267ec2dfcf5c17d00000000000000000000000000000000038ca027c985af3cf60cda13e770fbe4919d3a5b413763c8ad155cb4903312822366eb986f2ec9e0804594ad4894e468275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000003a313d6d41f1ebd6b98b2061a2d85943e52d89e4b8680611d41ea182385e154da24248faa1563e6ad79172f91a8763f00000000000000000000000000000000038d9388fe9169710e1a205ecfd03f674b47ba2275794469dbd5f193a55e00765c8eed026363b41afda417bdd8910ea60000000000000000000000000000000007a75f53d9b8e5eef19ef6f5fe8ce5d5308a1a7d02e0bf46f91a1e0cb22555752d82d8471c123269050fd8f35a272f600000000000000000000000000000000011f313127a036403652fb2f83c5122fd12c362ecba2251bd6c357a964dd758eb2a2c3053dc668b9a4bc071898d45cd46ec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e450657331e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f70000000000000000000000000000000007860c3403607d4e13f738357e18bbcc4df10fad4aa25776f84d3c2758624a83aee0996146ba17a812384e1d67a7c54f0000000000000000000000000000000002169148d86b1f7a0ef75d9bd19b6d7cd66da4293fcf33fed9241544dc2564d980161a6bd959f3b43569312bff7a23cb0000000000000000000000000000000001897c121cbf5e82424cc50078ca7143a0c670f1217a9180cd2a4700e06aa895cf84c0af94b7c04bfce047a7d1f8443100000000000000000000000000000000040c1a0c4257f90bd83fece3c9372842a148132d2dffa956729e741ce996d229aacb04387d51a72630329230020b2235f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e00000000000000000000000000000000058c001ee1343c6cde55bbdc4c538f5d14b0e8c199fb822f080ad96ee764bd1908f92260ce60cd521919f223301ba1220000000000000000000000000000000000f8943c35e7fb8b58963719f1b9820153e0831cf81dd208176af7527781ceabcf6ed2e2276cbf374e0525952bace0c80000000000000000000000000000000003b43ea8c32a13c014b05326f7b4ad5b5fc1bb2367866a69373ba91402f4b45409c6d034898e8b0ec3b93c2878d59b72000000000000000000000000000000000101c371ab4d57ed2cf17dcb731117b1986bffc586529fc1edc630de1c6f4fdff1e10b0895907bb81d2ccd3eaa96c04a67b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f,0000000000000000000000000000000007152d538d0f750901466c1ea34a16e7b0e1296a2a3740568812587affa5c0c76ca2055804e24f3415a403f06a717c0e00000000000000000000000000000000119c0c282d22a01524d87eb850789c4816e7dafdb2782b57c32409b1016615beeee2067443835466283543773cc8b427000000000000000000000000000000000d68137c3df081a519747c044950c3231ef82295eea5b7040843668195d4549c8ece4a91447e0ec89530bc51277535fb0000000000000000000000000000000000d81a4fa2d32ada3e08a7bd4471d45a6afd2cfad5bbfa3d378b1df2e0749f9b05b465be61cc9d1a0f4abd56dce03dbc +0000000000000000000000000000000004b153d6554c9879359052717457179d8318f8127eb73edc1d6ac2efb1ea9643c4357cc813d1df49730b77f995d6d449000000000000000000000000000000001533a450737b4bf8edada15446cf21ebf82aa7bec7943025dccc4784e1070fbce430699cf3a37a36a3ece692ce87639c0000000000000000000000000000000017b8afc300bd70a3221120f6fbc37a8e6158c48b476f00992c6f41808036765071bd0a76f7c641443b97ba523153947e0000000000000000000000000000000012f686b4759a3d5db2325508f148bfd6217e027fe261d3ed7b8fd0526036044dfb563e1c4399ec266e140ca372120e289f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e00000000000000000000000000000000061c8288b7bf2856c075a176d1086fc49f0359ca3e7c1aaf5f151e6462916a4e1b69b6decb18823759b620f7593079c0000000000000000000000000000000000877af18cfa0d029e7c9a5833b346c7fdec06e54d9641d3953d3cae0e8912bac7c990f8864c2adb6e576442c634865b6000000000000000000000000000000000331490f42993de3ce7cdd53afb4b310f25881710a23f601ed95961bad4e9cbf57d3077803908a91b65fc32d1a84130c0000000000000000000000000000000005aec079da804fc572bb8eb867acb93a24ffb6611eba920d2ce799c4c80cd8e73b3cefe989885167ab685365529b4f2a8fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f3000000000000000000000000000000000ecc7abf4f6f9692cf3a118cd01abaab4754c90d1a59468d402bd699992800c2994f47b2094878604bf7825f125133e4000000000000000000000000000000000662396427cc596458e880bb8a43dbe046deb85601e3c64556990de36e8637e8ea3b142a8195762910a83609ca311c3a00000000000000000000000000000000198b9c52be68d073910f5b26bdeada9e9b308e4541561a8ffb21fd8e69ec9d93b01ec966fba65be27ee53d4857a43e120000000000000000000000000000000005201975985cac810248e333ca714cbcac0ede46bb915d8c857837c80c805898d0f9ca0940819878a26d269faa02cb86128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000004e61ad4818ea3c98ed3c0d247798a1a6ca6bcb35a1cb60bda9394272ec092c385661ab93a42af439f1b55ee8b9c0cd4000000000000000000000000000000000ee0b71ebf39e4009bdb310fe3b555cdc860abd47a67bf481ab36b5ed0c00bdca8082abfb75691d45e10c2f2d777be19000000000000000000000000000000000e9582e3b5bd580f3ca7ca1f58e39379918f2d04b82b418a91e133117a9703f7df4aad30d48a47e29aeaccf5b8e33559000000000000000000000000000000000113b4c068fd040cd3300a2d1ef658955b014e571e7c77594edd31968037c1fff241da88e7a88669a569462564e28cd7d45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c206000000000000000000000000000000000d8b0bf633072f19db61ea263a1dbeddb326738396caf1196e31e2cbe99a68e8c70f8db13cfdfa4fc4494e54c1ef28210000000000000000000000000000000009ceaf2a0c63604afb8a903195933fd1ada0e5314255be3d74a95679c7a7845785e22d2c0c206f3afd62110ac9810c2e0000000000000000000000000000000003a135b405f46ae3f5cbdd63f4964cdc5014c9f3405c2062ba17423dcd22b8f2011638d520ce0ec7bb0cb5b03e8ec01700000000000000000000000000000000066eafafe1cea67aa6de267c767f49d4a3fd44c28d45a920fe9b3cebdeded883d8960f5e9fa4cc179246918942b1428d28f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c13220000000000000000000000000000000017e37a2f1c892fdc58ac3f72cc5a5e2b7c0c87333afb06de89f7a84b1267695bcd452925fb2f15f8b7b20aaa85a6b5650000000000000000000000000000000015b2919343962337a41b54076d6735a093190e1965faa33eee800f5eaa43c35f349aaf93f19977b6fcd19360b27edd6d00000000000000000000000000000000161afb1494482f953007038557c847e2cbf84c57c5f5b806e3b0178e71e3238305f733943bea7ad6f2bc290778638e6a000000000000000000000000000000000c27a2170fa584863697292e626e2539aa15f3c8eee65cbf1f1b7ced6297248d059fdaeb9c955437a51cb016d1ee97c3e6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000313a30edffaf864d0f1c6bdafd7d1e563cef434d45e71489e9f9e4cc6700e44991a99220f53f0cf5e7de5f6e4098bf20000000000000000000000000000000010429081ebd2ed6fa07de6ab0b7bd559a26a43df99fca0a2252411b4554dc69821ccf3df1b05114da84a616ccee0a9c800000000000000000000000000000000131a31442f80da4621f7691664e9f8b467988fa039bd086a2d64f9810878b557614c27745b2e821016f648ec36ee797d000000000000000000000000000000001160cef9f5e4d022baeacdf10b3bf9d7ed5e50627a99e29df1be3667cb872b2af333f803bf426314369b490c2eca642aa83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000b20ec53bc643bdfda1e3947b3773d748cce1992e2ae27c6b7460d90d48e08eb9240879a5a7d3dc3189f486706438dbe000000000000000000000000000000001024bae4a7f71d3d2fb8246e82d95664c4ee8bca4a380c293ea084f749911f984aa4c6f266ecfff69c4f57e20c0660ca000000000000000000000000000000000b58472d81a9f16d2fe7af87170ca0c8c51dada62a4b2a713cae053a0066fd268283a785ccf269e05d8873cd686d2f4a0000000000000000000000000000000016b68177bce92fedfbd90cdb752bc332f46fde6673911c016fb9cff4912d79d3267bf629c33097cf8979f2b913c0936d4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd37000000000000000000000000000000001030d5791bd2a27469d242c62403883ca167303d907839e608acd827b4118b752840a4eca0acbe5df0b447d6651e517800000000000000000000000000000000106d65f922581237f779ba3e66608729dfddb2c487bc927f34e5e39707f2c8a82e8c96af68e3257c7a9876a05a1b01d800000000000000000000000000000000115bec40b8fa914305b1d5a85b65f0811517d36839494ba69c929fc2422f7e8b85d78df4e1687ab0087287eff29c65430000000000000000000000000000000018d78a75ec057cfcf179fa2ffa7dba79cabf6525dabd69ab95b23dc8f293aa077e46e562caf447dd0913ac9dd60ec76b29d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e600000000000000000000000000000000023f7736d6de94b08d9e9efb6f32f8c17cafb1e1b9b1f3db6e58df72a451c3225d11f4304eb0d702b07a7966f95a11fb0000000000000000000000000000000007b3204f258c873a6fcb48d0b36c98ed5f99b424cf4f92a028174e0e93db2af549648ea95fa8c7bbb42b2a10eeceaf8500000000000000000000000000000000165d6e769b7df91374dfb44e18d43e03ae12ee10d8a618a20f67332cf96492ff514eb7de06ea53096e823770c686c32700000000000000000000000000000000012e69ca1e106411165c06ca15988362de583c4a05425e2f4aba4c14cef6d8d04c52c87b4fb26b1557801f55b02ee8ba971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e4000000000000000000000000000000000e8e6bf1c8837c31446959242285e9b85978a5349e1f0b3447e380a7bcd6bce758bc6192cb880f9c09d6ad4a0ee36eea00000000000000000000000000000000199b361ad0b435d7a66b46a43d06e5898376a6c260b68c965f7b186fc75d2f321bf883646e7551eaba03181907d3aad1000000000000000000000000000000000a76e3f399f31cddc4dd4bc22187a68fba31fe2371291ab515d22730d320ae4240911c755750f687c7d26aed09da4210000000000000000000000000000000000cbc8dbc004b9253ba91b2238c92bbf7883360c7ce39f6e15592a8668654950a3fc5a94cfa97f5ddc60add40c32a3630659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e0000000000000000000000000000000005448b623604262a9cf1a9a292c36738960e132bcf0ec8e61a510008c2ae0b51b31da25f2bcf0d7c0d4ce15b1d7179fc000000000000000000000000000000000b61df56ef891bac07a873571f68fe43f79438a31038cf8ff97393ba44cc47408e5a6d64e9ebddf0195bf914f141e668000000000000000000000000000000000d196ced22ddf11132bbebf6c85bb3006a194cebca975d74992ecfcbac546f0f25a39ed5d6100768c1f1a791f3604d12000000000000000000000000000000000f727cb947849d2d7b046218f084283e5513e8582229085f9f98fca522879543429cb8ab435aa3dbf01b68ed258d82c112ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c6000000000000000000000000000000001044bcb16b3384a1f350cbd62bae568c96932a364c16b34d91ab9b1035ddee93a02920ab4dbde2c6f254031909dc3a450000000000000000000000000000000004a29aae48210289e5f588aede0756ddf60724b8ac54de5d9159ea834d5da98b7a9d09a6f37bcaeaabc559dbdde58b6800000000000000000000000000000000112ca953b5ba652c715fd20e3b85c5bdfeaa7d577aa49aa4656d142c9c2afa3d8aee151338f59a199f3c0c3f6a430d6a0000000000000000000000000000000001ebc7a17da7809f9e744cf7f13fc437de34d3472f022493f58bb979e2282368f989ca0982098a7c377498f1d8d32583666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000000e7c3d6bef4b1723479ab6724cf7858c221993357b194e5055db96b8168f8d78f72aaa4a2046be17ae9a7eb00695ec510000000000000000000000000000000015e85e85cec08133b86738e1f7a738de455930ffd5073997a1f1692c28044ac00b634b90eb24938cab56e286ca0dfaa400000000000000000000000000000000164646a4767ed69f9280f96be9a7f988d17c187162554239797436a0bf4c4ffa7e4f8387c3d2406a7528c021f56081df00000000000000000000000000000000197b1080bf3ac3ef7bd6123a55f20f1002f366d4efea9e14ed92fd2ef147e2b5d9251a302a85172235438bb2d35943a740a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e40139000000000000000000000000000000000db7cfdfd58a6ce9dbbcd5d65cbf22b5e1a81acc70f1c85651ba962d61fbd7ad83e5524fb9aa019c6bd75dade96f7d4e0000000000000000000000000000000011e269a390fd15ab1d52d38de78ec97eb6202604fab02c4598ecebc7635ac91ee564e751275a485fb43b933678f11fd8000000000000000000000000000000000b8597a00d2401664405dd1fc7d69786353c86cd4699af981fe869f266f9087b00df22a46ac34883173bead870798f650000000000000000000000000000000009117a49b3f2a8a850a0179b558319bdd19a5f1f4a45af0ccad0890e63b222d028536e9bb612093cb3f1068d262af90d662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000002e1311abb9df5e4d76959276b6f725f13728844f8c7dfe5e25469cb95c6937a822282b3baa38817e24a6219601132bb0000000000000000000000000000000012820e6ddc50e19a8f98c15013ecc38901a4ef8ec2b79b85c7f7913da24404afa1c79045f1318cdf271028126f9420a5000000000000000000000000000000001794e653c5673e51a3ace631c1a1265dba07fb74235506b2149d42b90eb16afc26ec0ddc54d03f7ba2dd6a2503971fee00000000000000000000000000000000112479bdabb9dd057b325563c666910c01ef66adf47aa32f5a41bc9cb8234750985c266fcc329ea3704e2b8d9b15bfeb59401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6,00000000000000000000000000000000168c90045dcccef35cfe8eb642924ec2629db886366fd9ebc082019690d103627865f0dc39ffdd2167111f68d8d03c89000000000000000000000000000000000b6f0928a32672983664ad15252b3f145afaa04f11d5f43a6169a2fbdc0b0a04902a183b25e38987c45579ac6d11011f00000000000000000000000000000000195c4d796989630f85df4594eb8353d44bcee76d82b73ff7a57069466337b49b875b3c1418d22d79716ffded7e704a6c00000000000000000000000000000000032db644ff8ca6a3b1ac7bc51ff783ce0cdb7bee8b2c21dcfd3adb56a3e210390756211f22feb3dd4f706e13e5cc163a +000000000000000000000000000000000c675cb5e90e45300619be91c752a5831ec47b4143c28330422cb57139882e776c1c5f000d6032cd87c16ab3b1c08ee0000000000000000000000000000000000aeb4e78724d46a55e5f101564bc05e0a1be1d80a781ce8a19607322e82c7ee59db9f53ec34c70bef0766a5b965f54b1000000000000000000000000000000000933e8d7c2420cc553afb1c88b5f64c7a39f78272b34b5611972dd5ced3f639ae2ed2aaba470abe829be6ca6d666ddaf000000000000000000000000000000000ac0a9b46323ccabf4b2024e3a5b4717cd8de9ed7de8a78e33a38037f802651a4b43380a746890d93289d547d94b61bb9c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c000000000000000000000000000000000196597114fbaefb8108c185a85d0fff0f6bffecf056902b22d61cc70b49a747bb35638f5b663830a8d2ee15df9fd5a1000000000000000000000000000000000616ed44a5fe69da994e2ada03a1e09065964223333229f5f30ba7a452830848f599ee21810a95e3659cca495897bb710000000000000000000000000000000012d0631e524ee9d3c776c79137499f8c9fb752ca93e92497d89973033d60971da23f672f140c1a753b4d00d08a00babc00000000000000000000000000000000111159e95d131c8cbe8df75853fe9b3f24013daa083e57c5b716e77f6fd3872dcfe0156382c9d2778fe886621be19973ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000a7d253487591fbed97381b3a430404b87aac04073e5931ee0bfd9ea6e0d38a41090c6dc7f6a591336fc58a97a3bea8d000000000000000000000000000000000647c67f1816ae6fec39033c3169eb1ea89e5e20e755cfdea33572d6397e7e87635c7439eda4912361a32de313893206000000000000000000000000000000000e0cbd54634d070aa3c7a503df1171a5cc435d050def17395707bdf7a61cfd539348ee5a4c29c7845cbf0e5df0531f530000000000000000000000000000000007d006601dc1e092a616eb470be35b5d32742dc6a2a4d71cda8865f071dbba9d8a3a8cb10b486253b1633e4590e716dddfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000c502a19770a892b2fa1ba59900a36c0ed054a8bfa0c4e32bf471b90d0da9edca6c06b133c8f12e233b104262a81dbe00000000000000000000000000000000011801f159086d07833a691182595a42645513d316c084b2841445c4a63c6bbb402664a9a9a100e8d6436337ecbf398bb000000000000000000000000000000000f2b9bfd8ef6286bc41e9f47ffdd3efe437aad553c9da02b3c22ca04b5578d634c0543a07bea966bedf345562218c2190000000000000000000000000000000010be5ffe0cc9f580c74e027aad09c213189fa4b7aa92160ce813b8d398b2e2803294e1a730cf5c891cf1546c6bc91414f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c300000000000000000000000000000000132cd5220c125759a18c31313592eda774247f97b5134111b01ef28dad5c3ba4d3f13d1af9076d663f7e217258a6fdaf000000000000000000000000000000000f06a5b03daaf8f92f9a302f06413044ca0dcf2be81d9cf016120312fbd41b273650fbb542d419595fd2815a809c4b960000000000000000000000000000000001b11acf12cf46e40554a1d6a833566cec1b2750f3f72ef77496477831d5933f477d59463ba19c03dfbbbd02fcbb680b000000000000000000000000000000000b2aaf91827ba923c8a1c2fa1d6fb92384c9f48f8f77273056b94245114d1f3cf66fdcab330673ceb2e9dad6c1aed0d4c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000051858339be99d1271152bb390e9a2ec0c0760b7686804ba072c46db3cfc4472404a9f87d868a28f2aef16c9e989d6e90000000000000000000000000000000019a33f21d0bb8303f540bf26816f145360bd1e9a8229dbbe7981f1cb5b099e814f2691fadbeeed8e4c4b772bbd27e60600000000000000000000000000000000073eeb49aa7e601732dc0888ae6b0f5e8dde3d97b818155221f5ab8c599eda75b25c86f15ceecafdfb9ee4abe3419e10000000000000000000000000000000001507073b97d494de26e70f18bd1723d931cd2a88903ab6da2aac3b80fea78ce75caaa9b99375780d759fc4a1683950bd458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013b5422deb0e80bec71309d03fdba007eed33c3ce0fc6d4f9a0d063136b3b85a6fce90ee59956a9b91e1caa519f813e8000000000000000000000000000000000829a11eb50f3bb1a47b72cfeec9d1f63e02b9f7b2592174c481ea7b72a121645ecb36b3d1964b082bc6c7efb4483a180000000000000000000000000000000003d3aab53814f55fa97285af2dc6d32cfcf5a08032d2c15ac83ea036603e08a53e0d2b8d93a25dd969937c113e78064a000000000000000000000000000000000c938a68688138149cda64f168ac1466c401196eaaa44a464d9e345c422948767ad1e25d1ce4cc5996ac5d5dab61516b804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000001ab96f0b60213855fe221fdbe2fb22da6bd6cad8bab8ecb747c9528d3511976236ffefb34afc462abfde13a99503cb900000000000000000000000000000000182fb121778cb002be3f90e2d6837a406edbb609bfae8fe59837aea6f5f6131a10791f92188958b57059b7b9a9d3a24500000000000000000000000000000000159cac269098d223ee6d145a4489f05875b6a546767c023dbea62b3cfba9f8518c9f4d2594d00ceac325f3d8ef551369000000000000000000000000000000000c0d2e4e7aaedec7e53bfebe8f7fe5115720e58768469b6673cee3473b08fb8cd1ebd0514689ef65d78d008889e3ed296f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000079c40bd7fd2ce0f48806dd2e88850ba988e5adb0cc5120977da8110b07da264318fa034c0c213590a2616f0ebe40f21000000000000000000000000000000000905f41389be39361fbfe7641394d30870a079f230dacef89149fdcf81a4d1e0e10b9fa1c0c3ecadced9aaa19fa9dcc800000000000000000000000000000000192f50e08e497f902403df40a504a1b4b82f1957572a9ab7ef97f5ab93c6fb876d8b08f318244cba95ad5200fc2a6e34000000000000000000000000000000000be7ef45a14871dbb344a69c4036af4f994a22ef14540377d1144a92978a23c2d678cca47cbc18e8c036714112d11f7cfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000018d7410f0105ff03cc4ddd87a6e0b65ede4abd4609db5ae53720851c90255757e63c6482de4651eb1d3669b1e1a2f8d9000000000000000000000000000000000d4223be106693a672da890b64d2653135119983639f7052eb32051c34113022080ce2355a93a2f64a75d8e0578b2f95000000000000000000000000000000000764780391249d0c987270bd181a44f6260ef82eb00c06585db7ef09e8b069e46c4e0e659a081ab0fda491534b71b0ba000000000000000000000000000000000a8546031e6466ae43643462b7617703a63841d6d4cb0c09ce63b2fbe2c2ba7cc35367191d0313717b1daa665bcb54551aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000001922491dee4e0f29a1dc090c9b48fe8e6d70c3441e532021985932005b22cedaeea7d9ce1796808d756b740ec63f8ca80000000000000000000000000000000005b34dae0e630be6a59ccae17b44eab4e7f10be2ee700bea15f9771a724f0979798617e129540901a8aa023630a446f800000000000000000000000000000000095bdf612289258b31cc79188566ceeef6fd66858b4dc060864d378cbbb69f951e9c6bfb3d1384014507ff29f9446f410000000000000000000000000000000019f06f11a833c06c1c9227255e3a1d74172e73b06675c547844065dbb909ad66bbc150ba396fa1ba22b7183c0fe80e96c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa2890000000000000000000000000000000003de8082f828ec51e23c864a16147546ff60b5fa71897ff4c120556af5c6616bde96b6e53fa673cd1f8af503070bfacd00000000000000000000000000000000093013f75b6a19b5433b3b5ff044384ddfa258420c80fe81e0424e3102cbf9e550a946e56ba9746423ef745e33da51e7000000000000000000000000000000001227cfc3e9a8d6a71738c514c05766ed4f1f4605198f5a3ad8309c0a49499e4ecd34ba1ba7677d6d90203e54d7611807000000000000000000000000000000000a635221d514e58170ef299eb7f5b679050ee24c589cc7e348b2905a3cd1b7bcf2010cfe168f5aa60f4bfe15e59b4436e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf0000000000000000000000000000000009e8450887137cf45b04184b3c6fedac6676cad416a7646e9980dc99a6d6b62164dbdfae7cc20edaacb84432627e6e550000000000000000000000000000000002acbd87ddca9dc775da01ae026f1c60f1cb5974ce40caef80cb0d2eb7839777c1f61eae0472c7568ec9d0ebb2ec7dd20000000000000000000000000000000017c295c458a9dd995d848e3ba585f8dcdec4185a953e4b8e3ca760eb3e815e39a8ff60416e1e6f974cf7e7b086ee4baf0000000000000000000000000000000003cd8725e1cadfbd80585bf5a19e086abd631d6787403edb4bbc785d1a81f6108f451ff642f4df17dfcf94dd6107352bdc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e000000000000000000000000000000000b3cc99db523b3647937b694fc23281a74010079351b2c7d1ae4cc9167917f06c06e627c4ec44af6b09f2886ddf309b800000000000000000000000000000000001e2681dd123994627adc92e6ddd3ffb006521d8bb03040fe1989e4f709e4797d143cd0bb749de33c8109933c709e970000000000000000000000000000000017df13f532bc9894be932e72c609c0386d32390dee95dda45821bedbc1067043d46007b39b6ade871bd36d39a17dd04d00000000000000000000000000000000162db4d1e956fa5b5f9ef244dbc0c6d27718eca7dcc512d1d7b97bbfd2bd00cce7941d1b9a170da6341891773a729e9ae4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a000000000000000000000000000000000f0e4b606ba0a175bf57d4478aa286640ce4b5507f9f9e354fd96c45443333f6889a93012d663d78956bbfa7c645bb9d000000000000000000000000000000000d85dc4d733f0498fcb10e1e814eb61245203d6c1a46181e5a388fda2680640a1271a68d645f8fb179c0dc3107fb788500000000000000000000000000000000185b02140f6314cb62bd7977042ffaaec41ba8788d356047488004d609ae680c2f0cdc94e59a3cf90b6651298b6a81d000000000000000000000000000000000038ce717d08d367a9f882f2241ae4cc0e8a31418498bf68d05805db2e162d053a10dcff85403dc473598089a78dec27e03f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c,0000000000000000000000000000000004cb919a72e67c31b3409c28dca1d57833a5066c240d2889f5bbdd5540ab2a49484c2462b25da197ec8d93dc8f26ea83000000000000000000000000000000000e1ac1dfcfe22ed7ac52c701a7221b542ce72bf59d62cc49f95f8ba64c05060671098d40c83947dd1952494833a19b55000000000000000000000000000000001331f6ed8ea5ec9b9e1a14997c2c9bc9af2ca305b313e2bc5c5bd35308b7b451a362f8ad61d636dbf77d1b2388702d8f00000000000000000000000000000000186b85e656e45cb5ac9a2a2009353e45749b53dcdcdad4f378431a0e4a317652301f834617e14dfac9836c3c11512aca +000000000000000000000000000000000e6292b4d3031fcdeabe62921f0c562606b1ec6139b9c43938971d7851da4945cf69f39652425396ed1b2e70e65b9f55000000000000000000000000000000000e94bc63f3b8944ea6bd7bab811c013fd61303aa7713619faab85a271308bb220e2a94b26f5c7e4136a3d2761dffea610000000000000000000000000000000012313ef65ba41f8e0a57e9b810c13d23241e8430c6ab967a1a9bf5bd6308e89c135e00e789a5610694d146840fbd877300000000000000000000000000000000165ce83af7edc9e701eb57b332597305fedf4b939f3a13a95a0bb3d119c2a9204a4991388f7fb344ec8f15d32cab0eb5eb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea0000000000000000000000000000000005a88477765bbc8290b7eb137e6de78e62bbd929ca511cf0aa701f926440f21d33bcc6ac8f2ca5de57ee8116c685ba38000000000000000000000000000000000738074a9365c707190f882780b27dbe96179224103392b86c628b601e33b092a03e24a89bb6d1d1024862a9df6fce8d00000000000000000000000000000000188c713945046771bf852155ba412b4222173b6dec8320ffd1c59e9b36943c2c18b0dd3bd551b7b1367dde3e8031201a0000000000000000000000000000000017222294bacd664ec37e9b214407e5325eebe9753b430589de2eea13360783be52a479e2b0e9c5dc4907dd5f06a7fa822b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a000000000000000000000000000000000c97299d7e18f41e538b91b75e962c3ce4e068202271b40469c58cfc477d7820e90a0e91d647e8ef5fc0cb822daefd29000000000000000000000000000000000bd1e11a3646c499a240bad708f97a49acaeb653aa5bafdcaba41c1c9d32d32c516c94a3db8816e0a43d1b1eceac7243000000000000000000000000000000001223ecf82c4622653ce84460c39afe8a967cbd87a2d75cbee1609161837c15b522480c4731c9e6de9c5c392ef1db18e10000000000000000000000000000000016c5e98d3d17c723548427868e3e6d7ef4bca339e41acef19e0710459bd4732de4a556b22cbb49b823c4ee656fa354f1babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db000000000000000000000000000000001125735092842142cb5387f1ef8fb69a3535e1f0ccce59661183d3104ec1ef79dd87a7fb36159bc67bd73ad403b46c1500000000000000000000000000000000162caf579539574199d56f4e756f1532c66278a55b4f67f4f4090368260f46023543a8a18d49e8c5783cb65f93d750480000000000000000000000000000000003accbc87996a220a625e36d5cdf05d8c16fb353068ad819f94ba8223cdf6436f8d822719153bdba620a07c5dd955fe5000000000000000000000000000000000b53c8a4b62466c998327e0c5ad65818ea383650bf0977d98a8a94fa9653fba276f7781af9f5a4e99052ee3ae65c283d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000001264608a59c0ee9a26568cdcea8801cc8cf6616773bcb0971234b2d987563270c7b2291fa035c8f2069ac99e16c68fc0000000000000000000000000000000000e839d8d982d6663ca4552527f4fcab6ad5e0a444e7b5921055c774871601d342a151133ae15bc76c023b7ea643182ba0000000000000000000000000000000012ffd0696b7e29b305412fb840c596b66b77ac2eed936fdbe0562541e4de6b3166a9991dbdfa0f79b78b4b86f11291de000000000000000000000000000000001777ece357f82d7303aa816237a0dbd3a1398574f4061dd2fbf6b32af38a65abf5ec9bc53bb8ede932db9cfa0842d53a1a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000002b41743325db9550c3a84af80bc20c54b8b0b685d7f84d05d14dcabed2f450b91675aa8c5c650eb81151bcfbf1603b4000000000000000000000000000000000f3d3e69d475fe1d4259f18f193cd84a90b91589a6502588106f0a577d1c1dc4b2feeec20a4fc30b3e403d6ca9e03894000000000000000000000000000000000c10e2bd1335363fe958eb50981b99bfbadfd1c414830857b5257bc8fa6e26b50989d9adb5b3a2fa610b3151f8754309000000000000000000000000000000000008c825371319f4ebd684f76b567c4e9a389dce96068c101568dc8cafcc10896e3c20202b591a344d9a1c1be02310be9b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa45300000000000000000000000000000000101d3f92fe64af93468229608007f50e3406719572acf265fb8b2a7051525a9cb67cf2e46fc8e098cf081e73f3b20c770000000000000000000000000000000017b1422f8208c2521e3896820b22a65bb2a9b47d7fdcd2ce57196123c1ce43c1db6d00f236d7582795d00ef33ad6d585000000000000000000000000000000000e261500a9c64f5ae107d6ccb57fa9151f5321ef4e80f0e271515f1eaaa5e3714c59bf97b39acca41b15d90c0505ba9a000000000000000000000000000000000c08c955b6df18444ce3726711d29c2088721fa0aa6e317c52a05f73ec7171ef8bd61047174c74afa1dea804c68a28e33b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f646000000000000000000000000000000000985cdfb3934e0484805a1965984028d6c459654a3eea6ef66e867dfc737e1bbcd92e31020d5a4ddb7f8091cae2371f8000000000000000000000000000000001998c5682209153a261bf981e16bf1f7a6f8e5e566c1b0f975253ea62439e5b36c5e5060751f21941edf0d348bafd18a000000000000000000000000000000000c8822c1d6412bc45fea05faef33c65d5a6dd13aacf1279b9cfda2a2ee34df3146d45e3434ce8e5f242e9cf7d3ac27180000000000000000000000000000000019191b51d6664a3047aeb5590df2939b2cbb115ded70fafc2de4c2e8c2a955a957375314081a8838bf89d5a140b7b915f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b00000000000000000000000000000000156fcfffbf01ff3c8a97e7bd99e59327d38c6f7f1083d068ae158d1901808b3c9ac96f95c2bcbdf5f74b36dd8ce58d7d0000000000000000000000000000000014c64256d1cce124c01fa727482caf8ccf007e4ae00e5277d984f31a11ce584e7633565c61d47bc8accdf7c28bb266b200000000000000000000000000000000052dc9f7fce4859c852d3d9e1e77bb7887ffd35d4d550726632acab3d4303ecf8b3ec7f4114dbd590ac20d748570899f0000000000000000000000000000000017abd1e5dad7ee06116a8131c05c9b48defaa92efc636ee34a2970d701c02b6be0345a58cd8749e582ebd105c02f10a06440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af0000000000000000000000000000000018ce0fb077dfefd57f7943d432e12dc9bf92dfaa30f8341397ff8906b1abdf0c02b599edf85ba1e5bb6287aadc72d7a50000000000000000000000000000000019e5e9e3b0632ec10a26b7c1ec40248a9a8b230806c38aa24e47489a8aee5abb5450f6e5679e3f13c6ec7a79560689050000000000000000000000000000000006e257a74f45142817ea8044f403e98c99db8355d626c59e1d11c6859eb0dd1dc8af389f07562259c1f85411be6cbfe2000000000000000000000000000000000f463e345b004b1364894c6e8ab5d35bfbdf6b7544a576ed6b5c5398ca2074f67e2d80af1ff5b721fc126d3afadff1ef43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f00000000000000000000000000000000174231581338fc8c461c981d4949d18f5b753d27184ffb41568f11e178a271bfc69f8c73f2daed0fdbe5bdc7fdf8ef56000000000000000000000000000000001532474399d6a73501801e5f3fbfc6f13bdaff7a3ea7634568fe82745752ee15af23b16809be18788d295e044e29c05a000000000000000000000000000000000912eaef94ab1f3b3257b26c5e8bbe3f99eaceb8c7ae8da577ef98e24f3308abe6e6005ff674a2af01b4242f8ff87108000000000000000000000000000000001925cd635d0ce770f4925a3117721e96c316dd96708b096901ee04ce02e7b357428e4364cd488eeedf76352a26cc1d10a40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e0000000000000000000000000000000005c4a7f26ef0416f34750badcbbb3bce075606435ee7f69b3589e21e37491f0b4a7a98c825ec222848f5e29618828258000000000000000000000000000000000381c5f6511c9f06ea1a76ca84adab4a26a3cde13e0825b3d81899d6ad3191628894d0f57787f854aeb9e4c57fd15d32000000000000000000000000000000000bd706a5b5ef0d4ee1b679a0af90c217ddf9242b7c39523c39657962952dc14e5e07d02154e05693bad08bfb24a2b19a0000000000000000000000000000000009f28a84aa5bd39eeb09f13fc8770fa7e2e053b6f5d7e6021da77f48b9c3807ad917ac671de88b28dd343c2847c5e8eee03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000a8ff1b15ddcc3684b4d4ecfb53473497feb8a04660350ab84e5719fdb0618d61acbb555174b0900b32341154eb7bec9000000000000000000000000000000001464d21df798c0242ac6aaaf3c579eb66eb8cd53eb1e5ab2727298ca61ea8ca4c7cf815bf5c9f94c2b76bf659a4e2da50000000000000000000000000000000003a25752a4360c84e9353b7f1ce74d5106cbd637ec5ecb03dd0752660fe5c7622fe2d0475a4db98f785307c6961f14b000000000000000000000000000000000163601a86f02900d214ff8fbd041934189503438c557138b6ebaca8ce3c109af50ac28074223fc81d6476a3a99559ac565cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c100000000000000000000000000000000189597e6d618a20ecf9a87cc70b3e0eee69ffa4dba75056ebae93cfc3c2ebb368532b17d9f6c06f09e44d9f101397b2d00000000000000000000000000000000086ba610e490588e9385c8b6944c2bad1eb03058e927fb2f9740dbefb779bdf669a51af88b45985e8345b8cb168c13ec000000000000000000000000000000000db8b9cdd4a9bcfc9f7de144da0b33981e4dd53744cd260c4bf045d643a4ef5f25aa19edab7be0c7f8f5ab74a4b7f1820000000000000000000000000000000010198384a646807b16e2ed9186aed99ca3197b05964dd0348086f446d3ebb847907624f4e02f71a1e866d17a125e07e93ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000a293f07dc3f0da0da4bee671951175a4480a719d44cad3d627878ad2f17596f0dfbd6f43acc7a1f9857c5d1f453e5d5000000000000000000000000000000000be6382cc7a00d590f2aada3b4b75f01f8538caad2ade90227ec71e5661ae353e68789807a13f28b23b17dc0dafc19b70000000000000000000000000000000015a9ad5a6f1a511ffe1891ce260ce442996fe4d8515ca593e3e869cab9b18af57956b1daa43aec98a0281143b0c319fb000000000000000000000000000000001807a4ddb73a9aee58b54bab2b878bea8429cdc91384c8fa533a8c9d15c966350e892bdfce16d37a4048a763cbf25d71e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a30000000000000000000000000000000015aa6b865796f88ffe770bf25612ad27942213131c566a446dc149fcc70a018230f1cc8b20461ba2c55300fd27930bb0000000000000000000000000000000000c39c4f229b23c0f65ed720d655121eab50f695864959a2aa49771b848730494d14597eb85ba35743f64eda897f95917000000000000000000000000000000000ad44cafa754f06e45dfab801998c40e5a9f56e4add5c8add1d7ed9e05d12459f2efe3f3367cbcd161f524c714f7782b000000000000000000000000000000001437b1f1a1399ce2a860f7c6517b14a2db264b2602c1c57b8eb04e165205842b483497e98e6b6f8a62e25ab8b0e722f04c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d,00000000000000000000000000000000077b81fa5997de07738e1b34d8e17ef4a9bde516577a4290253cc759ceaae745e10a457204b9ed0069942e0b487d106e0000000000000000000000000000000015e79be67a752a46dd0605e9d07d738c5732b2b605457ce056deaa1f7093b0bdc91b4c81c4c5462a51bc713a7fbb86c3000000000000000000000000000000000cfd2e6043263bda2b97798e1a7dcb24c63aa7197f2749f92524329e97f93dcb56429d82c1d63c067d7ceb38e6c65b5a00000000000000000000000000000000026f352d2f93e6407c59d58742dbd91ced464a3746dc1ad9059e6bb9c146dc1e74235bd54b1d90bb3399865cd3102f3a +000000000000000000000000000000001387fb972f997ed0cb97a5ccdaa759dcc3c2c7f4f15e5cc4fe74685e42cba75e778772d795847b45f274d32cd4960de600000000000000000000000000000000150b1ad31a3d434c1cbef877fde2e105d4a047dc34e3889d21544c2143e7b41b8e0024443a774bd1e09438293860a43f00000000000000000000000000000000065033cee91f5c4d429a074be3d2a8b001892455a11dc708ea73c0082bedb1cb8e8b567a6ea68a1296ad2b80e4b5b08f0000000000000000000000000000000001991ff6fb57e8cbf9d228f1a99697f785261ebce9d3c1f592389fc860b8d7a069896dd48debb8cbe0c43175cd2ecfff5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000000a0fa2d39d868737b9a0526296335256ab4894cc58ffd80bc6334e80d1314bdf017c8226b41ea135f6adefd07650ca1c0000000000000000000000000000000004334f7985211061dedc794ee8931ded12acd39d7e6a6ef44a749118d19ce8204d07935fe62fb2a8ea4f68f99d7c5f5d0000000000000000000000000000000018850a3fc8c851a06781511faaded1ce0752e7ef66da82c2464eccdf78c32fae306da3cfedaf76dad371cfbe012f2bee000000000000000000000000000000001296ca0b0e368429b122537b096fac77d6367988956a7f6cf70c7193b7033ce42fa0cccb8b84b9c78b16a68fd5f4c14c2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000019deb7de7fa5254fdf5ef34fa616651ec70548187fb0bfae9f512e0bfe1f662783f06a9a99e434ced84229deddab9d240000000000000000000000000000000009c199ef916e6f6fe0677ab07beeff221a5687fa8da3ed3ad99a950b7f27159f857d1b561006bfffab551d240b764fb300000000000000000000000000000000148a211fb58b38072cf7c417c70d3ef92e9cbe22b31b2b626198add01dbe1ccfec32d333abf42140b9316312ac48aaa2000000000000000000000000000000000b551b57045365d842133e46814d5d0084248904960f8d2fb28e9623660bcee658582928703f86261cd70e95cd20cf3a530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000185959a297a8f434cb9529a1f7bf9009fc1af3d09efb0a9dce1b9e7d30699da64e4b1d32cdb05b068621db092c1eb59c00000000000000000000000000000000106ef21e9031d108364e93ae4b5d21b0d6d78c2e86e0f8a7af27ed3d38dba0192954e8c716665333e5dcf21387d3f2b1000000000000000000000000000000000185d21efd7d613c409b6ddaa66eed70c235440974b2a9154f3711e3969061461f8824b4547c65e9db09ce875512ca2b0000000000000000000000000000000001aa46b22451afb12962bec5c6309feeb4acefdf3c98c1ea14275409b7111aacf7c92a8e024d01d4dcbfb1c91fc445a1d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec049913000000000000000000000000000000000557370d81bc3da4c50980106b8e65ca2edc757a475194cef201c9edc0f50363cbebcf2750acab0b67e1020daf5660e7000000000000000000000000000000000462f1c1379be9bfed97a1a83a00428de63eadb6360393ba162af3762a99d7eae8549d0cee218e469e4997ada7b35cc00000000000000000000000000000000008aa5ead309fc703f6de980dd43c294530cc2b38b94d5281e9cd9b0d09f82f747a7107b700f1437f3abe36c01bcfed1b0000000000000000000000000000000014110a19d574f26e11e2163a981c3388c04854c5693e9033a474f1020d5f980666d84c60370950734c46663e194bf0ec42a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d60000000000000000000000000000000015beff8cb3c79098bc73dc1ea4b240a4e0d094b3dbbd51592df6adc9c9847beb436ec83df6c55666e296fa843298446a000000000000000000000000000000000943aca2a6e57e9897ec764ee2911d9ff0a59d9e903c70a8494340cef2143895e79d3e6c03af2d6461ca199dfbd0ca0d000000000000000000000000000000000b812ba87c4989af07af44f3dfa87de119fea28ad598cb8e52247cf41bb8bd384c0d8913fc82e4cc2878065e797cc581000000000000000000000000000000000410ed148d1e354653f9d9d17c50026957fb03fec64964f2bee5eeea966b430e77f7b3538d9f4700a673fa07d0daac6b7a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad000000000000000000000000000000000de0a9bbd63c59767938b555c7f9284d0885ca23019818c213a7d4f1594b028965da871cc5818240d155c05c69e4e25400000000000000000000000000000000079dee5649cc67700e9338799a9810d352a5c68098d0676e42e00bac31f37513944dcf47408288cb7f1cba121506a10500000000000000000000000000000000101a650e84352aaf3817b400da0aff40907aae3d2fcf16739f8ee8d5bfc62c2a0dd518201701932728a41134ea3f6278000000000000000000000000000000000f1f9dcc0b55d0ed327f667cebc052c4b6116fde5e3076dd6e447c3214d4c8847885be9547f95f341c42e7c7fa7e2c71bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b00000000000000000000000000000000148f706b4c93e739324e5db40d42025535cd33a32bb3f211add618c0e2022068384a5612da67150746896a2813a664e80000000000000000000000000000000007204ebcef495ca8232078fbf1539a4b46e89506a09dc008da457dee2792acafb6baac4f6cef2de15cbeb48bfd12bfd6000000000000000000000000000000000bf8900e48a4a56b653b1e02c3b9a7d81c2045dbf6297f1ac2acd69d1bf9e06480ea917e3a616243c3a30235abbc426f0000000000000000000000000000000005ebe0ddf4cd1aee76d0b3d03eab754664c8b36fb20ab1060900909e0e0a4abdb45bf74a0b1d40fece9bf73360f580bcbef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d0000000000000000000000000000000007824d1c48bb2cc0f406e356f6e52b66392f6203f49dca7ce03ae6302ce3e8055d071cd812f97481acc654b318d6cae2000000000000000000000000000000000ae89f9eb1abe452efb7ca48f8f939d835f9a79e05211ed9f4abee06b93e34b17d920ddbca3d8bf18b96c3705c1a064500000000000000000000000000000000119ac787a7f3e9b7ee34070aac1a769430eaa8cc838f1752b573ac7f3c02a9f490de9600c856a55448598b149f5392e300000000000000000000000000000000193a3655a80e6e0b1278730600fd4f645d54947d193484131176b890ac197702333ea847317568230ad8af1280864096392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000012f66629836f0f57bdfd9bdeb2c9b7d6d5dc55c586e15d76aaa04aef06722bc8ca156fd1295b3063d738a85b3e8746d900000000000000000000000000000000097825c5db7289b1b9e640d19ecaaa81ee59e5b9884713f6d312604d8ac367634a264c316d73a9cf63358c8fb15f8c5700000000000000000000000000000000181133d027b97d8e2bef308a93b7ea2a35824dc7d01a3ed2f404fbe12ba3b3e51d94ec86cadf3da7dc9ecbaa23b411cc000000000000000000000000000000000a28a609d0bb015e375e74c087ce426dd3c20fbd8b374d3817c626faa81469cfd11a2a4e418a44f4d7ca621d0564bc4920a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000000feca6f7e3cb286090fa3df9c5ebd10c06192fe14af58d46b827acf48fbd462f3f76d9d20670803946028437410ea52800000000000000000000000000000000183dc7085483bd05c27691c25588e33296fb610bddaac253af5b2262db38091650c1c3185d71a69d1a63770f95f381d7000000000000000000000000000000000189f9b9ea528bc2377ca3354fccf440fee059f5732dfdac320fb58541e74e444dbdcdc008c7b47681c05502f0b302f5000000000000000000000000000000000906162085e0e299a07e41b9d62668d4810b97d4be317bf376da537de7adb06de011f5f40af834593761b774771a80e4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c47258800000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0c8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab0000000000000000000000000000000001d04065373ce5d1ce47e00476f07708bb028040edb9ae7e8e00e2c6c460e1ab8b730ff510a25a3c8114c1753b7bf1ca00000000000000000000000000000000001c87217f150694a84a4e5aba8d188ebf7224e76b078dcaba4a91de6b4ab317966ce1a9267a5a27ce556c3386b086620000000000000000000000000000000003c8422590826e0999e7ae3ecba84edaed20fd7f1eba02b9daf1c46c2aec74d5fe63319047d37f5115f243ae0ddd4ffb00000000000000000000000000000000136ae093c3bd55ddaffc2494f3ba8176947cdf2f1ae408e7e786b23b6a65ba8c4131c83cd890386ba531b8637b3b042c8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef2000000000000000000000000000000000d09b92a559b8efe5224184fb4f43779d0b8c8f23587f4f74e2fc6fb1f94e8d2e0d591eb0702cf51a9eb402e79b46a0a0000000000000000000000000000000014ec2e4702f1ee1074cd1ad29791cf4903357e62570d16ac80c5e8ff73b255ee03a5ba070091cb2f984b2139de06a97d000000000000000000000000000000000d22fceaa48193756ce7331952a2d9a8057b67bede729e07cf8422bfc79f9ed2aeb99a9227af256deee9f8a6f227faba0000000000000000000000000000000015d9322c3a5a7ca404259c4cc7cb93dc3d46dd8dd9475756d2ce6fea527642f9230c7e94a804ecb0b4adec7963fa9cdcd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d000000000000000000000000000000000e16fee11affc6714c7fc8fc5e7cce44d8afe645861dd2f0b8e58aa93d4f0de9b7e73020a1537bfbb0e2c8327c4aae03000000000000000000000000000000000b7745a4aaa8ab4593daa61e375d55f9043fbc7385ff229889fca514562168a4e769c5eeef4d564b41cff28b4efdb7bf0000000000000000000000000000000017f6c5b1fb00746b50ee4c7c743ae57fae2742617e5565241d012a0ef6067d9ce59be749a99886ce9836b648525d2e92000000000000000000000000000000000a3be81720e80f6aa0570c89613c78efe95d87ccb374e7f77065800590bc71d23ae097516ae1e97b498cd233221cf717b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000008080a7d91caaf2470f9632575b43990a9523219d75994f1944979ed5b650be1e3c93eceeafb0875f66a40651f4c6dfc0000000000000000000000000000000007a19c4a6340e39230a33b12fe63e47bb0d1378420ec9e439f216699e512e4d70571a1670eaa6b60a5c899ac63360a250000000000000000000000000000000016898d22b2c123003480e3a01965a72de94cdfa39b20898c49e451dcf6a4727a1ebd629172aa1a1aa6897916cea192b4000000000000000000000000000000001217a373c78de9d3005690023b9e56bbed3073f13ca2408a27a3480578d8013fb9d3ee5cda95c3cdd091a5cc68d928da7b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f,0000000000000000000000000000000005829c932c80baa420602bf841ad9bb24fa25c61f33f5d88693207b81271c94eef54bb524aa830fdad8caf8c082bd4990000000000000000000000000000000000b8d184316c2471ec6875641ea83de4f9b7227041922415b38b07a0704d01f2585ec2701bb4ae0bf6a0c0522efc0c630000000000000000000000000000000001dd81e075620914254b38ca5a7287eb56f2f31f6f8fe02fa51488d45c7f4609bcf49972d0ae5ded76eed5a4c096939d0000000000000000000000000000000008067feba36999b58342ac54e48b0fe28245f8ac2498b60093082822d19854df5c3168dcd55ccb6b2cb397b77e712333 +0000000000000000000000000000000016ed84856b9f41be9bc6c025a9b79e2968e2ee6bbc27608093256c541096e2c9eda1159e6dcdaefe783aa59d52f28ee90000000000000000000000000000000014aabafdfe8c7369f93d5472a9c6c4d426e4b02c943488be993d04ed24aef5477f6d455f82b4af78381b8bd16f42b56f000000000000000000000000000000000af34789c6c923103633e5b1b9fb447b671ab05265c16488ca7224e49db21973487a5d3de4de40b9d8a97ac9b1966619000000000000000000000000000000001123a6601c5351a586f27f8264d4227f5e1df868a03e0c3df5c148cb523cdd178f96fbe52464fdab210564dfc22b29536f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d7410000000000000000000000000000000011b82d78cd9b53b8e7e5c14a7371f34f08546896bd59d1e7d8be15d21742180aacdd01b0d08da2cb24873ce75e166bd500000000000000000000000000000000161ae0d724085a6e801edf73443cca87995c2d6b37e962db5719f4c480cb830e379fa778fd2f29e75173e1c31daccaee000000000000000000000000000000000a2c2b89d00b7d19f2b0530889905c30cecbd4ed0b56ca82208d666e7576c32a6e90cf867ad87f19e4fd367a10c449a2000000000000000000000000000000000b65c0226743b573dad7ff25bf1885e3dec686cfd5da2862ab300fde4fc8fa9b587d0f2d11ebe1f6a6770bcaf2588f8f5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000012d4f299998aa897db9e3194244fdd1dfb95225e3271383b5cc296bbc51c4e1af52e849d8244f82421cd198158918d8900000000000000000000000000000000110638a2f7cdb7104de8fffe29be32610063bc656e13168921501e1614f282bdc9fccff4eb3c479a42b240a2c8014864000000000000000000000000000000000b0adbcbaedbedd376efd20a417bcce562b87b7449cac1e90d44eb05930e6f558b35ef755457305da012a231b5675bc2000000000000000000000000000000000db6fa926c7e02f633730569732fd9239bbacf2042599e79a4bee76619872901c6f4ec4d4fbf3f84143a0d17b167130ebbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce500000000000000000000000000000000166578f3087772545c0f47fe0b3efe32874d26463e4f262be65a3bb6b0fad7d0f779808f69362f3fe63c72f24ed03d70000000000000000000000000000000000a8e61e8193228fa1825cf14e94f68a5eecece9afb48b44871c5ad62510ee1fc4e9c60d5f2529b8685e6aa13ec91979b0000000000000000000000000000000008d25d81bc4bc92508c8cade33c305c11d71a06bd46f184b05dc406f0939f0e0967b02f15b4f7f6984c9fba0644ca8e800000000000000000000000000000000113660a7d2152346500a1578641aad4dac2919ce63d01d8ffa6dad72f524c888fc2e9d2876859859e47d8e884f170f86607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000160529ff217934c85cbaa8b347151539e252dbb502c015e8e45c128df2b8a737866737d5cf0eca6f76e4a16790cd02a200000000000000000000000000000000127f7b0e4f9351836db9c204386a199293955471dbcd7b4ee9186f0434b46dcacd1edc02fb46b4c377c4e62cec10cd6700000000000000000000000000000000094abac17b11600d7447f7ad0f21d98c14e439c4a4a6572b00c90e14d9fc54e85045d0576f74b054d384179afc0a70c80000000000000000000000000000000017165c32410a498add8e1dd55ae43f94be234ba3859fc6b4816d7436746add313f42b1fb49e0cb6c4b7341f0acd09db841c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000060e503ee1c5d3eae4bc0eb30fd86303a5c48c10cc7b4736d17b8774c78a8c97ee05b40d366b2cc9bc7781b1e4a192f200000000000000000000000000000000034e7012414edfc6a8f7b2c6049236b6fb77eb94b05d55b218851fc1e553514e6ad388fac08a24c33bea63ddabdfd8720000000000000000000000000000000004c832477a90683d417a00a698b69c643d6dbf82f5afbb83eb3946f8098d80de6f2d457c0a06d0051315f06e93b5e13b00000000000000000000000000000000048c3339996948974f2bac14d8a6b8430897644ec8e9cff9eb369557003aa2827a4f3fc3444c4df73663ebc9325ff317c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c00000000000000000000000000000000082466944ee7c62788b6fa77816094ea623d03c7aa2af249cfbfbf78eed26a76cff8c23c2295aac7ee1ef8dc84630003000000000000000000000000000000000a8f88adecc3f50d8eb329492f2c031e722f36627cb3b21415781156ef44954c5b8529ceed5978a37ae1248909d38b5d000000000000000000000000000000000e08f628aa014152b50a85bb6eb947d53c596d82c0d03594ed3b64c486b8630c880adf43fb1575b02e4eb8174a04034c000000000000000000000000000000000776844f28958d3e12a5c163dbd039e50df44b1c6215429381790175a609a339621475a5b9a06c3276c9177d2dd2b576c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e9590000000000000000000000000000000004477f153c0510d8e50bfdc2db69182c05d5ae9b94bb1880de239733e380e03d50001378432312b24b5bf0952c38396c0000000000000000000000000000000016663990dbe529a5658f2b3044bbd390ad430adaeffbd5306f758d86bd5422391bfa1d21e88c63300faad55e6a2d1d3200000000000000000000000000000000188f701658558033ce2c41101a611f74ad6d3cd075c195476bd2cd59a1a9dcfe937020737250fe418b4de435f8b3a0380000000000000000000000000000000013f8d3625309767841603329f56686a99e196d697802cfcf31f8b48f9c76f77a321276a0158a22b94e91d6907f6ff451e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd2000000000000000000000000000000000f184d22f3c0431b031ee0ee7ae9598ffb511a2a56f5c9f15c9a4b0c53af2a10d22a311805786e303e234239326dd74b000000000000000000000000000000001062725b8c576e79e314f6a56ef9c41f05a65d7d0d57d8414e2ae9cb1a520b16ede7e418d3a9413c9c1660dd7508d5860000000000000000000000000000000012ef02fbe96f9a191804b6c4a0b65b6024e3e2b1f8cff986f5a950cde9a32ad50d4f7a72804b2d18b93250a63a7ae97800000000000000000000000000000000000b3b0333d61fc46653a7172f5a813d13ff5a48056f9689c78c4b18b8aa3afaeb7cec305d98dd600786351338a2185a651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f2690000000000000000000000000000000010c91e1dda48dc528f618f01abbe01db1a7b6dcb0d47b83c7b7db3331f7156f7b2d0f081458241467b0078935a7b4a4c0000000000000000000000000000000006f87f782979d2adc02e65b56a4906e50430cb4e0913636e9aa0364535c9d7ecd3b9433358e00caa8e90e84b7705bdfe0000000000000000000000000000000004635089c7706cfdb5a22ef643d1a9a5021847646ef01ea559d1b655299b65cd76a73b04149adbac612e7aa756cf30060000000000000000000000000000000002d83d82bc9fd66c558e00547a8c25633899584c9b855195c00eb3c8742d22c601982f244a03f8e0c5c21caee24405481a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca0000000000000000000000000000000000848f10eeba8ef9c7fd0e679767f6b6a2392922092916da8f13573661f84ec97c65717e55c65526cedd59dc1e096f0840000000000000000000000000000000013781974518487de12661bedfca5fe72205c51cab461b5757ff14f319d081e7845cf8e099892ea85470039713e8e48cd0000000000000000000000000000000004cc1a27d1aa88484fed40ceef72e6bd201e5ee276b5ec27624286dee112ece767b37c6f1f7846d71cc0f4042f04dc170000000000000000000000000000000004f7335d6a1463976d9fd86e2baa45d08ec65059b14449ebe4aae99971c5666cdc6e40cf0510ae99dbce97ae8b4598067db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf70000000000000000000000000000000003c1b1e0848bbe37e62f1ebacef1a574400d5048f1e09d935af2052da29140dc4074175e4d6ceb7c2c071331b2f3d1d3000000000000000000000000000000000e1c84d6b20553ddc5ab09049ec488ea2839c5818e31455a7b231cd0455e2945aefcbdc6c1979821a80bb4f77d46e91e00000000000000000000000000000000199ebb31e8800395a9c2e103c9340444c97004186929b52de33cb8d9396e7ab8d5af3fe6035d4463701ea41e341f577300000000000000000000000000000000081b3882bfdf83e67d2dc42b211069a4e93c0f173263f9f20579128391e7f2de70335df949b9c0e9b834b6e574f2f8cc14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e60000000000000000000000000000000018aa45c6b3898a5fa618f87f9a08a7234c1b94fbe38e2297a1f9c7a2e9de0ed83023deebd56560b1928c012c14dd7a860000000000000000000000000000000009ab80da6c519aee8aa1fa68c35bd0fac78b55f88d861e8fcd445f629054325d63cc4241f61e5596dad0d54c94511e4c00000000000000000000000000000000105f8253f37f5538a2c25587fd33ea61fdc744a7cdf4ff23a55e2c66a39040d4de5eeacb7e11c0d2a483d59e7c3186aa000000000000000000000000000000000f6b10cd6522a1e34c87c702f58a07858cb753d67da9625155bd433020775351a9ec4ff879f91a43f63be1c969afe675ed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000001618dd5de43a6bcde91a6a03fcd88fe59d1c8c51d3d85cd44a1920dabd2608a0b17a987b76eb8f5b20c7f1dc0abb383e00000000000000000000000000000000198034b7ab8fb8ff267a52a9423da95bc587eef8684f18639df5db44e50bae7fdea5c5e5ef37ff14937f86cc948a34e500000000000000000000000000000000106d1f017da463176bdf55e3ada78ce70da4486be42dd0095e3a8a0f6e59ed503324565b717b45ee38d90dd3ad13c10600000000000000000000000000000000112d425765fa2fc28486b95e49db63346188fc5a6bd0b7dffa4430dc82703eb44d98d726edfa4a275aa5db5028d01ef530fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000001326581ac1a1a960db1ff2e8b89b1debaae46d1e2d0aa6ffc6c7398f207abb699ac59186ae7222b5cae3abe64cb61c93000000000000000000000000000000000218753594c63ebe5fe503aab4dbe1e944b24138948542c7c43d92ccfeba5854b7bf1bbcf8078d85fb0b8701b8b092fa000000000000000000000000000000000c3ce8c17f75e78a8c9980e9fe125290d377a32ac46411876ef011e169e86e1458ac5e71cb4a446f6c640cceb8d5617300000000000000000000000000000000176966eac1e20586ad2a03b4a1598b4db1d7c66be70b1b22833e4afe0e0b3783572f791ddcd4eb70a88f4acc28b6fc7a980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000099ac8430fa411e74082cf3282f9a456d3826a7df4f91ecf621e645a1abc057e1bcfaf9ee73f149bc447cf4230f2f6c90000000000000000000000000000000004e93d7fedc9e2d7423c9e111b4674a2bd83de28dcbbcc54ce4b324c96318a11603fc9ea385f1c02364ab1f6b5458481000000000000000000000000000000000bbb29d70fba5b12fadb02a24bfe3f6a5362c71fe5f964dcd0e01442781d0462a873501029192858027d612a8572e9d30000000000000000000000000000000010daa9960005562ca2d18eaf4b4bf081f194fa824cc77515c81b2c836627f21b732448f367e2cc1830ad0fa4ceb928e1619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1,0000000000000000000000000000000002a61fead6801f41f2f27603cf34cfb4b917f2f85cba1f9c684995227653c9dde559e1e8497234fba9b2e4c119cbd9ec000000000000000000000000000000000085f73b8e835a10bcb9312931eb08d916d2b93a1da843fa2f4530cdb26e93b5dc95a555dbe8e50ca465b162661ce1d3000000000000000000000000000000001442fff9019b5476c217ff725ad95c92c17b42471ed7bcc121e8a0506755ec279d4e56d770a322d56f56bc6a6b0a41160000000000000000000000000000000017e7710c4639d51c4a79c5a2791101b52742df202b1827192872f168bd21020bd068160a587fc501782c1301c231a0d3 +000000000000000000000000000000001213b5d5704c454845824994769c8b300676e75bafdeb95202001161aede276ab7967ea7362d38e97ca1484cf9c342fd0000000000000000000000000000000008c7c1fa04bebe5a1fb8678370563db63e7a10b30747c2ddeb4aabd4fc0ec93220d578b8110c6bfe8a3a6ea2820f0db8000000000000000000000000000000000c4061a295120a00de52300ee625ac46566464e6702489467316e8c182ca2168052c50b5962ff47285866c17d213fc8400000000000000000000000000000000086c153169a9ed1aba10a6cbebff4911b37907d6398c441ad47da17988d512d822ee36f5217355b93c9d6dd8dcbc8e0b119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc0000000000000000000000000000000017929edde8f9940826ed739bc9f59099ce76e85950698ab0140784647023f96afa064aa4a49b9728f496515a0a807e5900000000000000000000000000000000198d98f430384c1e7fa9e2403d9c3d2f81873fb7b204378cec95b97e674e10a1a43af97db0488209904469989ce80a0a000000000000000000000000000000000afc9b5138999bcef35613e38bff4f81cf532e00346f5205405470b2424622826c746ddf0369c7bdf77467dcea5cff290000000000000000000000000000000019ccc05724b3e9966bf918f01312c80e8422b697be89365b6ca00eb31b0bd08fae942e90a75bf9da1b3d264e416060f1f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001568e52c2760d895874527d1ac8597730578176bdcfc67aaf69ccda253f6616230811dac59bc27cc1e57b94b5743cb3f000000000000000000000000000000000a4ddeb8b56f105ed5f47a538052f3d38a23c0ceaa2dea241554e6508f82f47d32415ffeeafe5ae5664c936b78e07648000000000000000000000000000000000b3b335a390aa0090bfd6467d6cd02eea1ced347cdce3c9ed85dd46e38e9f2ae9642392c2875a27618ba8f2c555d5b190000000000000000000000000000000012baa4b29d116eec749353b7658af70d4d216189133db707e56068c8483af43ba86583862e6b39df13b88058536861b9e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001888b83ca28c244a6178377b4ee6844dc916e28c3f56312ecc0e29d08e6254dcda39a36ccdc317d1908303db3c028dd000000000000000000000000000000000f4b73d9316fee42d60f8de402a7d07765508b84d8f2c1be1f3f9e802ed7b0c6c5fece3db95d5287225026e73de98ea4000000000000000000000000000000000f1b48122191e1bc421881de831293a80566b9a7f2c9836f7718afb69592d59d2a714cfddf88945b94fac7a50b743eee000000000000000000000000000000000f1c6b052dbd03795433d7ad122473f109484d50245021c8727d252145e7db7dadc015265d1547f9c748409d74f5aa33f7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000011e8613c3a771a177b4b85f0c6f97a53fd7900cc23566aecbf115058d2863189c21be36dd5dd736f6d0ffbe88182b400000000000000000000000000000000017b2c4e8d8aad0a12fd7130789188bb63a08f2b243c8f7700599dd33d7e176f70f2b1818e56540ab3fa507878d96a46e000000000000000000000000000000000e2b5ad5ed3578dfdffa414a4a2142846b1232cd2de468725283e3f92b536d8ede74bacc236993f6f68a16fc6a7828d3000000000000000000000000000000000fdbf06ae4cfedc462f5913bba9bba2b5c86ecd0e298bf27a21317fe74af6ab15014c62cbfb617356548cf808599caf4b1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef000000000000000000000000000000000b48aebc6525620b99cd83979658a35afa233d17849bd0dcabffcf3b550f875a386b6c0b4ddacf18a23843629072c0150000000000000000000000000000000010432e5abf862d3be10ac5677b9f296ccdcedf1480e45de631b6bfec42f20edf62034f7205f659f11fe5a6aa9d882c7a00000000000000000000000000000000011702a3590e7aedd6948bb94bcc874e0b8d77a18126ed4ba3753dc98953ff941495486c14c6d801c71fca3564ded9910000000000000000000000000000000009faa427c0a7da26c92b451c61f5b5e8804fe032a4cfa014397e430882cbfcff81bb22f9c15a8747ef455773c1ef65b0a41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000061a1ee841251bad461f89c52196bebb1cb4463298e88abd62cccd21bbd325ddb33d1306ffedb2734be76c18d80c8dfe000000000000000000000000000000000d05a5ce6372ce34b0bf4b19d8e05aab74abc1cedcc35a2d1d4db38813d1e5c1375d63ca0e8bbf29c510a4319d2aec27000000000000000000000000000000000dfc57aa8de28745b8d28db3769ab5ea26b5115d3e59e51ff19af8ba37efacdccf763ce682cfdc77685705781c3924870000000000000000000000000000000018c17d87411c4f8e0ca51b3eb4c3765d3846e0d1b75574f8e511b2f3e8c5ff53bf7618959ce18dfd9e4c6285e88f094f63cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de000000000000000000000000000000000a0642094b89dc9c6c7c11c1e57ed542982bd246112884969d424a3e091ec4fd73dd40a5ac116f6c68216fd1e733cdc7000000000000000000000000000000000788c7a63eecd1cbc26ee6b14b09d0a3b7a17a848fc0551d50cb7497bc98287da2d9b898260eb678a8a0f446eef5c6670000000000000000000000000000000017a1298f90022ddff3fbbcca180e3f4da8760218dba595a067287a2473a6e10b93dcd54154cb64b6c078b083b42cd09000000000000000000000000000000000116e999b808dcaea0566c0fbef1807e160612dce91756b2cbcf4883b04a90320a0759bba21b41e6f4d8449b52e52f9a96a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f00000000000000000000000000000000064c95bc9c0e2be48849a349f16713791c37310f71b5d0613cf0706febeea3a56a0f0f1ac6b504524eba801e8b759f2900000000000000000000000000000000007088d2f41fc7e1147b92a2ee7062b9bec194d3a47eb9985ac1ceeef57e1a006571e7247a13dc95afcf9905be57e2a7000000000000000000000000000000000e6a0770f4315acd9e410fe58395ab8b20a08240a6948b762dfbbad3414bfca0ced4ec9da982bc9b8798b60dde78a96c000000000000000000000000000000000a70b53a6d71c83971167afe329ffacdd417bd7b228766851c3b43701a439f253a8659312db7e83a398142fe19332b527d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec000000000000000000000000000000000002486eaf9b743d3aa6a1f3e1174c5f213bbf3e3cc0558d63ce40e3c03e1c2f6e8508248bb649aae1bc92f3eb8118a2000000000000000000000000000000000042b03959b40eb0641d39117f7af50dc7ff048697a57b80723aaca164e2dbc647ffe78fea0a6a4c07671f7db6d5b2dfa000000000000000000000000000000000e141eab29f52b9bd0ee44861f154ec1bd30abd715935a7958a19007e789a41cdb0f4b9cf7b3fac0b0d4d77637b510d00000000000000000000000000000000002cc2eaf89cb7a04d425d878a30b5e2e9858ae0b2a2ab28fb28a6db0c7283ad861bb6a92067e969e5721b43466e857db8bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f63b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000007ecfb753be501d9f9b7ae7ceaabaa4fcb7b690ee04fa1a711a15dcf67e4422adef64a0f8118f93e67f24a2d1a2bcb36000000000000000000000000000000000a459e403d85972f7132641c05bb842416a7135009ff46b617bf0918e65cbbf33f76b98c10d901936e589bdf5de31ea4000000000000000000000000000000000bc6ec31a3ff92b4fae07cb73ad7bfa8423044048337b0ab9add09bf10fdf190a5f7996d157483d29fb29a681ed585520000000000000000000000000000000004c622e2bc606fefc8bd83c4a32f7353123205a6d3716b581c2c71360e5200ab069f60c256dfcb04b466c53cd61fc94470de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f5315000000000000000000000000000000000125688e44f593c5f585765f30e9fee5e4f15247cf33ac78ed1744453385f49ac61128e23b1569ea33d74b207a5e72e930000000000000000000000000000000009d77360ea37298fe971569230159967012c4991255fb5337ca6d58cecc3cd44a024a9a044ac98a894cc97dea161844e00000000000000000000000000000000056b2dd9569f0698c732367cfb217af90a3d6dc15e2555ce0aa845616e4067a7fefb304f6525b539555a0a685f0ec5f20000000000000000000000000000000009acb138abacac351e03f7589d4bf29cbd331e93bf538578ca9466b759ea070931c786d35f74fad42261e2df431fd00316732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000013515b0022ea946a8e679b9c0eac6cd67dbc4efc820f0b3d8984f12b7d154c0632a8d7207747284d49c498c79b6bb5c60000000000000000000000000000000004d6765ec6aa8744225c1e652ccddccc91fff7fa8182931c8648b3d8bd33b2177a9af03b2906da02bf117bea59aed3040000000000000000000000000000000006f1d858c4b223552f0aee466cff35d14b3ac6da35b8f482417e8f597514b065be315aec6662ea5c7784d3a9e2184090000000000000000000000000000000000345eaf0d72b9c11fe72261a2fddea318a8dab92a67ccb9438c11e61fd298a333cc42084d4ca127e09792e346cfe0f004a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc2400000000000000000000000000000000029ad10ed6d6d5bb591771cbd597a3a0b841c2347c89027126bfd1efee2ac403933beb99d08721232ab9b7354fcf9aa800000000000000000000000000000000198400d4e026c2463a07ba5a3974c869ed8ceb1f029bfc7f41b23dd7076cf4a83b17c27ad6506c852cd2cf7c4987f93100000000000000000000000000000000152bbf74cefb77fae8e825443e4ce09b4e223242187f563a236695294d0a5f540f0b29d6f93a54cf0a77900e936e61e000000000000000000000000000000000079f4759eaf044a80417345a1b4029f8d4cfc7e00fc625e815cb7daba2243a97d21e42b42ec968dc8647158fbe467088fa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b89000000000000000000000000000000000cf3148d0c30774104a097562cc83456d5d18643d5f7ad58aedd9327bf8e9450feee50ee893442b1cde87acb02b62045000000000000000000000000000000000011d4037dcc15d0c50337d71816a2b77428b8ddd530bc3b3c8550606229f88286ae94ba03578cbb5bbaf118916dddc90000000000000000000000000000000016160c8ec4e2fb780748aac279bc248b2e2f1092262f86d368d2f06a78ebcd27e929930c8f2be124e9d92dff5c6c6f42000000000000000000000000000000001980375281735390f48ddac9d00d4c6ee7312ed0797333a26a1684e09c9575e57bcecfc4a31b8d9597a8ecc703835e22c4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515,0000000000000000000000000000000019ff32d2901b7732df1a924eb3c099a9d36bf36cb32ab322f46a72d99d81c7942d0f2193a4aeb55cf079a2cc1707c7aa000000000000000000000000000000000193561d0433e1031fc51829504ca70e92e19bead2e0bad655aaffb6b41f5f75d18f04a162db7714f3f23da891ea91af000000000000000000000000000000000d010c36acbfb38d9dc2df6e6e21bd75deba5708fb1012eab23d06d78b1244d4daae38aa4f803d12441d91adfbaece7a000000000000000000000000000000001459ebfe65c3b2c9b2684042bd71201869db1a0248c740a54fbdafcf18fcdbcc7b677af43abe803362b462369237690c +0000000000000000000000000000000005b9860b565fc64146020647d1902e2a2d2fb2002b54bf5e21b6601124edf14d6e8836f938843fbd8c02ed8530953dac00000000000000000000000000000000104938181f16f16318d223febec3be3877bc57067fc23729d1f5552099125558cb21ee0eddc32ae0b0cc3555219eedba000000000000000000000000000000000211f809b624c4992a43e78a978ea32accf9e61fccef6bcd05155e52adbf4853340dfacebec9fa87e5417c045da25f9a0000000000000000000000000000000019bfe94a18da9ab4ea744389c17870ac96218d02178bf2ad502f166a3a1da8c14e3fc52038021503cd24042cde8f306d0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e150000000000000000000000000000000004fbb396eac2a1de9953febed9fb6e158a3b5a366f783d2105b562e8143031d7a1ef039e3fdcdb675b3d3aa4f4dcbe4f00000000000000000000000000000000155e23b5b70f1ce34fc229ad5c8bdfde7fb5dc0eca19596c658c1f8c38716a0a7b5ff59ff19a7a67e12760fa90eeafcd0000000000000000000000000000000002cc82cf87e7ac05be236104c1e668b5573674d9bd741f2d91d05c8a11af1f72aaa1dc20c73953fea38e6e069d2a43de000000000000000000000000000000000a7d1dcab00db0e7c0a239511d630526fb120defcd9453fbb57ee328f974a98721274144e48d22558edf25595b8ff4cc6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a270000000000000000000000000000000010c9293b3c58d646a95c620a0e0a7a0a55cf43b4abaa0de1d5570fabca8d97c91afd67bd45aa234273715457da5a2894000000000000000000000000000000001454f8682f3736847cdb3f784a098f7c9e488629efc3820d49b36a2e928bbf736dcb3e1b30187c2c0090fff290dbf97f000000000000000000000000000000000a0fe3c635a81f20258db4f1031589afc8c7fd07f2fe1e5cfc8f3c40d08a958a3dbec537c51be2de99b849e006870b6c000000000000000000000000000000000728876e3fdc42273e8d71953de61dd5c03e7c31ab6ec56fc03cdf55c8f0aa4b4e5c8ed88c23c28568be0d864df026af4e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000c9ba542189ec1828c397ace9639cf2ebbd1613356d8fb26d3c40dd00af1f43f5bbb25032561aeebba7b874bf39cb0d500000000000000000000000000000000175aa6e94a9e42cf809f48f51c48d60e74d61388dd217b55f3f63612c4565357581e5c39751a65afc3b7488caf5151720000000000000000000000000000000014c880d35d1d31793145803182584a8da003b0ee3c29c978b64bbfe4e1da82910a4539587ba350d393e1bf3169c5e4c70000000000000000000000000000000002a063b3fcd77180de632deca1ba89ec4ceeefefe9883ed9e7e06301a268bdf377c3a6e30859e5a39419e449dd27ebf5dc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed700000000000000000000000000000000069edfb8a83760e09726f6d1c117d4bb3e499084b65e1e830ab30daf1625c37f851ac122f9f5c795912b5b6f7907ffe1000000000000000000000000000000000eb6e9b55869f65ecdf3ac46d0ee596d07c573f88724bcd802b4429392b9a56730a217a03286deb5103f70aba7a9bc46000000000000000000000000000000000e2803e1a646bd70c51806b676591b328cb20359aadc8e79d59e7c31e1ce2f1473b0b19f7a34f23aae09678b11b37432000000000000000000000000000000000b3c9fb5a39a6c40343259e12ff4fe5058f25619d145922e1d80c3f5d105a7495dd9a4da329a2e78afc31a87b2c5d5e2f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba740000000000000000000000000000000019da372143e30307a71c7b96ae0703301ed723814a35e270ef6a6b0c57144f494df1d3fa0ac369f59f3daf534070c9120000000000000000000000000000000006521d89d810c7542108de26bbc888482a3bcec8cb9b542db42d5d4af30d6c339a5b4e959da4f98dd6ef8075554f4017000000000000000000000000000000001387d9c684a0fbf615e7023c0f3ff47f4d2c5a9f748f0261656a09b23066c745420df0eb180c9716d6d0743aae7689a10000000000000000000000000000000014271b9d0b21cc69072333a6c03493321b9d9028149d24964a3773bcbe5045875c457aee11ab0682c2bdd44f098f363d80eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd51070000000000000000000000000000000001dd881f3d2063adcc5638b4b3813a30e75fd308de3c9f42e5382fdbf097d5796ee9e03cb44752515b2459f131f58bb90000000000000000000000000000000010f491f4594dab938115343edb47b0087d1cd1bc12ef908e150ecbdb3a54d8dd51ab24a0e10c585f235ed99fbd3172270000000000000000000000000000000019d1665d452ce7fb6bb6da9782a55dcf12a1d9abdfd50435b8f2a1bc5b323c004fad35ff7e9aedfd414a9b68fb1eb1860000000000000000000000000000000013828087beeeb85e43e8540fbdf97af189878f5ddc1eb35c95aa06a26923330f3b8a2b43f835186865d6f5f6afdb2b9b067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b100000000000000000000000000000000014964f3576b97c00a8c5f4372e2501944a1e4374a3c30e11376ea62e09d52d40d428887833bc2f06279b859c00c98a60000000000000000000000000000000019ed533a3bf469ce5b3e4e9035af177efe9e4f8b0a0e5dc9721dde49a7fc66fa31c8b1c8d5bcda1bf75a532bd2be356d00000000000000000000000000000000064ec4ed48d63ab62373adb7898bc904d246bf2b3790c3cd850524e50ec38e7fb4a364344a6a1dbd26f2ad2d0fccaa1600000000000000000000000000000000134aa3c6b72d39bedd8f9c619d206a295cbc05c611147d38aa7304e995089ce34ab1fa13c2d6c6807a88797dea20214b34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a000000000000000000000000000000000a2a4c8b457d0d2554a2d439fd3b74b18843386aaa00d1b89a1c2d8ff7192cdd1d3a888994376bb7ddda4d16bfcaae3200000000000000000000000000000000155a7dc763caf6f0fe1ba9537c0f75d3e455c2d1c749dbb4aa7242b40a9740fe9e8e88af6017e8f743a9e4c5dc6ebc23000000000000000000000000000000000a693da3aee178e2f0489af77f671c734423032f30c0b7b48debd3b71e65dab7db12ab1e0e72d3ef686d6c1922aebbf700000000000000000000000000000000109c3476016884386d6206c94073c628375a02c8fcd3041e06b8b413508188a1d26ec5ddf84a77d059e9a039dc5470d08d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e19000000000000000000000000000000000a07f457f5dee69e9ee746dd67f982914a2182b5cb2609d273d4122d57a32c195270c956361d78eb65449cf5e13907ba0000000000000000000000000000000011f149ce84c2a11ff818be3ff0f86c1b38a9555e169a8cf791c79828207b7aa89c84e8012a0c5d8cce4e89d758b90e22000000000000000000000000000000000d636e5b027e41809d7ec8bbbfd4bf641a56599a63a7678569404ec8d45c3b88c1d2969e6101528d4edf1ee9d8e793320000000000000000000000000000000011878eefa5ee49be83ea1f7a9cdcd4997ccc59a9669778b3f006429e1a22d3b2a051924f371a228856523e3a09bab59b29ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c6986930000000000000000000000000000000019e9a1950f663b258474b24c334bd256d3aedcd26dc971a745857bf1fe007da0aa00777db5c3e5d21294e99862bf8ea1000000000000000000000000000000000329a12fa0add36f259e401442bbe6e5f9139e4a46d5d091a2110d2561b5629211a1c1996f20d19327d1782340e7ce4200000000000000000000000000000000032782c94c6e45a88425438324f3a24ebf37f0be213424b1be52c878985633950a022f57f8d64af1470486aa3744f3f7000000000000000000000000000000000631556d52fdcee3529023cf20d46ea09ab3c642a7f4eca2878e4af88801d21b80b829c9aec9e73317252639c148676c40bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000009eb122c61ec44afb56b64929040058a804311e0e97d3fa513a162748091304233480bbc883f6fb66080b563b308a24a0000000000000000000000000000000007d1d810fb8788b9f0cd04235771d7adbbdf8c6e67e8538b2c6f0f278755cc5e57ad720515ca558412ae1fe2cd40b74d000000000000000000000000000000000955496bdcbca8716245a130fe6eef44d13280b2d56f15bfc772f8ca66a52ca0a742e6bc273c28cfc858a3269f59beab0000000000000000000000000000000000b27aaa0d94633912c96f00ecc021773e5cf5e164e20c7a7222a58b0465e7baec4e67fb56ffe564c7a2904f36c265e61574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d70000000000000000000000000000000012e0572f5c84f6082dd05705a3fae738920ffff840c21e444f0ed002df16394afdc21c249b6f1837389c48719539f4c5000000000000000000000000000000000c26bb3ab52e3bddc219dc223daf472247547544e3a9ccc31123b82000b17ef325148935621edd36ced4e702ade1ee3e000000000000000000000000000000000c13a8f02dc3f209e9abf3d316fa843be9c4dd98ce1ca2edecf757bf2bb498750f6d96c28abd45d9c6cf5b8b6334b63600000000000000000000000000000000157a50d9034024dfc7b0f0db4ea0f45323d76c81bc844844ff9bdd0c13f2059066ec3060210aaba61bc074afd7ccaa286dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001054edb092a7053eebc542f690e03139f2e25a0098c665741e8711c8a6b9582af47e467f74fff9aeea098b7732be72d400000000000000000000000000000000084f919e219de15e7f9ee122383c772415741e5b86be6ee7d2193a4f6be5c9cc9b2fe5e8beba26cd768bf2ea1b6ebffb0000000000000000000000000000000001822b4e8fae5bddbb36f5c226216471862af238be770d33c4fc1ec2777350db2f42e33a7ba468c317a128e8446ceff300000000000000000000000000000000130f704596ddb28ec6e335d9527707a75c97298407ff3fe17d3cba0cde4c21bfcfd1ae46272018c1db768c036f215182d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f42308200000000000000000000000000000000194dde06f8c54de9ab0ad72ba0de2241fef32fba30fd6f5e83fb7750bc120d51c461d75e495cee0d1e85f0f39aa9d3620000000000000000000000000000000010646496be02c658c82dc68eab86a4f784cf64494bd8441f884e8ff384cbb6ff3a4bf5126bbacaa556aafd652397a8a800000000000000000000000000000000109807bd4b6613acb3eb7d386e84166219e52e841c41185a269cc7cfc5f34e9ef5cd1fea29877749e0cef93a3b44eb1600000000000000000000000000000000020a388c668c9339e7aab15d03108317dea97720dd27a94cd3bb59b372b268d1a7d7d7409780bc4912c3f95acd42a57619d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb00000000000000000000000000000000161506c4a2d57c852fe8c3dce63ec6673f05f99c1e032c8e591239616ef4469c4240482ce5985fdfd4a80f54dfa7024f000000000000000000000000000000000486c5b106393e544852c143c5ac4a882c79870363858b2c910ef4041d8803876cc55ef59cd6a41869bf5247f0db2c0a0000000000000000000000000000000000fe765ebf3c4edd3035c7bedd4aec918426898339d7aa004fd74bbf0e3236deeb7d2bbba56c31fd447816e301100a66000000000000000000000000000000001917c9cf16032e22cdd3f87f098a532a33c9fec560a88f9d4232f96cbe0fe945fbae6bcfdd2095cafe6e0b21071d6ec53f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6,0000000000000000000000000000000015a88bcfa39f444cd66d0d7e15c4040561154c59b832c5ca895f8f8077659487581681cc8f13be136a35b4a573551ad00000000000000000000000000000000009fb6b87eba1edb3d1d23e566977eac68e8f1a28386fdca9d484c7e341c1b210390787418e2f2dff7a228e1cf10962d6000000000000000000000000000000000978de870dcd8d094072897707313b9f1a18d525e60a7cba2b2a395ffcc9d0f97f84e0784df36247d6c98824aaf3ec82000000000000000000000000000000000fbc6832c324d40f104bf82c8cda941212105131c26f630af1d3f7040ef43c6eb4486766b75a81433e46966f79953647 +0000000000000000000000000000000007517a941bec38d0e84d21508d8bdd6778a853d9fb4c5e953bcfe3c8fba3732ca0b7f6cb5c363f33d7718b1b1a68f8e800000000000000000000000000000000150c0d975481422ddec2a58a55b3d917b6b7c0510e75442c81ee856e267d7efa09641c6b79fb9e699c6b473cccde7f4d0000000000000000000000000000000009d37bf938ac30fae1cb3ffaa971ff3746ee4090d4bf8b11dff7710b3f2e4cc686813890e03643fd56fc99e847ae5e940000000000000000000000000000000010fabc4048e0fadad73d0481e290c81884f4578cfb66e0a83324739652ccf273b62204f126696a2fc6469ede39e00a9fcdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c900000000000000000000000000000000089b167ce7fa997ca0ad3f8bbbb358824cb41f525bf60352d5df99402af62cc6d768113d2c1ebc7fe8190c5f732fbfff0000000000000000000000000000000013dcd35865e27bf98f1f6508b32c7e9a989d528df0626228087bda0d8b456af3ea2f4be6732edf1bd8cfd0ab9576197a000000000000000000000000000000000333b0612d7068986d21e1cb67a1c7af423e98cb14aace2ce02f84d32a38f97bcdac465f2b22e5fafa6aaf0d40380e4a0000000000000000000000000000000010de7ba4f50b6654fdecc4fc6c41289bec50cff1be18be9d5c9d1f906ae843189bb43f144aad4d2a2cdebbc2697c974918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd000000000000000000000000000000000fdddfceb29fd79c31b138ae8e41507f324abd5e3750da14f4f86176126a06380d53dd5f7efd00e7f94bc1370ac9816a000000000000000000000000000000000d8371c602e393a4be250583c299d069270a344953f7f07a5fe27f8617cbd3ebc91f423dc176b272339bb3bd8a9a348200000000000000000000000000000000193a260a417c9c46da0aaf139e3bbfbaa9f248943048396d95716b3be0b8a148a3f0ebcf7d6f9a318b16d2d850ec2f5c000000000000000000000000000000000be4d0f2bf6d746b930034eea8a19d73377617645c29153b6ae6d3ca6fb35a704b6a0bb658282cf93555c998f6fd054a650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000731c0a5d076d6addb15c1e5d3143d41371f4835d77756418bee452d2f03b1e603230c59f87905fb67d5eccce65a45d20000000000000000000000000000000013bd198c023009190c65686468523eccb57c5fe7b159a1c5ba30c662a275fb24d69338ec9c023ee6a10a8ec9dc7968210000000000000000000000000000000018fb369923ee655839c7d996e264133c49f102978f18261faf2f8eef376eb0bdcb5af375ada2bc783e50df16737f78dc0000000000000000000000000000000009ab8e16e1d0b406adbb37e950bc3820ec13c882ec4483528ebac836726ba202bcf796e84abb3c16dbf6d1131b3854cc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000001401029d7cf8e7d2690a27c01b32008e273b5a33842dcf52d84f77dfa4b2a1fb290f56eb4ccddbb420b27e06a7a3a3b4000000000000000000000000000000000c464c6fdba702f2fbf4232b34d615e66dbb5bdf80233f695e9103272111a06a79f8972d1034176859d0e29400f5a9c10000000000000000000000000000000006cc97a29f4e694f0cdbb099278fa94140b40147f4f911de96a762f2bd28233598a892899a6329cc3cb854b56076787a000000000000000000000000000000000625811cad7c740758388f330c4a56ef30429ea4cdb9a00e2cd1b7f310184a2e6ba36ebdca57c87cebd5232f52c34d92283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000d0a16b5d48eb062c71b91d74a0d25eca0d4bd7082de25199f33a9d3d598d137fbee2ac36e8f877c157be7438ebabd74000000000000000000000000000000000bf93533bf677050d9a77a5dbdbf7cf084b5d934d55318256712ec361693738d48ef27536476fdc93dd8e81f13d67a8e000000000000000000000000000000000696fbd8841e60300602aa5528391aa8b196d8c186d6124c842a0124a8d8dcbba637502f330c980b2f5a900be8e04d020000000000000000000000000000000017b0c51e699d2252f35619520af71775f9dd8c57c2ef146adeb72640bec2ca02a59680153e5c9f66bd513bd8559b9d66145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f472000000000000000000000000000000000eee94b5148ccbb3642e582cf0a517b72e6ea019676a13b1484982de7f4be0346b7ed22979ba7303f6367294a3eb2716000000000000000000000000000000001502bb3964f6b3e862279e15fb105073e876c4e48c55c42f3737dc9efed82b10fe8e39438ccd39c933f5ac3c6768497e0000000000000000000000000000000016cd8c4b3be55474aef7081cb969b75ef5e7cca9bd0f9627928fe9931c6f869a9a49d0ae2cfb8346116eb3ced25d4a8e0000000000000000000000000000000012456eceaf32cbb6514e6211136475a750889caea18ff4f9d5ed7b378e6d1d265721a646715aee6b9f2098e954a88289d9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af0000000000000000000000000000000007b5633f4a7dfe62f11065d44581f5060210f8e572d960eb85ffd0a903d8b989ce10449fc90b7e5646784a9f6df28699000000000000000000000000000000001710f252cb35d88f6bd151ed596f2d6455f050c5e25add394dbaf60fc036016ae07a5a8ed494b95875c02df3c523186a000000000000000000000000000000000bee19779dc6430ebee993f82a054fbd42e5b7265090017e5b2d2f1469bc96a5a188adf471d576a416f6a841081043df00000000000000000000000000000000038f9fb4159e4e6f596a17ddf45a00a9e4aede63b062af5eda045efacd3977e8dfd61c307834c08bb4c284638696e92ef262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e00000000000000000000000000000000197687895f22c4a639bcf2f494dd9e5a034610b0297528235f1d806cf032f5a86c5248a83ed6b12f0de27f5c6e6f49420000000000000000000000000000000011ccd5dd6d6ce553ade9b31503a9e6a6119ae329178706f051581e3cf0ee9d6fb527b340bab8c79fad1cd451c7edb4330000000000000000000000000000000011e9f051aacd69c8bfd2f0ecb566e6d38eabc43f276ba7a1b8e8ab093917dd1c672c61d6dac4651026823b9938d3601f000000000000000000000000000000001362c3b2e6fd9b3618df26ed28f96530c1915f0a4ecb647658d1ae4ccf4c000f3bd1797696c9ac5c5000dbe58dba8de44f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a8000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d000000000000000000000000000000000db02fcda340fb27a3fd7da468c5cbed9c8dce8471843a8ddadae43dbec9957a0479aa52855d7a6dca99e7922432365c00000000000000000000000000000000163503d24f9af34058cb5afd8e9d5aaf29e141c8521eaac282f138466e834f0daa9ce14e0590b501680d5b47f866aa8c000000000000000000000000000000000fc9175e6d20afd9d194907f2eb311bf8134aeb96da72f6423610612f2ed20a074c113fe8bb632d9ad74b2f6e7e2417d000000000000000000000000000000000b4621f5e4465629648b62b7f2b77afe6470f9706f9bee5b3ccfa66c596842cbae26badc689f7f623360cb7fc1d416b84ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a61630000000000000000000000000000000003c77c7efdab9a9e71283b034ef581a31faee417febfa99be3c18e8ab724c140be684ce719bc5a9ac5d3855ddbf3651a0000000000000000000000000000000011889b02b4a1150fc2b7191a95c5ee767f3c9b82a3a53591018242fa8685ee3b3542526dfdc00695a6cf046033b8eb760000000000000000000000000000000016d7463159c4e3cb635f24bfb944bc518369e894218bc49d7b7f0ea99240259f7ee2b4c26c6083dbc4559ffcfbd392bd0000000000000000000000000000000010a85df6294fd6406ca651f15494153e9802f0068bfa149e87fe4b1cc3071ba74940a21dfd55a8a77e7e2a193468a3d2eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a0000000000000000000000000000000016beec57d3049c382fc039ac96b890412c5e8075afcab599fb877f8639747a587e82241d9a8059a0bb45ad49959777d0000000000000000000000000000000000a70fba1b061dcf587f133035a3aaafcdace3b1e771d71887ae914919e5f52a99d9933307ec15b5f0a1623b9592824500000000000000000000000000000000005064161136c04f9f50e42a5cee5dce3fa0ce1dc0655b3785a852cb9741927f6c9b357ac1010d7212533d1593c83dba70000000000000000000000000000000000d50b992bc0eee37a15cfd32eda2c591fc4c4203ef84232d1a1e7a9888005bc00755d76b9d0345bb01ffa7525f2aa1e9d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b0460000000000000000000000000000000007617e60d8f67344ce6d2fb65cfd5b423a1fd091626da837dc8a51d6ffdeda9712864e8f30e45ae8df917e0e4625e59a00000000000000000000000000000000077c4aad14f870ba24703397ff0b33af2e50b026f3e0f13f3ec1aebc9ea3af98cc65ab56cce4045538ae6e5f410196f10000000000000000000000000000000004a31d0eff18afa87f9a53098cfd5d21e913c7519cb171f83d0b73abbf3e893a3ccd5aebb9f2bbdd3b0eb0326d37fd1b000000000000000000000000000000000393052e6dff65e01e79254af757f12eb1931e0b386f8cf0fa0782269f962ebc5d9bde46f5a4ad3806e88330aca59ff01e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f962200000000000000000000000000000000033aa108d252e9107f29cc7da79585d4525ff2a35d31479a099c7c011a9c4414d7bc5f8498f8a204134b2d14c5fcde5100000000000000000000000000000000121214465992bdefb970d420face6db75d531e67314a021d2877643ddf738fbe57625d286bde7f40efc1d329a2e85b6e0000000000000000000000000000000017e14f6cdd916b1fc949be8ba3ef9ae6cc16d64da4dd498b5458ea0c14eb7aab8f970f030aab26397110331da11a232d000000000000000000000000000000000c56ccda2a5cca61025253407e72967c767f0e7f2aa0b97d4e4a09420dcb882ff35039ae504a9c62b3f9e7bb0c2e7bbbe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000010fb3396b0674b9285cc5d5a4e7a41ac002f2b43332c20a56f428d1e19e1d1bb6f886d3bf03f7b0fc509e52d75965e15000000000000000000000000000000001196b7c253c50da10815bdfd7930a69608187fc3ac5fbcfeb35b95754d3017a094afcdaea867c2f08346717dfce7bce8000000000000000000000000000000001021f178c53b7d7d2041a6419203d12ee162f27999dd8f79baa15c37a7401e7a6df6aa4192a310cc1a23bdb0b427d63c000000000000000000000000000000000953c75910165f11112583476574f3987495d33e5b1a5c650a2b30692592a442d9de36da49255b0c01a7bacaecc9b81adcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c,0000000000000000000000000000000014da1d424c936453600a4acbd3666c6188493d4da8b34d6bc508aab07e59e3680a9e3488e69d42a724c9486d70ed4fd000000000000000000000000000000000048c637348fb9a4c631a82ded1fa08d693cfa2cdd6cdffb8bffee63d1bb2ee8676512a1a8d375e7ab942b6d6bdda45c80000000000000000000000000000000000443264e7dfca91f17251c33cf72c56b045902b4db2eb10d1fd856f79b4130afa6f29f3283af7d3b8b2a9d8dd63718a000000000000000000000000000000000fb386f875190ac7a49d4742edb387f72c1ae0366ca5c71d5b7e385c11442941ce0fb9fe2014fc624fe93ab86ebc7aff +0000000000000000000000000000000016a539a21320574fc25ffbc0ff10c821d6ad20674413eaeda6f4a31f9a028e21cbb3b224c225a2e3bc3dc221cec084cf00000000000000000000000000000000104e44989e2fba9ddce8e309f5d3fa3129f679d6456ed11137149b50adf8b22c1a148d47154450853e6797aba2b006850000000000000000000000000000000008b33b8cfc992efdf7d733803a6d08a4102e27fc4960ebe6ebdb7949c4ff5af76e55002d93a4f7204eff5f2dc4e37ef10000000000000000000000000000000017c35411c571c302c746a9b79cae892e988d50b4660564660de960ee09b3937b6f5b61fe37d09f1c02528f554210744aaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b60000000000000000000000000000000009cd32594094d4744f59690cf8d7fd260b5ffb2a22945d938c035151861507ecaac9ea553e7b44fc4b3beb03b33783540000000000000000000000000000000006f4de33731b9b13b9cb395798769e54a0679d272c2d5175455e10c790debabae4ee02b6df08975efe806da9c4a208b20000000000000000000000000000000011859798a8383b7f994a1535bc0a96a114b90644d19921f0eec774ed58dbaa899dd3736cd1f4a4ff9bfacbc7370091d7000000000000000000000000000000000376c25b0f70427d4974c4fd1539d40996b6847fbb67822fa01cfd541cd3a3f8a9f3fe9f7ddcc3ce920a6ecb27dafca0c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c0000000000000000000000000000000003a178f91a135d59dbd65eacebab293a3817d30e734c247f56a08812aa540a5c80e3f9908d86ad787bab27fbddd21517000000000000000000000000000000000672b3544dd2b91a626f37dbb389aff073777164e3e20dc572b18a2e5223bd323094e41bdbe2dec9bada227efb37dd22000000000000000000000000000000000f40f2d279c66f22bf0fedd129e02c96d8906f9f1ec19f5a5c1cbd5beb10942a066dd391b69920a0a697138f627a1b180000000000000000000000000000000016ef3caad858d323b752e5c437ee2043c8f691ca0f1862e80857f7cc478a689df97bde5b1d1350892c1adb03c5d2373ba1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000001276e133fc5e708a3265646ef0a0122048ef95d7fb46f78b8dca57dabae0164ca986bdc74e581604ff31165f9f28dca50000000000000000000000000000000008a77611be0502d2ea7fbcf73774fbaec68eba36038e2f34f79caf07f2e4b7444efc49a4e85f88af585fd28a041f26c800000000000000000000000000000000181ab176e391190b1cae2e9b4105ca14cc82d15890b0ec127d8cdb46f30b704a089ac69e76f5b50575ed66176950e1120000000000000000000000000000000004031ce77fe9ee319b8db8f220ef4480c81568b3f6e4043c8710b559d25ad69dd38dda48b2e11d5aead18db0d1cc09b98810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea000000000000000000000000000000000f26e6d71e206c88dc81b8b8a5c05ee84a9f185e7b7f155253aa39104b5de5be7bb6cb6662df4f8e63b37fd1682721f20000000000000000000000000000000010058d13637c8da2e91c8cda7dc2cf1734a2f14b12b798e5c563ef9ef3624255a6e1c7550c37b547c35c55dc736a17ce0000000000000000000000000000000019ed470bd514f8bda8fdcd9c64f7626efdde0102907bd31551b1d1972aa14e1d361e1d58b17948909a669fa4d99cf3200000000000000000000000000000000013277afe1891807e269c22c9aa1598c12081809d888e0eb2513ca3f81308700893f74f176858ceed9c7955dcc0d8fc6893e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a00000000000000000000000000000000083664daa965c4173d6028e047794703a16e52ae459d3db0534d13c72d749d603edd668b9ce500677715e45216367c63000000000000000000000000000000000f4e87a65f4720cbfde7868eaadb34ec1916925ffd84e5407defbda0c39e1c7afcbc90855b275d528e7b63fd3707bd4a0000000000000000000000000000000004c9f689abe0d2dd3d927bad4b39ab44f6704014ef9a1dcd1966777129e1c72515b43c1b92ee60e9611245454683588b000000000000000000000000000000000ecc57b08b45037e62498135643cf077f01d216b5106551daab391446ce7bb37d40f41378c830081bb6a326f0105c2c4eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000012b84341bbad1eaf7fc8ebe56f67598821017365b6f3b4cc1f2355f868e8d55f9c0bed2943ada202a7d85cc884d8e6a20000000000000000000000000000000017693721988f73d77f7a41db108e428b0ba781ea88eab463693ec352cc13d394101b9a2792e0f30c77bebaa395a4776700000000000000000000000000000000093245e2919523cd57a0abd2e8a9c5cbe774bee957f26d3cb502b9c8c06483b850b031461dc2cb033d399651724f4fe4000000000000000000000000000000001530f7dbf6a0fbdc8b4f7a4d298b7824c15035428cb8df834907e25c64b8985186bb13f397b7b99ea7014ae65c428b12abe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000f323f01f2a63bc6eb1b565594ded14043c4ea5d1f0fbf20f39299052617c334e6126afd4273738aeb153c3561348b8a000000000000000000000000000000001525d1e1fa65f1b674feef74f6c81c82c3eeb709e597aedabbfc2b3262271b31d93818613ecdeb49c5d3a6a64f17a5d90000000000000000000000000000000010458c15bf46947a237dd1c61882b1561121f64890681bae5db6fbd24ef6c34b7fcb826eeee1fa328d9ef4d859faf238000000000000000000000000000000000e1f29275fe1805d02e069082d5e9a7acf69be17013e6c4c351277408d49383fe06f00137e777ba4aa49c29c25c6c0ddc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000efb7373e11694b966d0182a9b01d1e52ec1e89cb18275921294e2d36333460b1e49fd420f1ab781b000d1491ccb0b11000000000000000000000000000000000cafcdc2c58fb3fad713ce1a38deadba8636c384243f9971e3930b961efaf303cac4eef1e8e4662636ff91eff1bf52a80000000000000000000000000000000007ea7441e1b2b0f1e42bd511c060b646c2d00bb3e6507beb5d17ab93ff68515b02f82c2dd43ce035ff660ddb0c104a77000000000000000000000000000000000bd04b88caf9dbd0ef5f89d12e72aa47d64212332b0ed871b7eb96b16295cf4810f6f20cc85fd4d1ce72119f80697c1b37d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b000000000000000000000000000000000c3d2d978e23a690e8422fd54f36fbee1f642611b6c3b2c2413844066159bdcd3703d1a392b030446af04b654f8f73b7000000000000000000000000000000000ae652fcdbd8e467ee9b447e61fcb811f8b6aa48840476c92daec3285785a06a81c1705fc2896c0843ab48eb92555b9300000000000000000000000000000000007088e6441cb85aeffcb4a9a0c81ebfc54a61f35c542be3870c2bb94d7081353322d4745747b0dfc3e5db07f9e48c560000000000000000000000000000000006c11f3e0941ea3bde0dd3a562dbbdad433f0b1e99ba34879e86f7951ddfb29b9e04ca62d54d7552a74e8cf1c3da3e704904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000000c665f4417d0163820ac96c83cc2f09b1b3c000023d827e2690aad7357ff59e278832b992703f5f0016051ce0a4510cb0000000000000000000000000000000012f4b6688300b253fe868b3790f6d2f4fc16d81a49ff7a2edf821de16dc992d79482d66e443e0abb5da43df69f8d648d0000000000000000000000000000000009e033750a118d998b136cd671d0e760e3a617f1d6a994db8f6dfc391619f408720cc57fe550785306184b0c824705620000000000000000000000000000000018cbacd471e528535e22f714a841f110fb0484826e30f97842d65072b2790dadf0bd7b28df96bec531fbed1f3f93486b68911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb49000000000000000000000000000000000c42b6fd52cc52034b04078a6565af2b43948695851393596e05f37f297dfaaea931a33f5b4c25980c093f8a742c0020000000000000000000000000000000000fdc7aa20e63743dd6ab32c82d2d6992b29779ec06eebd452c17d844159e90a7f3221f3e0e6b5805dc0f42dc3836d90f0000000000000000000000000000000003a2342a1bd528d701c2a6c72708a16df632f4e4b6cdb3ccc224b58b57af30b44556cc968ba3c0396a5e3f11568a73710000000000000000000000000000000019ccf76462668905c5687b7612a0bdfd4aac70f291d8b772e84fd5d4bcb591556317426471242fb5f44fd695c7d49279481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef000000000000000000000000000000000c8fd4a171c5fbf584f567a1c10b20628e7e0d5d796eac4a9dd2376f8d488da25b9219c7c70709999b5553f8bba915ae0000000000000000000000000000000005d791c907984f2aaebf903a0ace52147745295f0c5e85964999a8fc74b64c8871dce358f26ed1b4af6c6f7f18e8f4c500000000000000000000000000000000110a453bbba72ac171876e0f6b4acd5b178816301e02586a143c2bcbfffcdbf593655408b9aaa4141b2a210599f452ed000000000000000000000000000000001025d5065f9801fcc1c1ebebdf67923b967ce985b5ca27ab5db8af7057fda23561a46b84fac5e793dd9af692c4d56cde72780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b00000000000000000000000000000000023e5ea1909032676cdb79111a33da7ed788d2affbf4029b932eed843268f355dc92905db283d6617fbb530da3d704dd000000000000000000000000000000000b46f07de520aa17d597586cb0a6894a356757941ff9bdc2976f620e1bf1eec1dd9801d6baa2d7efbb3cc7073412ce8e0000000000000000000000000000000010022940611f418de9f9210b1be919d7506aca468fe5853675fe159d3e58685bcff6cbc2c1cb9e7d45a7bf305fca0eaf000000000000000000000000000000001888b5b0dd1648d9a27345f570a1278238957de1bd30c195d554750ea4b119e98b3989b912c4fad531de416c1533467f84ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c65670000000000000000000000000000000011dcc334a5037719256e514b2c3b0f36396d8cedcd77f33545842c686fa0f35558c397562a7e245f8cc412c776a2b3930000000000000000000000000000000006efd32c6afc56a07c813fe19e71f0248666c87e1df7e79b7afbd70178929e5660e85cea35d1c6f42b4c627a94ae0d150000000000000000000000000000000005a5fc2010798c793c1b407a577da0bf0e04b0478f19b7d0cfeff8e4e4fe2d581461831db165cfd17146c49a732c41460000000000000000000000000000000011dfe3b62eb87b039113152af74ae74137cba1762d4ae62d3cb0746272d1c42d3cb4a8fccd845a519fd0650a23a897a13256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd00000000000000000000000000000000110e73e44734b7ab63f021727b75e735702f1acfa6669e0dc27111794ebee371734764bb165132af3a7e02f3605456480000000000000000000000000000000005fbcac7c7334cd0e6468feedebe077b80390833eaa4c28af80d29e75d692a10cf13058526fa5e5ab0fb635335ac8f220000000000000000000000000000000013f537ecc28685aba2cd60d0e3e787bc8104a3373177cb93107b63d39919c583ad3ad7a42e322249d7605ef035fe1af40000000000000000000000000000000014791f94aff42bfca13ab328a3e47b06f7da52e13436ad477cf55e53b54108d3aa531f0a5d73ae5ed7108d5cca1ecf7a575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b,000000000000000000000000000000000bab02defb32b7938372d656feaebfb5431de1484361542c02519d20c6a500f0b0b112c331fe6f4eac3ec7f6ae4167e50000000000000000000000000000000000796b38c67df1361115bbf3a4afad2651664ef55b1ed02d3172f024f90a003fc3631753d7142aafffc64c6f6f57bf7800000000000000000000000000000000080d91637a93a9025e8691a400254af37cfde67eff7d3037d428596a808a01d9bda8025b7246fb00785cd1068b2752d400000000000000000000000000000000182a97624249f0c6d24672f04e2c93eff63fbe76cc11ace0f7193facd0655cc1e1ccb2d89d9547bc352a395efeb95afb +00000000000000000000000000000000115b14c4eb9cc78eafedd2072be4555a3db9e61b5fe0139bf3e40a92cc37b4936c68576fee5692a80e4a9aef05a9b7a80000000000000000000000000000000019c828ea555a3c8d28cf0981e98609361b5bafa8b62e860d121c0f6d0f0dcef544784e8a5fa6a9f1d1a68b30e8e8a6f8000000000000000000000000000000000a2ef5146d2658609fd4eb98fcb5d42f6c6aac4fe53597128bbba3ba3539042ee5824f381c41dc76e2c6e4dbe0665657000000000000000000000000000000001807a12ae5f289ecde8ca0a913647d44209b13fae9dd6aa8fb4365a3beeb81652ec17cf92f6784c9ca5a077824ff6dc31129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b0700000000000000000000000000000000186bfd109ea2369818ae2f466953eddfa763960caeee9d6f1ecbf6a3f854163342c26b56d3844bfedd8f227070f75546000000000000000000000000000000001877077daa2ea074b2868e86faf14efc6ed35a64161a77aec54624d9cb916c45de81b40acc3797c6e3338fcd7a42bb0d00000000000000000000000000000000054be1650d9bb6cae6a1ed08879668e4aa4cd139c8b07ce21d40fb1cf37f11de730ff13814a02d2d6d6df5eec4afe8470000000000000000000000000000000001612b5b7c613cb66d4134aa867d985682f6a544147e7865732887d4fbb191a9f5bdc27bfbefd397f38cb101a2d68b192dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c90000000000000000000000000000000015e2b23aa42f1e6a07b0a31dd4acc27e35ce1fb3333c3f330f2d88f112375cf24e6dd5afc4d245531e4e84f1f82230ea00000000000000000000000000000000193649f3b7efb346e0c1f7bc05b0910311270cd44b5803fa16e06655d6239f609363344bb7c16c2105e20709fb5ff0400000000000000000000000000000000002a6ee30841f471dd2ef13888ba03c9cb93c85cfd0f1d0a3927205e3f57fe291bba7eccbd2352f25cc4047097fbb63860000000000000000000000000000000005482d4a47d6e381f755c4756a761f0310d0d981523afcf288e47a1a643d6be62ac6410521e0f25828f469af6150f41e5a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b01000000000000000000000000000000000178ca460993d503e496633fe5230d895bfcdd0696d817a23ae94e529bb145a0861e4448d3bd48c55907be762192a8c4000000000000000000000000000000000015d2db77105a8ed6eadc05d3d1f26a54b3d1b812a58ab49a889f5b7fcf5ec08c2eea6ad09484fda29cc007037a1f6c000000000000000000000000000000000fd5628d61cd0835fe49fcbd2f17058f23d0ffaca4474923a2c0706d9333d9881125efa2fda56234a82158da3eddb5b70000000000000000000000000000000010ce4a0bcada5f92cc8898dbf5c108c0897322eb6a467662be569d9ed0f6e2c808e214b83969ebb86c84d38f67d20754579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000fc4ed0ca43d5cb172deef02704579187a480cc977737070b8ed2dce48d3f3141619f37e985c220a2840ac01dc5667f900000000000000000000000000000000047a15e96760affa4e537a45aefaaab1e0e18052f63514a9f6544136c87b7cb4a5c8dfc0d9544518adc7932ce9cff5f1000000000000000000000000000000000c9be55c06f81e87de58a5c1df8d16174cf4115f81091937d98dad6c4780a9b8dae1081f8961fadc4f994ef62927664700000000000000000000000000000000165f9b1a8f23831a91be8077b18563c7648e54caf30895983cc26580241ca7c86b9b30408a9b27776286ed9f07bd8ecd4be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d160000000000000000000000000000000006edc0d62ec31b14e87b2ccff3a21a7c8d38c3ba0ec48bbb8df27fb1acf58e1a87c4458dc2b770172460adfb9a9bd50b000000000000000000000000000000000ae80063df8d41d45fc43f3aa0881364ab5fcb9ac526ee22d3870f2edb0aa379e9d81780b0ab08a4cf308d819338deee000000000000000000000000000000000e0898453feebb51d9a1cd2bda36a307ff2eebf44dc8f4c694831218c42b51f723ffe521b356ad4e5f0dbbca9af9ab47000000000000000000000000000000000fd186dbc046dd02217cec3c7894972f71e5f00e00a40fb1521659a33e079b7a1f60b026d9055a50ae18aae5757ab8490983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa0000000000000000000000000000000012134b433877e0a7858e6c3b95b2a1dcfb0548b290b68c209642dadf550db1c636598ac43d101b13c2d8d5ed9602a73800000000000000000000000000000000102b5de123c449a078f6f06935c9537efc791ed8e5475ffc2d9e1d098c814abe56d4b7fc6501c315edf7e64a431c5183000000000000000000000000000000000bce703ba78f45a1c59c69429d3dda18243ba2413c5eab46d469f504da975c434eda451c85357738d6c7054755d5cec1000000000000000000000000000000000387724937bfd817a65c0e0411678cdc78df26ebd4a814d92b023710558701163349b56b80e6bb68a4401f2662a0525506615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c0000000000000000000000000000000005abed9305bb79a0ef1cc70e7fc2eae35a8580cd3d1ffad73d3bdae541ad546b8f74b4ca76f8f374e31dbdaf1bd14be9000000000000000000000000000000000199b29da8d161ab3061a18debc8b7400415caf029ced47131e27d81a0f7f79b6ae5e570f34a4c74fb85fea1411bd6ea000000000000000000000000000000000b8a7c42f5289d20b1a55a42d53d49510d3871b6efbe560bb4d87029b85b930f787c3a42e225006ad62c68d5f96c2f8a000000000000000000000000000000000e74aad2b29a210cc316181863e71a1dce8866a088a072ad5972af57b813a2e968a5b16b294273acd6e81e9a5be2961dd77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba1812272736390000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c41f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000001611787ba658b64467b4a28e55ea24a3b230836af6c2a7072231045ee4ee38e02302a62688d6f988f76cb5e50eba40080000000000000000000000000000000008badcd59d6d30f26ca674753ae9257a853dbcf49a5641999634a9a35a97096b6096b7b058360bec2f9476a51eb0d781000000000000000000000000000000000d30154440d8bb5fa6538953a96ba404658817be8047fa7a3a86493f02399543220758e649948b804f2daf84fb86f7580000000000000000000000000000000014fbdd62f761fa675e4cbcb61083a910bcfbd1f8e37f1fb1915f60929b047c970b87be0730ddc20f9716ed8c9bea7f19c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a1600000000000000000000000000000000143d1f811644e3a51c735b708cb2f8a2a90311f9971c90b9ec8e45bdd6488638b6851dbc882205263887b4dd5dfb4e120000000000000000000000000000000015692a6b06e3bd3100e149c6be3cbf1566fb24531eb29036fc48f85d5da83316a38af4e714a17552024c1ca4a5e39d9d00000000000000000000000000000000172b9c88ed9a1fc2d5a7f147d034fa243d420b129343ff92b79bc4d836e380e5a7e388069e9af9026485e9d3f41a7aa300000000000000000000000000000000012e8453dc64f72653c4e9b3f6f43fdd01b896c642d21604f992dc5591f2cadf71de4099e1075a4ca4b7539f84dd5a908db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c880000000000000000000000000000000003e9b6d23809781f50c0033e53d245dfebbba9e0c4d9f676ae61b80fb6e774509f62fad854fd9ea841d9905d48d943a30000000000000000000000000000000016a1ba62bc684bb1848b0ccba59597b19973b56fd9b1d9d06352de44aa79c6bf65409dafb54f859d4a7c32e188bbe19d000000000000000000000000000000000e782741a4b16c5838a8f6e542135221ab3c6ad180c85c08742992ddf0239388e273735eae76c656e61614da386ce2640000000000000000000000000000000001cf6752e88990c221af94e18744790c30aa6a158b10a1f6a56c2ee3c3f0fdb2fa7213f16764ac9e9f4f65e99e715ca170751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc0000000000000000000000000000000000810b0175d781256053c3c9188cee4f55620a6624bfbd2f4d2e70ee68a105bc7b60bafdb76794a048e9f25da976390d4000000000000000000000000000000000716095f8fd72d9350ca62ca3ec34d2228cb563d4e89b19b152787d42fbb750435aa6233d0a97196a9324319837be14f00000000000000000000000000000000178e939d87c37d4a2f49e1e5596945879f2f0f64419e3dfe2afa06bd58098e1ba57a9b60c32cd6527481ab3b325ca827000000000000000000000000000000000aed480a1da482e40ae610a9522f0a18399b0130202f9ca79e3573987f5f7ae30724feddb52fdd05817a96f7937aaf7984bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000133adb236d9eec3544fc91852278abe37a1da0f32a84477c0d93927d64af613b7452a5f64ddec7447779f42873cb157000000000000000000000000000000000f6bd940b51b7ec5a0d92ac77a55c296215e970e9a499793864dd69c3a8d583403e95c08b719b5d8eb0c37a8476d3b960000000000000000000000000000000007d4444062ae06e65b45c6105af53c487f6b275ecdb36f87ec7b71d5861a1bdd6d735e9a1fc5dfb476ab8c13a98b570a000000000000000000000000000000000e043cdc87c67157b5ea3e5ab1b243aef479b23861f8cd823bced140ee03dd1f8bc6cebb4bde4683ac3340823f4d55b8d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000018c35112c27caa6bfe9cf8ae55f51755ed349ee7e7141c99069dea07c21a6d8634778a91f4dc3d17da04966a9eaccab5000000000000000000000000000000001800c8a9b146dba27050ce63e78895bee2016255c59acc34fd5e6cb926c16a8fcd2e8a579fa02559b3c571cb08011bea0000000000000000000000000000000014afab23fd4ea54b1ef576a12a2a62d42b493612ef466483ee8c4e62908486c038598e72dbd9256166960db73259def8000000000000000000000000000000000899a99ae8b10da4bbffb6590d79aa33bb2adb2444a11627f05622c732b70f90cbd2779362349aede5b591e84b53a8a06940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000567d6458d1a3e012c63adc8b9dcf32254c98c0b7021ec6a8d579dad47d501715d2e42a0837def225515d663e663c4f000000000000000000000000000000000178daae121366ce025c1dc2d3e72068fd40ba9d54b2b3724f7a2071a59d4f17d4766a82364540bc31a46398c66d0e7da00000000000000000000000000000000147b2851311913ea53662082acfba785d21915cf00cd154b1b495246e109ac37c3fb6c63aabc4fe71a0d37c81a40148d0000000000000000000000000000000000122b7b1a81888aee37fdd6c23d31c38e79f28945cd1798cee3f4d674e923fc68311eda8ce45a561abf9c5f0bfeb4297b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e,000000000000000000000000000000000f5b941cda417cce69a30c1ba4a82cca71cb4b953d06d8e545c1b792ae22738dc006627da02b4344bb8be93a5a0dcf07000000000000000000000000000000000eebf4ac30fe0ffb905f81577466889666f801d4d6efe0fb8a663fbf1cbe76b2167243edfc6cde3f49d97d3040a9507400000000000000000000000000000000007ae6a99b86dc7ea95801776589472547ffc7a623009a592403a9710ca365510d85bbf20fa4519ca0e0ca208bf86a670000000000000000000000000000000004b5abf778c72bcc5b887855c582c042a4cfff489b0548785e4c1b735b19159be8a3f4cecf34c769a34cdefa722ba783 +0000000000000000000000000000000017b47384e6302b140118d0a9247aeae2091607ebb413ffa232223bb42d16753b2ae48e5ad0265e33616b25f0c4234be600000000000000000000000000000000167be566292b835a42ac7c099d80e8a0b5d4ff91d842d4ff6026876aa1570ef9641e9c0cbd44d8578f6a758717bad6f10000000000000000000000000000000014f692d195979abd9c55ac132d0def925d4e158fe946fa7b0a010c475d60171a0951d4b68ae3c463bf1136600a1ddaed000000000000000000000000000000000ddba1f4236c5200aa52f8cb7e15fac1f20cc66dc65ed180745a3eb8308f2c851ed6c1e27e1507d3f902ce672d6f8d24facfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe243351300000000000000000000000000000000023d08e255b244cffe911e43b9b48408f9fb3562edc2c27f405bb657731c885a58392ebbde9fc80cccee2404cc8547ec00000000000000000000000000000000088ef289adaf206afd2b72c93049fca2cf9292bf6471133c64ac4f42015b97bb9a23f6c34653e0218fd0abdefa56bcc60000000000000000000000000000000015cb78c1440f74b17125c547fe7a37611f01b83b91a351664c696e0f647bd2db3ffead880b96a327780026d74c9abca30000000000000000000000000000000004d1a63607b9a5c9ec31168d85fbbee77cea0ae93e98c8c1dde14d0baa72f91042b2b7ca489958344916ce79bcf286456546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000000ea6cb7ff6a7f4ec38ba11e9945eb406dbb8517585fef6cdd64edc970efba244b071fa162f7c8e184acbf71c5d1e12160000000000000000000000000000000001ab80c0dced33cac8a6a085efce71dcd7021f6255684bb631cf5c1716021bece57b900b819e6eb6f5b755b74c677b6c0000000000000000000000000000000005465fdd51352cbcd8b804cd509526c3b6232976b8278cec3b7db7da14b77f78898c6240c30943d1418462cb7a5abf8f0000000000000000000000000000000006b6caa6a0d5f2d671b10217c0ce5b3962b0c3edb4f2918497c316ebbdbe1a15c803d7fc3413907346f0e7d03920005aa9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc00000000000000000000000000000000170db23154805a04013052a388e14b5da00e65b35b8ce2dd967213a74735dcbfd28782cef1ffe9d384be3ecadd101e9300000000000000000000000000000000082dea309092976408a379f1dbed9d8cf91f768e2921e49ece458859c80a1d9efd4d5e588470bd669c777d16f9d2e7de000000000000000000000000000000000adba8ef34e197689228e6c4e13be75b3d4732872c99b865ce7733b7a42034d6d4d7520ef7ab712f60f1ff87bc4d9d8d0000000000000000000000000000000005df0788ec39430fcd0625f8e030d917d8e7c251ee6e3b0e79fc6fa5f6fac2ad736c818bd833e58ec61cfdff52c9c6ee2c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000000922d8b5db6e415aa3acbd0d6065db1b492c92313260019ef1bda0fa091c4bf091de95846af1edb34516b1abf7d278e0000000000000000000000000000000019af4ecc4f278315ed90d67cf4d22ed6fc9af5c0d0ca654f6a74a3c4bc98588bf5347b4536f36ca8b4750c18464f9b7b00000000000000000000000000000000021eaceb11638bda8b4293991983f11cc60c1daa2287f4b4a6066374bac82d117ac3ea4ec73afc4372d254bfc433b8c3000000000000000000000000000000001037fe26a10305cc5dc11a65edc705be5a0082656cad53e63038ee57a79e16075df54331233229a129483c34d6dd92ec9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000011e63dc251a5a1e2ec83741682d90588b6b185365b33dba45458b1f56324a4900b04d61af155a0edb0bdc2971b7aaa210000000000000000000000000000000002dc1bd5448a2ebb9a02509af8777616ba9657bd3be65519233f0187df77c49fc931bbd3ec0ad5856b2ec0dfde476a870000000000000000000000000000000019f0cf8baf100451313711bbb0a0fa318c14224933897e74fb727b585cc8620b7d741c9ca2f0d3cbe14a8749aa48ef3e0000000000000000000000000000000018448fa9e05f87d4991ae1c248413edc9a8c3ee789c9c005e691bfc9003191ff469e26db9e42e5758fac79309a62942c5e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee200000000000000000000000000000000109b71c19cd36ef3078bbae25ce6d0e8f7b58e129407fe68ab09aa747bfb3e90c04ab804fa6b7a223c172146fdb14683000000000000000000000000000000000d297750ba112da88beb84b8bbf74ed134b59fc9496da3045aa6dbcd97c68425fd68b75508de113733602a5565f4c8a600000000000000000000000000000000149b8ba6e05b66d07b353f46ace4e583bb61ed18fdbcea0e941b8d9805d3168040186d1c961add494f98e4e7fe68824d0000000000000000000000000000000013a6877bd46557d23b9aaf371ee5a101227d7938c64503b04b39cc6cb4e8ddedcf5cb6865439c9f8b1bfebb807ce52e24a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a200000000000000000000000000000000073f440c2704fae6c86aca3cee34591ec03c362c2c5153a5e82c7bcdece2af0c58a3484b448c8bf4da851800ead959df00000000000000000000000000000000075a2c26372b482a2420bd3c9952fdbf9e5fea906dc8a4deb9691f8745372805bacd68a4838a3fefc381a2ce946ed1780000000000000000000000000000000017575b016435782cd09901afd2ea6773b11f5a983bdd19d14668d75362f95d055b76e5bf6966b1bd7bfdfbe9a939e4b60000000000000000000000000000000001569d74258298fac89d0d91a9945780f4c08d7af7b942d06255ae590db6e8509c908c16bd2c2bb634279debb72f489b5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000017180e36b925e2ce23c46813d96b919ca181481efb5d1666c4a4e9c8031abdd9521eb8228c4e3f16de0b33da4c73588e00000000000000000000000000000000138965bff7c573546d80ef7efb3d45e87ed20f59adb0cd7ae148d09a97da7feaf1b0ef2455ca19381762768a7d82f486000000000000000000000000000000000360bd29c3f07c5b560e2ac226112a628839da9db18b052991eb2d9c54541c1b5ade9b3c2d7f446ad50050531228120f0000000000000000000000000000000007105978bcf13bbe2bf5c8f7d165998c3ad99b6a2794c90f5b61fb7bf2472d307df8fc9f4afe7ae1e40e7f0eee8ef9466e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000f4434c5180ad10cd45dca62b8da790cdb912c255c0f33950f7039e3885b38fa9e9297c7b0a875380545839d8c4d4ada000000000000000000000000000000000d0dd1429e512884ac209f788b5832d31649a78a8966d3348a93f841be23c8e4e42d6ff0d6c27e8f43daf495c9582935000000000000000000000000000000001307377f55dfed30ac1a406671af1895218a01d063b025d25bdbc53f5f9d535e4cd8053c09b2cebb25d3a08365ab8ccb0000000000000000000000000000000004f5c06f505ed15aa7661249b7edd71855bbf47237e049aa951e1ea3ff88f98591518bac975ac628e417892f8e9e5523428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f00000000000000000000000000000000083336fa0b79691b4875ed27b2bbd2d2586992940356f6ae5ddd2021c5ddd87f07f0a5c1e8d8a2654b99182cc2233e84000000000000000000000000000000001880f3824f7cef95ae5743de2e17191848d8d30f0469f455461c6559ebc75a7afbc86dfa3ee17f5470f74018ec335edd0000000000000000000000000000000007c2b26353e86223e5dbd4ed6d59f1170b9cc9dc600fdfbc6c73b96f2c667a82128b1ae5af0542b11a7d1efae87c75610000000000000000000000000000000002427b7eeb497a20cf15c10513cadc9ea612f3ae94e2ae833d281734e7b5d1d50e240659ac01da7864a95b4cdcf88744ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d97000000000000000000000000000000000e72845430ebfb84f8e3cd3dd418f6dc528bf521aca4f9dbd798ed903ef0ea3cf21dd1409aa3759351be32b21d8e8cbd000000000000000000000000000000001457ad87f0957006192dff7d99815c35adb3635815e5d157542b9f52f1e9f8c0143a21a3be4dc1aea3a895689f4a316f0000000000000000000000000000000007e8544b1037ece2e5a9ea387e0f43b72e895e9c2ca4d205f12bf6df0b35ae62a4d62756221d6fff65b928b7358f48b00000000000000000000000000000000012c5c3167f6ef118c4044c0aafc85a337d305437d694a7bd6fb406dabb7364d9e90d74a8b327aba971421a5b3dd5d06988a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000001995d7cb79da7b6c5a0c8ccc5ba075d8d6d8ed3cfca85e8ecdd2b589986fa58c4cd4f045983e9184d79173678d618f310000000000000000000000000000000000f9f7f6bcff0f6fd621f3f8fcfebac132b3f0d52a34af33bb9830bd714d2982f3cc6674ad6ca668131a5062e5589df90000000000000000000000000000000017699b298a46829020e0299ab89ab6411af0a602dffb0e149053ff40ccaec71a908da02c8e611723cd06c16a8e5c0f2d000000000000000000000000000000000523b287383c1e47a6f31d397359941fe0bb8167aa11604ff8569969eb5ccddf4c4f432d2b6fe6f39204020e850d4f2b30eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000004760120239593cae5bdec813735ccc99a88129c707686cf43efbd48fb08d8da3086879a6042bf118879fcccce0736bc00000000000000000000000000000000105b8191431f701b365c66680cb4eb267681ee4da17ba55d47cf26d21ba1c0c3eeeabcafcc79dd87b6457bcc91e9fec600000000000000000000000000000000126ab502f66e732aabe02fdb2f7a665a9a43f6b4ff21c22fa976e7e434b08b606e9cf0f02459fd85f5a80a332fb3a62e000000000000000000000000000000000b2ef01adea6c00250f2f14c98ec6d6083c45019f3d166419e3a137667324f80c34b6b72e991daf72e2eaf9985d0f9287e8dcbf708682225fe3f71b7a687da23de5ed188e40585be0553358012132577000000000000000000000000000000000ff22a0db4f1b1679bde5853a7c2932501f191f4a9f25eed968a796219cef028e26070851a9036a05a04abd73bd6bd4e00000000000000000000000000000000097e9310749f52a4b645190069f4d52315f0eb2ff9cbbcd31f1781a68b2664bbbf27166e6e74fc2be2e5b1eb3f3d77a00000000000000000000000000000000015ca218d7d128095bd4f4b4f7bcf7666e92b905e551dd22745bc743ad0783b6ac44b841f87d3deac44617a7c9a341c55000000000000000000000000000000000a1cb723a4c378e5db2775f4dde9a6887ee3313401a64130a78b90d65dda3a5d9c8bcbc1a0d78c310c869a7fc4889954532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000018b30cc461a4e1fbefe209a709a21ae201bc6094b2d15f0d6dee5a55dd84ef56b62ab1b6bd513b27c84c638291f4205a0000000000000000000000000000000008a6f2082d6d510b280a270c09044ad31fb18b851ad2b38859138c9c2e4870fba6b607f682a798bf21a13bff116014d200000000000000000000000000000000150ef352d494a97d0a7ffe44903aba1611c8d81fa2788c0f42a6db48a71101e12f07318da5ceb1f0af3aa10cd4c26341000000000000000000000000000000000ffdf3b133cc926684e4624531569bfa09b1658e29ad9c3efbd5e9d18353ffbbfbf23a2ad80ccee88f8fa597416d47173ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5,00000000000000000000000000000000134f45e5409998e657923ca76ce92b7d2acc932308e0694bb22f121f8324d16bfce86f96c67111c8080289eada4b4fb40000000000000000000000000000000008d9063b7845ffc8400c0b7585e819043884f92e28f7e3ffa47a39e808cdbb034ef4230b6e19bebf083e939b6b686b0b000000000000000000000000000000000e95f8fcd6b5bcc9e00a580a99627d92fa7486ff5ea587df5dded24d1b0bb76d339f6765a5a2058a8e227f633ce36e91000000000000000000000000000000000393041eb33f2c63df3f40d8ea1e1a9eaa9eb0a46151294845e542054d503ef69b40b0b676b0e4f3e08f4d26c36a5d4b +00000000000000000000000000000000042a220276f12fb4e81c40ecef3a0d86634b4d1c0acfdc463df4e7501289f0be385e03808d44be053e6ac98f403a8a930000000000000000000000000000000004e3dc92e155aeebdfccaaa1d24f49efc8b02e4d9ba8500a5b953a96e0fdd58519bbf1c279750eb8b98616e6bb9a3f6d00000000000000000000000000000000086bc212a83b09c7361540349767896058d5567d4342c607ae9c07fe5f123d9aaac95ded6cdff0825edb5acbce3e2b6d00000000000000000000000000000000062598cd6d5680a155b6349cd51d636c1350746d17fe0fff5195f164ba2fa51cf52f8662f43d555f7be6bb8bcacca39448da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c00000000000000000000000000000000193e2c749e5bbe87dd5c306d822740969cd69ad6e156c323d217c08b18bb3f97c85aad63aed1e3a455ffa1b6d2a670340000000000000000000000000000000012a3dce63a88ae32a746e3812833569959021e1dd9518621793308f8f11d04829b2c3d0e0ec39fc48dfb8285b6852746000000000000000000000000000000001235488d01c380e91872fc57cbf618c3531a6bbf6cba9dddb9f07168cd459c9e866e44e9e5336369cffd8cc3a36cddc00000000000000000000000000000000010d85f85d6242b63f8421e92f1c37f64d33fed67e0cd3dc4b2b2cb4a7fc7637f9e049fc959720eae6d1f452159d48b78def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000010889825c752d0ae8445a1d0f3510135b9672c30a781788698f637c2f535e35788d76492edce8ea091223d016b5cc141000000000000000000000000000000000577175035c86c022e634ccc9a5beb96a36aa068cdc36e5a4fc2028d5dd099c5296d30a916d3b720f2e051e7d72e4d490000000000000000000000000000000017b46e49ba08a0abb9394479d693d8097a140094d0ef1d1ba7761fb601a686b0b2b4d49abc2e393a99c5cb760299992b000000000000000000000000000000000820f8e52c1b09986a70bff04909b044f671c3933de43a6bdfcbe3712310274ed880d7adc4947490c7de095ea651e578b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000001776b92f683069fdc006904fef8e91f716d9f6bc46306b042228088545f0e11a41b40b60722d4f0483250391febc0ed90000000000000000000000000000000010d5052ef2504115e9d9d4ca81c7080c0868cbed605dc7673f7f94f5959c793c96aa5334175e58499102ff76f974ced80000000000000000000000000000000011d1e719bb69d842df4fc23e8dc4393067d00f6fa8ee42d89b462a546414b91f68dda5378fa093b3ffa764b5fc63b1aa00000000000000000000000000000000099d0784a200e5d2d38773912cf1a49e813c871ede8c50da03abff58ec1943d2adefe66bd2feed1c57f5a80253e091d0a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000d7287adbe0bc3cbb35ab8bfe69064faa83e3e64d73a0c64d960949e10070081a99c35d1dacea5a3b9bf312745acd6e600000000000000000000000000000000034f1995eb8631e080378b22a51ace902ebc9da4589c89ab557b6aaf685fcc74ec1fcf95f6b9a31b7a45cfc5a1610c640000000000000000000000000000000009f56712a46c0fbc199c12d5eb7abd60e660e2c6d437007c34954c6234a0496ad0adf68cf759f8ea30980c9a78175e1a00000000000000000000000000000000073fae1cb78c776188190a4d7223f7cdce9a36488193dc06898919ef4d5136099c3185d352028760c753e9eebb52ac240b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000019944fc459fb601bddf10a3a7eb55f34713d396c3208a10089b8f21f4bf0a5e87e95ccf73e0bd90474d3e043f37a72300000000000000000000000000000000158445bd2b6d396a390a0bd5e26256587f980eae84d7a592b2b4d788c452d312b854427185a770084e1b4c7898962e50000000000000000000000000000000000ba44a1b912645354da7d8d9c694b1d5a9ff2d642fad31975171deef3adb0f8d92b2d3a8bef6ecbe0b8e90470b3938d00000000000000000000000000000000012a040a72ab035684bfdf57bf473ff59cd1ee01ec949dcc6066e5c8afad775ed55123859cdd74c7016a092bade7f991b571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf000000000000000000000000000000000317ca8fdec8c7c56fa3812157f9ca8e9bbf91013dfc7052c0795a04a1b4649b2147d9cb1a61f2c114a705e5133729920000000000000000000000000000000012b893d50fe5ea2eb528d1a04bc8596b10d4714a0dc38bdd5f0a275c07c846970106c3f7b5686685f5c809e93c57e2ff0000000000000000000000000000000014f018a0d13c4c494f4a6b7e836f0f2f46c4b7975d91adb93616a0555910f53574add03b905000f8492465c9b5488c1300000000000000000000000000000000146eb4ef1103b525dfb5c31bcb98e550245732fa252a814824258093a2397d1489df8ca0228d4f5df0a00d473d1566c454c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b0000000000000000000000000000000010e9962dc19aae8e92abf32fb9c8eac44d77f587159af4e3b3a080748322715a958d953d3c057999839a47dcc840076a000000000000000000000000000000000ccafa9761e654ba54a46afff51384f1c6331264082e23f94fffd6c31a1b1b568a391eac79417657f40ce2fc9a154a670000000000000000000000000000000007276b111c94130b2608827156021815faf2be29ae42c454f3e2f95de98d2f5b98cea3eb18335a8fa00e5464f8089cf300000000000000000000000000000000053550896e867e237086098f4493caa2520e8c97a05e14d0ab7012d37b7fbbd42a90accbf0fe2ac99e78ccf0be5c9c58000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b0000000000000000000000000000000016b5bca8537059362147911da9e69ad3ecd3b4a7c43ee7d6d809f46c74c16bc7d69bfb5d7c727b4d5d8a356a0458b59e0000000000000000000000000000000010f3a7eefeb3033a733af7d20c3c5caff4c409305de8d71e08cb9cefbdfacda41bb975c92c5e5f2952c3c1e2bc6ca8cc00000000000000000000000000000000148f5b2bd65b71184ba6974678f709c5f9e3f1a020e3d4bedfa5f5f66478adac47f06ca2626c4a759b5eae09756cfe49000000000000000000000000000000001301306d1259059b5567154ef6e4779fedf98c29ea967ce34b78147c5730f202e1c12d5b5094219bf85fa62834329b45909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000001741f739459f5d462fc9ab55c68101a5a3f2741c05b4c3eec6959b2aa5e12493a19d1b33a9aa89337add642458089eb6000000000000000000000000000000000300d8b7988522706c0690da52d0a67ae41344e43cfa05d22feb91eb8635bdb970810e993e0ebf8fd63ab8fe3e048d660000000000000000000000000000000007c003cfba125692b88feba85e7288bf61bb25e04b1462f7a39b4198737010224ce4b73a320c81b1f70119af34d381d1000000000000000000000000000000000a4870c9de67517f4353de23af21fcfadcfce55365ced33a61a19e5de52f98721b17c6eb382970e7c4acd81b80a7bd2f6d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000002eef52fc72d5aa0456c13808ba548cb765e11cd0bfd0599544793f57c8a27ee90880e6577af1b76b3fe32c4e71f4104000000000000000000000000000000000ea99a4f6772f8114cfb3ae9dc20f11a34880a86088511e5b7fe521d50470148b43f866eb5bf4f67c523266bb55117050000000000000000000000000000000004bd802b889e6d18df7dbd65f39a908cf5889e14be51b5ebd423ccb63e4e5b35e429eb0d4f384b811b47975143ea2ef60000000000000000000000000000000018dded357c546d709beffff2da0c08e8059c720023234c7b53d0ae85750b3e166cde7faf340697b546b8dd7c13b1ce7bdcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb28000000000000000000000000000000001405ef521bcc60c55f8551fb2e2aa7b10117b2f96c03e8535e5bff48ae197b7e5fe69a40eecd25a67f430ca02edcc9d2000000000000000000000000000000000477d85a7dfffcc5a2a1048205362ec42b268e5fbd27ee7c8d4ca77b5c9db84dba482bc4b164f92db2c15cc518b3d32800000000000000000000000000000000060988548ede00aad3682fe827d1e993ed1cf118bec7cbe6f69bc160f030bf87c299d40047a4fb5ee27dc2814649a4580000000000000000000000000000000006b9e0579f82fcb8bc149e40b1199f5897ea48ae5eb58abd2002c923efd0f5275d24a579bd904e49b7447c4a03e3fbe423d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000003cde2bfc5a865cac624d9018c37c1b5746b5394597d79c171b25f84d5fdbc76bb90ba5cf9db14b3b8e62ff91cfd79520000000000000000000000000000000017596885262075e45db62ca68ee5b99d12223bd476e36ed4ddbf5cd56a0c6e9db5d79e7f95b96b1bc323d7c9fc5447d800000000000000000000000000000000018333858871dd41cddb7ad2f179f1f341b2ef20bfc7a1d3cb235e3a1a181e0da7251911886f0788e0f868e16520c5a200000000000000000000000000000000098ce44092980cb14e89faa7efc2906051c9a51cf7b2757dbffa49fafa3a9ba145f809f1212c27aa620bf062e839f83c2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000001180d554fd523a51e0decb92e0134c6064a17dd3aa7b11d590b9b6022f76763b1e20562da21e836e65374efafd78b77e000000000000000000000000000000000488686f793dde899a3f4936f07f9eda7918450966ca85b4715d6fee978d9d091bae1b5d2d04943365c076a849b3359c0000000000000000000000000000000014661fb2d305ec9e63d63e9951d0f081aeba99972b094c922d2797a1100759cfe150812821411205f563e22f01ef29c50000000000000000000000000000000013dd681200608466853cd3bfd20f146a6383151931079654962684d6c6fc3bd6900bb049483c1ca6d2819da456f67e3be3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e0000000000000000000000000000000016c8c7a2a1a76ec05770f2d6c8df35003104c034c76323fedd49663daa759caf2f4fefbe8d44b3abf1dadfec2a06cb45000000000000000000000000000000000837305004aba2e322ae29e8f0109f1c756a44b21c72733019e63ff9886a639464090770d12d35553f0002ad028332370000000000000000000000000000000005c8f82ca2d4f6785e2d76ca3a3d1ac67aedf78e9ac833c52cfda6289e6f5d7a83befbeaf753abce12376889caec312f0000000000000000000000000000000013595cdc9181ca70845c613663367ff774f073774688dc58edfd0c58de5ae12df5acd04a673b645371940d7f7e1601045d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000010f1b8b39ea8ffcb6a96bacd1c00b413c93d3f8da64dcf9257a7cf0264831be23ca63ab8d3d1cea21ed8d83ecaa3a0c70000000000000000000000000000000017a9030fbee573cb71330007900723f85e9e82530283f713f72e68c1d9a5ff9552d0da469a4f38b66e30df1514f922a40000000000000000000000000000000018b9020986a49213d4f3b4b052cf2fb65f82b9bc2051f20b399f2784b984ccfa2752ca576d352c7d65ab218bb8d5df870000000000000000000000000000000015a375a3711f5e9f85ad7266b2d307cac09ace9ea36e149dde5e0d5acdbac3f62e1cecba8be51d88f2143c3070eddaf0fa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc,000000000000000000000000000000000b668f602b9f56182b74be413d36b03d2266d7165386a7f1f0d25d336d06d2bc5659e80e54dc71f153883292df1cd8940000000000000000000000000000000013151d305bba39734538fe9a2717392bcd134ef1f8c1879740c8cce981a2d70c94b23f1a71a0596e7ead55a55eb186c80000000000000000000000000000000000e5e7c268f93d8a9d3ce45db2a95be906483aefa3831ed2ab2aa357eca0ca231927c0e5031daa200784cba33b96e51d0000000000000000000000000000000011d57d9a9123123f9fb56e990626346e5c76bbd1a4b3349c3f7bc44f05a899f9c4dddd67ce5a788f85b4fb172385faef +0000000000000000000000000000000006a7946b50749147573991c91f13cdef4e9558be37736e3d990c5750d31ba9711721b88eec529ea4b1dec1c935fafa9a00000000000000000000000000000000078d8a565b7f58b915c220882a73b6aaf100f2d54cce2524cc3a80d9b526c3058903668d17427a617ea045c3322ec502000000000000000000000000000000000733c6562cdcb28d740c64f50ee9d204af4ecc8de2c1719fb73c20f2580fcf01e1e494faf4386764e03920a4162049fb000000000000000000000000000000000421365fa828affe963d145d318065933d4d865f2a3d24469ab0db66dd09a574945f8a8b622d079a7ce1c6fb6c795a8f6e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000a4fdc68bda287bd819ebb0a296212ddd19cc76b042e134f1637c894ad64bcd8431392c9791f2eaaf94f6c8d189846760000000000000000000000000000000009d974fcb46fe81d81d62b24b805ab5108c9450e162454c3260ecd0d5356b7c263be5f78f6214cc7254e461166910d23000000000000000000000000000000001081fe3579cb4d8a7e7d43ca8cdda28e1f9ea8df83c6069f4162a2a0e68e0d5876b283193649018e754c5c8fef101f53000000000000000000000000000000000ca4faaddc4d14a6648e3515a8b9028190c17f771c7de086fe4624a3008d7e6e374c967f303d9511b9da1a95409e3cb3d51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000117318e376f2c130e5bca89b3d700fe76e9603adb22a5ef353bb3b5a8f641c85deb4640fbaccf94e025a59fbf2a41370000000000000000000000000000000000433428497ed89a43ba07d816df224809a827194ca899924c3844650a3800952cda8db82f2f8e513994ed9893fac747400000000000000000000000000000000064889f1cb7d6ab216fcceef7c4abf89be14ef93be2d39bbba2b74d06999dec5ae1941b507709d093b28e030700cf866000000000000000000000000000000000957fcb8658497802e78b8250373f77acc4ec47fa2c87e78adbb2daef70240da640a7945895940f76bbb80bd36b4ba24fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000013f9771c105462fb6b975b0b2fd20d0accdd2d95d879c8019b08db394cd785ed9f151d0eb1adeaa63bbc2686d1172b0f0000000000000000000000000000000002062a5f2db0a01114a1c6e8c739f80f598f4e905952101a244853078298eb443be6839b59d4f0c7745b739cc89ad8220000000000000000000000000000000015b5485439f1b94fbb3a8f5ac6197f0dc0577863f39c44b34d4c5437b6a82a704dec17529654b3037a9ee1ebf14c8d8300000000000000000000000000000000154d750e2a660205812d428cfe79aef4e1059f4e231024a665889d112af37e6e17e04cd7c926b6240bf2f616a1f572dd7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca058129949000000000000000000000000000000000231223930956bd2d36a89a0a0a47aa46b4763919455ad3a3581439d25a82c176569698fd5ca2b9429793ebb16c98e50000000000000000000000000000000000b5dd675af51c18d2dc76e3103da4409f6e8c1cc719a622d4a33aaae3f23e529c78b63c55b67fa999bdcc7095a4ece300000000000000000000000000000000010c971be55cd02e4c97031d3b25acfbf722e47e5179beb26eafaa72d4bd5f47cf280a99e0c3c4cdac05bf1572d01fcfc0000000000000000000000000000000002c1370919e6445994df1e25ff4a79c8cd8805f12e5d8c781e58f04dff68a97424b35d162d875ca2b3f805b4cd6d1fa641b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d6500000000000000000000000000000000169938b4d3c859f97a0627bd1a83fde725eafb7ab77b22cae06d2a776569236d834702081e78d61386999c938c0259b900000000000000000000000000000000091e922f00828488e324f9fea652ce1edff83d9f479e843ed4bffee27805a5025e7a150719b354b5e61f381ebd24d4ea000000000000000000000000000000000334ba8044d7d47795b59eb089502808a7ab8f18e3d5e1cc49acdb5020b3973fd21d6d82557afd748dad88e45a7623730000000000000000000000000000000002299bf949ef249b5057c103ecd149444635b4f636a2fd0d073484404c1ff4ef71820260ea6529bee6f5b07f2ba94de35615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc00000000000000000000000000000000076ab7838db87727fd653a3b561a2a5594518f296284bc24a7d215b1fbc0a6492d425078fd98f92a414dfcb3c92cc1d000000000000000000000000000000000022b71fb467dbd6d9b130763350bd06f52d20ff2cbd46cdea5e8b1525fd73bfd08f5ff171f9fa28050e9a3b296d3e9e00000000000000000000000000000000007e917cef0195fc589317d4a71c14022867dbc0db26c653052e2e382d0dbebe67a0f582bc0a27dd1dfb4703c545d0da30000000000000000000000000000000005b1d8651b86a403ad993c5cea4b6b82a0f8a9f8a59d4b94f10e68e9538a559efdde2007736aa9d04f585851a89af88fbcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000653c5f5b2d97239821d173036929dc716e78d835a80af55868dcc3e218bcebdc2a052d31f6a573572d13f3bbb14f241000000000000000000000000000000000cdbdc3cf52239a6d4bdadc273b00924de8730c03ea82bd20ec1f04375daa4497fff3a1726269a736706355e72be83870000000000000000000000000000000008e0285b177fcd768d3519062177fa1314c4370f872eaf10f3e0dc94e716dc6a67894d887f40104552336cbb5ed614f20000000000000000000000000000000000638db8269ea4c2fecd5b45955609ef6a1c2c6faf6ee5a8d777e0b38f16d1acab2da7fe7b6f6ebb315ccb345835a21d94c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c40000000000000000000000000000000005b4f74cd099eafa6ae59e7105873d4a46e8e5985faf2d26ca564125dca93b1c48187ed7afa02cde8b52df878e1aa618000000000000000000000000000000000cda7f9eeadda16ef757ee8a98be147d374d3a1d40790d20a1ae42c9ed38e4fe22be76ec4f807cf93fff5c6efdb50d1c00000000000000000000000000000000121219b0b0d236a89a857c02249cc04c22299d041d95296dd235b3639416337f5be4a2ebe92a50d192fb748d5d4dca0300000000000000000000000000000000112545a4677ca7d60645cb8bd98689c4aa85a68bb62dc68c0affca5a17ecf0a08fb9b91589d08712b5af4aadf31caad2dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f2000000000000000000000000000000001342460712b73ca0ef07d953c32d280a3441e108abdc2d133265160608986481df3563c5dca20f209ce078b13b49707e0000000000000000000000000000000003580a5b4a7f6d6e066ad9073f7105f6cc1ff35ef5e79a0aba7f48ff2b732c7aec72cc9c5f9233fc9c267d8aa37ac17c000000000000000000000000000000000bb7f32db8a4e341cd9f8dd3b5677dc650cef675f0923bf2e5c8b84c33d447daacbf68631c2388eac5698495e1ad5a3a0000000000000000000000000000000015bf9cd1aa585eda2910128f2b452569abc1c94bc8bd308ee92b6c7315a56fc92d6cba03334bc36c137c14eb1f198b07d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c100000000000000000000000000000000091bca266255d692cdfd10929802d79b474706d160033495decd11cb0758136ec3ae7fd4bb99081e44dd7f25224e009c0000000000000000000000000000000001fbba1ba796416ac22c92f3741e3b268d89fbf0307edf0f25c7c12b5cd230c41582ba69465686ffead9f8363dc0c297000000000000000000000000000000001139590315fc4d81e3e747a53e63ad856635050367ffc143c1422e324d5fe9e4fb90631ff8bba764a87b8077b571aa0a000000000000000000000000000000000dcbba28afd445a57db762d08338a26980b4efbd11668e4050d18234ce35a909d6b563a5d3e8e72892514431fabf0147aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000001322b1f1388a9dd2853829bda1a5120250ed08f07c84fa398e59fa2577454f38f0a76a1e8db897bf15b4b50ff52a847c0000000000000000000000000000000017020d7de1dd424de53992c168d924c42f26231d184ea3cd9cfe64ad9c82ad067540b2d9ab18b0fd28477ac792a80c4a0000000000000000000000000000000000fabc0769b95e6feedc2165bd6d324b7d16247b79eebc1f09d849792255136538e628bd6ad9b86af7bcdfdd991fc31000000000000000000000000000000000144f39f792bf5585f4b49dcd3fcdbb61cc7ef471e08af4c15cfebb855f0ac8d5fd057c9486e53e8e1ee4f66bd5e943ad106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a000000000000000000000000000000000c27ac29db98fe3038fe5f537d5ca6faa240602abe11c6f530d9b18d763d6dda3fb25f9538d316e6527c114405ae54f00000000000000000000000000000000017ecc872183413d8065a99a2d1a73b70150e2c1fca2c13a731a39b52aebc6db79772e91f115a63f7b23e5fa231df697a0000000000000000000000000000000016b9715ce820b619274202b52d7e7bee9a17aaeb06c2ecab8bc77c670bd4c714789e4478178d94d2aad57e7bb0b7a4040000000000000000000000000000000002d0723a3386248d8597d2b63289300de6a16011a38985170a1652ff81ea70a78459b3ef252cc5ed26ff1ef1ecaf6a42c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000eb1ddb7306d8d2858fb57dac71f67473b813f37f02d73b17f375be86028176cc1dd84347f183cb7d427b861be34c3d70000000000000000000000000000000009a8811ec77eb21f2b33a591f2fe6d7b74b40c5045ceeee275912aeec664838f332bb49bedcd958ede0af0d0232e76ed00000000000000000000000000000000156e28ee3c40c6f18c6059e06ac8f7b39fa23e5962f640ef3afce13c169346a4c8e5c2bcdac8fa15921a4740cc5a0f2300000000000000000000000000000000084371522a6ebb1925c8fad3f20277c34e657aa71abf8ed7d323a10c14cbbb1a9e0e54bace32eb845e6709c1c58afc34a5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed49000000000000000000000000000000000be2ef9ef38a5dcb42ad31b1415c8eceae625850db4306a26a0598d4a567936d75b701c81793fa7b42d158df2dcb0d5d000000000000000000000000000000000851b82b59fc15b89e33fb618c56d11a07116ea35850583a07066ed97b8a864f3766c0cf921d007a6cb43931ad4fcf8e000000000000000000000000000000000ea8bdfa3c5f000d7cb1b5cd69537e4104daa15ffcec06f40a91b972d8011e5fccfa911c55a07383cce6760c145c39e4000000000000000000000000000000000652a4165602978004ef702103ef18e8fe7decab1522a76486c742d29103e3bdf6dda2d3cd64ff1b5d5a76f4823bd363083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a4600000000000000000000000000000000015ce5b43e1fd950b77e2baccae8c99b82f38bce09989fdc5d402420e7931a38b7fdac5a254b0cb9bd8fbb488d02493b00000000000000000000000000000000018c5b3ff46a04ed114bbf56399738e5d594ef8dd1d5e2e8dc23a0097893be3da4fa4662686a6dac04418fd2d344e36c000000000000000000000000000000000efa3e970a5cd0c7bdef6a2df3be9be18cce63c10c331a18d628bbeef30488ef73d866f3c8804acb3bd375542e99eae6000000000000000000000000000000000e966d9e2f2d47df5d661a89fafb6d4518fa1544ab7a56716df511cbcca99098f944a981c9da569cf95debb455842006d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d,000000000000000000000000000000000ef06b515addb951b24e5d61f6e6eededf5f93f9f17455e1b563f187f73394457b3b7c1b90ed454218f8782d2bc848be00000000000000000000000000000000167398608a87490fd17506166bf54636aa4dd6d3e8c4d42995bcb0262268eaf2a6d828b295434f45e3e53703aa67cdcf000000000000000000000000000000001602ec6519e4987a052f97eb222f505e241d99602c08ea9c41bc95796675ebf6a819aa0bf87319f29dfe47f45f3c8c7a0000000000000000000000000000000002ad4291ece7ea0fcc9f4440e88eef693b8dd53060ec847bd27d74cf71218eb6210a71895ff1f1f4537a901090f14de5 +00000000000000000000000000000000129cc9cf82aa30671e969148f058a0b8d275bcbb1c8da66e52998d9555dfaa075e2fcf39fc18f305940fbc972cc5c0c0000000000000000000000000000000001252482c1419ad72229a00d90f1c09d464e896f47db91e9680efe36822e99e1ca7a2b5ba8b17b5929fdbeff8993bb18e000000000000000000000000000000001287d5054bf5db038ec2f7b7a3b79848fcc8ca42f9e19d5e21d36d2256f97e0edc2608d19c17d3714024e1e9e86ae264000000000000000000000000000000000f6262669e30a5db67550cdce8a4611b9501d02cd4e950aeedd1c87c4f0f63c74f35c802dafcf91c988a154dd690103c67c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000019cccd010df3e668b1c3ec053e76c46e45d01a4fe02eb074e296df2a48e0e4eb647b06c40cf64a0048a8fcc2b0cfa6100000000000000000000000000000000018e07bc50657f3bbf736c38518933e91af29e3bafd776243296cca3a1d975116e8b428b050045a61069adb23baa22d3800000000000000000000000000000000154f51badda1b828346986264b01fb1be4c7e9b570ab63a5eb15cabe9412a2f9bbc6d5111c638ff5118a4f6d08ed055200000000000000000000000000000000064d4e607a8994c0bb65770a14a14ed1b68c766ac2aed45a44c0f7c7cd4c3ecdfa077206812cf9b24e35021060a3668d2d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba94000000000000000000000000000000000b9bae5f720d9bd3271a71d751e4c35c39ad30fa8a67846107ff769c455e42465b2a39dfa32861634d5e323878f56f4f000000000000000000000000000000000a1077046cf5c7a66452cec2193ee21c1ace50dfd79f707c9297737f13806dc05e9e1cc5d1fb4c87b250ebea5a4ca6c40000000000000000000000000000000013e1fbe1a9b5f2ccff51120590ac0cb00cab502726b43a6fc12459e27bad4aa41537d6f3cc94a81a170998768f6a0fc6000000000000000000000000000000000a02c551ecec1c7a415256caaec1b5485a42f9ca8d897cf26546ace1f2bc8c2d10a353b8b84495b8dab5e3c60881185a58c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e700000000000000000000000000000000021b9ab3ad614816d7006efe688e1ae8cd99b0c4437d4363e557642a7cfda2000db6503b32db36b6d1ffe40d967c5efd000000000000000000000000000000000b7fb0ddd9eb2be9cdffaf8f8c593a9296c4c7deeb1c714c11863d71dab1e6fd309b75c41e25de3cb6089726b43427f0000000000000000000000000000000001277065ea9d208777d0fb7a6726e11c8330f0b3ed3c6716acd559aab19b2fdafceca8126c9facc43b9d80534c07035a20000000000000000000000000000000015e8c12065d601dd5ede75bcfceb7300bf6f9fbcdf68d2f093b7654d80d3e565135d64137dc401d691a45fe052f58a6850a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c32000000000000000000000000000000000484c4f9652427d0649c33e93d666dcb15bc56669c00980c53357ecee874bcdbaa016236df65a4339dfbd44e4eb0823c0000000000000000000000000000000013836a7275c29c989891c94e756cee9d6c54a8f634fa570655ee44b7c1e34137edc33323dc0d1f3a0218039fd6f7013d0000000000000000000000000000000002e88c7d5fd87e97a0de1be95021821942a8004115fb4fdd9ad26b7e0fd171f9c7e6f962eb179bdb95ef960cf9396372000000000000000000000000000000001636e351a0ed1a260ffe0d1355e6da288792fc97a7310b040cb9fcd5c550d85d90572154d58a9847dd5a8a06456bb2e43b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000007579785c14fa012cb5d6c116d34dcc15dfc908a29e90de3bbfb8c9b44e0b4258644440d7c78d751a007c10f98053bd10000000000000000000000000000000009f023538822ceba0883a0e3454121dafe8e5e61d4754b54e6417c989efa998334641d458591b3076b615937de065cfd00000000000000000000000000000000130fe7f2d5e0ffefa67ad3378690c53a6e68de5504f3691de0df3a24c309619bd3a345bc2bec4dcfb4b77255cbfe09980000000000000000000000000000000015bf85ed997eef4d97a81f1d75825bb4409cf86b8c8e5f4368cf1e4c803f9e1e23a2a96f7b0a08e5cff55a78761ebce21d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e3921000000000000000000000000000000000a95597e4402bbd17c20dda088f0134d42f14443bd519b3511b28fd8d395a0e50758386498388ea6ad0e7634587336c1000000000000000000000000000000000079f348d3de505875c5192f795cd77e2f7385ed447b06f2dbee18e85c832386b201cb3eeb21aac3f258d2c4b0434d48000000000000000000000000000000001895b1891a08ea42eb1f68698abc20394ffd66bf0c32979668950bfec5cfc8425314eec2ac17ba25f29133a8becc9f5e00000000000000000000000000000000146160336d881b24c6258a3a86c08d346900680324632b6d5d4582ee0865a7e5f2d01677e5e49c5a4179f8382e49d1566fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000013cadc6c394efb2f93e00f3976dde34efe75adff34bfb6f5e1a150b79bb5baf6bf29fa149581fda48faa68653cb61e300000000000000000000000000000000005fd25362d87f9581a202b186d2786d2859faa9966a1ceae747dd7a48749abd424eb9813e44caada0e456ee8bd12e99c0000000000000000000000000000000018e6b279e2b545acf1da29dc0504caa5982522546f83d4d3389e1fd6cb5328d4a167926a00ccaca402b3a3cdc67d757400000000000000000000000000000000089a9992a36b476fee21abd50977dfee01d7c91b24b3e26d7c15b2301352069dad920f0ea93a3e477a48029eef35605f06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e1188620000000000000000000000000000000005200b78dba7e423bc23e87c5937b464e97405f6461d05bd9d1d0fbf8f3c8e64a39081f9e43b4ec416198dc44db897e7000000000000000000000000000000000bdba1ed07c4a570359863a1098a73830818b3fa5b222316a3e0692a4ec65e59ca6b4bf5f72f8c1384e73e807d272d6e00000000000000000000000000000000073fa3eef473707b6aff37fb6f829f0fdb7ae808e85ebba4d4924a185c3656eb2856896307b671080347cebc32e958bf00000000000000000000000000000000076b56330f07cfc0ab34e98e2fa0ce4702b296a00f6ffee07c3ab523fadc048a047ccea7a9003c090951e4ef698d14e5fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab0000000000000000000000000000000007252747c8275f87b21bbac4071c1826d166d14e6205095e5299315d6b6a85aed995f9ba59a2163ce2c51a8e60eaaeeb000000000000000000000000000000000460a000fe29cca24dec469ba5fb729edf3e443bb032d488cc99102a614a5251915267db003dbf395132d815ba78f262000000000000000000000000000000000161c01cb4d0942faf2303c108604babbd4cabf5d3d30c13d7db9428a445c7f72d96a7405e22e4e451058a94e20068720000000000000000000000000000000010ccf8a8ec4e6515b20e07057fb8cbecc5defb87480f3e32a1bcd0cfe239e00daf6a390c4815ef6b85be1f07a4c4bfbc4a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf000000000000000000000000000000000e6cf781162502d2a758d0f96946bab887591b7c9ec9f67a1b0b962e74ee514e84c14bf67ae3c0a9ea2a3e472b7ef59c0000000000000000000000000000000001542b4e97f1e8a64ffd51ca43137b0660f897f6b3d5c6fee598fc4dd03932c3658ea55e1e9e73376e51df278ddd3a3f0000000000000000000000000000000016dae882ba240343e752eac68122424320d1acb1fbc4bd26c3983dd91325f25e1b1f06213e0e06c142997a13fbeca597000000000000000000000000000000001138b71c95d4de320f02e68dae9bb0de3e5b317cb596532c5cc18ca588cc8566c21551d7d55d685591126b9d9e466455cd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf000000000000000000000000000000000904f6a09f3a5f5baac902c702b059835737c06f62c2ffe9101bac32f854e4d72f74031f5410a5941612b1aaacbb50920000000000000000000000000000000012f39e7022150b2be12cdd621ae23525581405021b21cb9e55972724a22b1aeb2e15b135ceade132d3310e050e607f65000000000000000000000000000000000a92b1daaf23524904d74c3f149fcd2c98e3a4c257113533e7cc59c4656b785aacbf0ba6b9df0dc17cf7c25f1ca698c5000000000000000000000000000000000a20a5d7c0aeb16ff498f46bf05e512784d120b9c3c8b2877411852d7da3abde9e83a6d00213bf69ed88bcbb051a486daba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa00000000000000000000000000000000153918807d7da07ec7014154f00a558ebe0d5fb48fba4c16488d61a826a1eec28e3828d6744300c04207e8ff1cb61211000000000000000000000000000000000a755480457896c5a3fde35658e73fae821151c43fb92e9ffedcb05fabad37cb68aa24e029fc33a2518398d723c4859100000000000000000000000000000000148798bdc5b14b90aefc38946db93be1754f15d78762f38971b1e64a53fda92b96b0a70ca2548baec882887ae7f636910000000000000000000000000000000012299fc413dbaa77cf8867e331bc0602c4fb32fe44a150217de9e6391374a9ed83781034e5775c4933e13cdfffd25a9e6ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c000000000000000000000000000000000fdfcbcce1603198fa344487d2d4838b3ff23fc0a73a76222707d9f8623f0b87dfc816be8717b0b12667bee460ef40f70000000000000000000000000000000015036dff68139419db619912e2d19b7d2a2d637fbb8bffcd941aefe2eb4d24c1f7dc32f4f53d4cfce67785e7c328d6c4000000000000000000000000000000000fd575be9bf54128a9a1cbd366339c993ced315a840d60f8b77e035352bf705c01a9def713e8cae3001dc1062cc0723b0000000000000000000000000000000004015ed456125cf0f46fe0093b81ff9315d955d470ad756a9303f548819f339e137305c58e6f4d8db3c8bbfab90718d4f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000016d31e68cfdc5823970c8c2ebc53c3d4517792c44e90c10f920a819e72e4a6966c59a691b905c8b0b612065c56d86ca40000000000000000000000000000000005096d516e416fdc0df552c2688c74f1c067a3e5e7fd782479bfa468096e6ee3e601bc23d2e38ae7500325765483250600000000000000000000000000000000092c994e9dae287bb6450607a4263bcf6267f0f66ba3e63436292af7f6bc8e4ba794a12792b6af49ef59b5fe50ff6d3400000000000000000000000000000000175a645988f33612e969e1d91b2c30e47ec655ad655d89cd8dac151c3bd194cd5a8c28b498b1cc2f2966b7fc37cfc8c532b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a0000000000000000000000000000000008d09ee15c80facf7e32b15418fefbf7e80400acf37f2a1bc6ced88b1591bcb8f86b45b544646c5fafa71b5b103b927400000000000000000000000000000000060865ba68ed8fb3d0a05779c278352b22d4244edb7add23d985a2836d2772dbffc3c82c3134916e9b0900c9db6ead8f000000000000000000000000000000000dce53bf8aca1ed44bee47096dd988689c1e32e1e65a5f8dbabab7c4edba866132ee2c036aba5648d0dafa9a26405fe30000000000000000000000000000000003319995785be720860bbf48692d1507185d898187993865648ded74d3aaca45df939c6dd986db42a51bd13579a55b8f71a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6,0000000000000000000000000000000010643af30c3cdefc30144c5d7cab17c9c54adccb3294ae79fe5c69376011c159be1e43940640bf5d9012ccdbc997e2090000000000000000000000000000000002a22b08904ea9ca99103a01caad745dc2afb7b6d23e666770e81a97031de921f9d4d1c04fa941c433b8cd9cafced3a10000000000000000000000000000000010808e5518eb6cd61eec8820b9f279dba2423b1a3677e21fe3a0ca2ad49fbab2995de1c5adc9ac867de79e3b40ffddf30000000000000000000000000000000003ce1270644d71e0055345c7463d72dc119495bfa04a818dd398d944ca46deb0aee8c7936557754fa18225522fb79564 +00000000000000000000000000000000059234bb6d1b66985b79e6a2c6cd158d37fc50ccd6e50ad2fe3b6a02ae2ad35b8b2be92139265957ea698441e780532100000000000000000000000000000000066adc7083a7f3dd75a8e431a36632dfcecc2527f261e961335bbe8fc8329d992880736bb41fffe41484f68c38fda61a0000000000000000000000000000000006aa0794c27d3f60debbee292c28b430c159cb2874b9467312cb857a8777058580f8a2d3b9bf4b8b732edb185cca6ac10000000000000000000000000000000000d81f222ed6acdf29437adf26d2b785535cd6d61b329df98be04114c3ae68bc6854e275792fd48de3eedf6ba7f3849a2e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b20000000000000000000000000000000013dab8066757b8bcce2c9965e600c31b792463623cd5561f7f6d55c5a52c22efcbe48b8684fc2dca87e2945765bf565600000000000000000000000000000000198a0594b5e606b18201fe2706bddffe7bee6c583147513333230715d18295714055b984cd3ff8d1127f9420863e3a67000000000000000000000000000000000ef77ae1e991daea1fe8338cf236ba959b22df4b24f00c6c01483b6956c609b805ab89712f80892bc0160fa3775907890000000000000000000000000000000004d30f5a866a100dfe469d4d0c47872245c4cdcfb18d3ffa0de422691044b52d2b9335682dcbf67aafa9275712ae3f5512a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea77783559470000000000000000000000000000000008010bd5fb5e222618bf4f919c203dadf9a7b7597bf90e16772020614481a0963a8e8b1bd244661bd33e0d147be7663c0000000000000000000000000000000007e21f548efb869a28d6fe39b999ab7fedae9cd6cfa531fe608476ef30c8703951839476811838608dac1aaf9cd87eed0000000000000000000000000000000006cc674c464f80cacb2156fc1eb680938cb38cc166a99f72daa50f9d2c40f10ff07e447d7bd5e59b6b22f0dc407dd8df0000000000000000000000000000000010b160c58ea82bc3904302b1b4fe83d1883efe3c8f52c4e05a3d8681e604eabb1b7f533e61c51e9a172987383506e7b189a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000000d4f8372d1138489913654a7567735be1eacf8c7fd497c2216bf77a94f483bd4a7daa2c8232581d6815af9898a7569b80000000000000000000000000000000013676a1f72cc2ed80fa24f70fe1c52aad9ac13ad6cad1f519649fda6ea3787d86ea164d9ac86de96436e9db4fb4aa44f0000000000000000000000000000000003f7644f7ddc9276ac36aea5c36451f3d5d6e4c508932b16d5677977108f04deace5e8cf0b3b3dee88c328b6030f3567000000000000000000000000000000001953cf03effb7de9e62937572850e9fdeecb74fb4aa655de1abdc6e065920e6d2e51ff28880f33341443b5e6652eff4827dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c76a5bcbd2a61172fdd53b351d143bd30d460e398c9d4b7094a604ab2c0d46d6112bd8a5483c9935f0bf6d84df04b9500000000000000000000000000000000116b15825b780c49fb24617dca620e939e2528e10c49f34971736c82cd35fd3965088595deb86eaca3d3239c6c78a84f000000000000000000000000000000000a0cdaa541dd96fefd46b303b88f1dd4d24257b910a596817f1d946873cfd60ae58a88aa687ba573832331e8fc158db50000000000000000000000000000000016259f7285de159a2c6d6d8687ed348ab97e8cf329ea5de49b6d708b6da5b806bd012ca3641c50f479d85921e20fbaefca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f84000000000000000000000000000000000797e815a98d362e1d7e2ac1fdcb477ccdec8ecdf340d7bded36856ce30e92b661669b38ecbcfb0896b2fd75df9b734a0000000000000000000000000000000017916c559db6b4b28b798c2027e2c70ba1b940212df8a1649b9f6087120660d698bea81258e2007edd4aa7d0d535bccb00000000000000000000000000000000167170a76db0783a8c3228f8246502b15d342b019fb44a46b514f4ba2de3ac66e435941adc3d91874371561870ba87150000000000000000000000000000000010097a585eb9264ea96904d8534820be185d8d9e4b1616439a926c0ff8ceb6f2bc082e5712454690c9c05b8018a996235aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000025eff57e1f37903056835d1b4133ce064c86947f35859817b2cccf1a5c3923ccca766b3e0affd20a4a6df62a45c31000000000000000000000000000000000011158fce4ade070629162b2b6cf1924696f1f7776f3d623cfa3d54c66fc17fa0299c6650b709a1472262fc0abe8d9557000000000000000000000000000000001828a65fb90dcebe25413566deacf0677a3993b39d68854b264fe7807097fbd3106ac618545d3a6a42e197c65f0d2a7100000000000000000000000000000000045eb8164b6ec874467286dc3626fad3c01be61f6a8a88e5f88797978463db648a9b8a1e1a2589364ef2879cb5f75423fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000011847bdf2f67b40aac3426716391da488a8f0462b68bd35a8c1c762591e2f426f48f979a646a094bce16bc99cef7fcfc00000000000000000000000000000000092d61e408120b1549fc8d2174572eb7ed3f679327cb89754f326fa72fbff79e98cf5ad9c94c14dd86135e9aacc98b98000000000000000000000000000000001440e2f4ee2ba254a780a31b02babca093a38e5a1ac09ff388080b6c60918ae5b26e1c0888ea0976527ba103b257d02d0000000000000000000000000000000019797e49808b756128866fae0d6aa7e755a1d6f07f7e6a877bee150fe9adf0cfe612350c5a0e31d68cbeed226fa56f2a28d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b62240000000000000000000000000000000016d723a64ee06a7a631509c6e64b1c8bbe199952da532dd92194147bce9942cb4a76f2358e6c7d263916fa36e2c0c09d0000000000000000000000000000000003d04ce655cad1d63748f6eaa9912d6474a34820986835f60c812aee9980d3ebc18d6fd856a6de9546be024b2e95126a000000000000000000000000000000000ea840bd7f76f8e944f95146cdc9692d97e6a2d7d16d4a7f054f81888472da4d60ae5faccb72d3a05781b399536ccb1e00000000000000000000000000000000155a1c43c39e9dfc6d96e01c981662900fadf1a46aa1c2fdb70bb34e94dcbe86c4f255e259c771ea8ede50b388ceb2f61208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a290000000000000000000000000000000008f189d97f7d82aad87fb71d090a5c99d94079c0b74beb3dc860d440c0f46727fc49104d671bdcbe5b9551552e18afc60000000000000000000000000000000010c4edfb64b8932a617c316820cd27d3f6ffa89b471949362762af8e10d25265b84ca2aafd3b14f33c39a4b533da60d60000000000000000000000000000000017ef3bb919b087fb6745bbf115e2929394fbc9c89f65e7d591f15da93ab785aa6828ebb6ced99d3506810647d28ed814000000000000000000000000000000001591d8213ab349017cc93f1fbe6aca6765dd33ac1f468621e2c79e30aa73bd7606a0e5ba1d97ff03e0029dbc8ab1c5f4d3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000019b3a2df3a9571b066eb451e34d8a38c0d90b6e365862bcd92ae76195956c21c59441f0cd03cc69abdf4ba069759b87f00000000000000000000000000000000082537ef7f4bba5f32db4443abc8eabceef643b0878ef83860d75ba508369a3b459cad96f1cfd872df99548f656b0f9b000000000000000000000000000000000b2fda5ba0c405c9481edd598181ed8a59a8a18462508af8c5d66988a7a58a5c9635d93b5e0ee310bd35e0091fcd4986000000000000000000000000000000000af7e15e0052576f82e36e7e2b614dd835a290e05f2ed9dad7f508b4c04e8d437e7e937a7f4c88b5e66b06e0beffc4df7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000017a1d5add5601010d138263b4793149a02e8f4f7cfaafb69fde7b843a51cf5f0634e26b6e5e3315420d44b0fd205230d0000000000000000000000000000000013ea863ebe1b1cfcf4164d78dbe8fc809d2b82ef3e5a2589ca1357e48dddf2696e910a90301ed910fae77a1e462a5b1000000000000000000000000000000000012b40d9f25dc5a61454ddf1fa9c38e87eee60e55938b411bff9cf2161ebd7d3fc930131a198e7e97dc90cc245164e7000000000000000000000000000000000054f19ed8e2682caeda10c252f11706e7f3b65c81e7ae0a617469babc5f3268fe5c0ce2e85d44fb6731e8ac132b97c3ac5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f13000000000000000000000000000000000ef0378945ae4683666099be36de3e60b5bae9c3137b702e5e4e35afd5c1e81d033c3d6b1debf5bf36bdfc4e3af37759000000000000000000000000000000000c37074af84ff596ff2c7ff963d96968464d6c8d88b69af64ae883457d02ee9ec80720661f39019230a6531a0f2952bb000000000000000000000000000000000454e8aaa2830f07d86eac7aca1d7589fb06aed646146a1b90f4959b5caed73131ab231313b50c15213f89566ed87a3600000000000000000000000000000000143516cd7a1b8da41226cb828887a0b3314cf4f87c207d1d84e9c49f0f7e548ab99e635bd126d49fd2e4dcea98f3adf784ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000019384e15a8754c6d85bd298ed550a26b51b714745bf2980b4920d6e73f59e657d85d3e86baa9bcf7e971233daff99d02000000000000000000000000000000000229d233d605a1a9f060605ae366a263594d8fa2b7797358ffe4c62431b9718d155d24d80bf5af1c806f447b92fcfbab000000000000000000000000000000000bbfb66cc0c7bcf251141c540f712fe9a359d1ed36d228379a1f3791991cccb7dfe1a10d40667ca062cccd55c9e6b08d00000000000000000000000000000000150a4d7a003cb81423604c13d0c5175183ab5f459b96842939f5c4cfbb9196db4667bb4382d2d5c92b70800adf384569a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b50000000000000000000000000000000016352fb8e2751f126fd0f889f2a62a85b95c50d6bda7704112e4487dc94417218b0daa1dd6b998662af2582c44b011c90000000000000000000000000000000016bf4c60eeaca103c90643fe0969c2c261e9697ddbc02279f0d5afb5c905a984ab2396db93555cc2dd5682a1525446d00000000000000000000000000000000014be742feb1215cbdcde21e974c74e23c7bbc2cbfaaace28cf1d4f2b5a77dde2f3910aea74bc200277e6fe0475208057000000000000000000000000000000000bf98dd3e3a8b13e487d8b1a35615b0c6b0f514f9b8da7d6402586f113974c8dc9561db797a96f4f8040c1765518d175650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000362935e552dd01b5fc5a15a76faae937d7ad086b0a67e9cd3558287274106623deb85b6410bb4e64c424d44335f3b1e00000000000000000000000000000000096f23a54cf57aa3306df0a0a4f45aecb9b09bfe83878d551a59c53e18efc5a9f177cb7fdaec1648f66cdfaebb15c61d00000000000000000000000000000000135271fbe0cc0987e82f3430eefa8e3cdcc1be4a441393bb3fac0b8e8f78dc47ba2b833d9dca4277bd60befdf33275cf000000000000000000000000000000000dc1b7512fa5f9d4ea3f4229d947f43d7dc46b7770aadbd7351b6d48d525d0144183f2c84293c63c68d5262851401ae0282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57,0000000000000000000000000000000001c11610b63eeaf9e00552a230bfee290ea49bf9c93cfea1b6f684c9b5a07f341b718a0070534e0da9e6ab1239d800830000000000000000000000000000000017e8107113714ebb1743c34d83be3acde096bfb6cf140e943ecd0831ecfcd097f58d25a45005db61551a01d9da46de10000000000000000000000000000000000c2eff6c7c25885c514aadecb8f0465a0fb4385eadffa082e8d4f497b10df2395be5e7760a87bc26772dd78701146b730000000000000000000000000000000011ad4e20f5c1518c72f75d67a897f30100dbb83365ef7729c3501c6f266d6002edcab8c8bc1f449c30ec3624cda13809 +0000000000000000000000000000000010a26eda028649789d9e01232884e4e5a6b8e0b17169b9d64393e2568b09ac4d3e61a5996108655c24e76abe9e3fb7380000000000000000000000000000000015e4e36bcad524f8aac7f909fbb884e879caa735f80fe9890d7874be919ee727beb2a074984c047dac1d02b8712afa3a0000000000000000000000000000000012458946f1e853a7a45861a92d4ce707e5aebbf69edfe69190c0bb130141a10869e2a73e06785b568248d5b1f647e63e0000000000000000000000000000000004d8061f25edb5a510a2db9e1df850518156138c78ace50f4c9ce47734a0b14352f5283083a232602a070c3ce94c7bd93d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb90000000000000000000000000000000000baa40ea518227b007b9714ae6eb5a4e92883dc75e6328caa780bb2ffee7573dcd7e9ac47821ac449187569986bd2980000000000000000000000000000000009d43d61f070ae308c5c285915600dd9c17b7de63cfeee6fe33c9ba857b3c72e057bcb4d4ac2b492797e7d785997c18800000000000000000000000000000000185215a7fefb96b3ff9229cc3239c3ce5202a97e275ea9b1541d7bc0a2931d7e3b01942febb45c6e96e66e3605744afa00000000000000000000000000000000103ae58b8066dd62c46c14c593c768fda91b90e4840b5560c974ce69b86bd6d2c13f689b72cf9619e57c9dc8e3d3fb15e71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000075585f862c0e0e031efe12f31e159f2a8b89825ce80fdf65906474f0155f397fdc666292f6a7384cab790f071335c49000000000000000000000000000000000eba3d37a5cae738ab99ed9475c2c7fbb88ff54edb8490017162dbb16c8225102158a266fd4ba7570ee6d5ff6cf3f5d400000000000000000000000000000000135a0b0a38c036919f8389eb7bdc505a375fd75d513eecf0cac134645d60fb6030a437ac6a0fbbd167b7a77a927b3b0e000000000000000000000000000000001688fabd4ad751598ca036ec5ef6d7b314980dce7d8652163e89fad01b233af64defbcf352743ec478af42587f58177dbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a00000000000000000000000000000000062a74a9ba0e2e8d95fca478be8d18fc716243b1faf7365a55387fd7188021f53bbe780e973e7d16c9db236faff176cc000000000000000000000000000000000f949be3fcf9b38995624570fcc9e7df9964d038eca189336ec39d9e0bd05148ff7df0b48436a2cf6e249e52248ee8a40000000000000000000000000000000007472e7c366419a0cab844522c46356acdf6a12cffae941fae3d3b78e7a83f0446c945bdf7b247abccaeeaafec49026c0000000000000000000000000000000006a564e6860b97feff368fc9a349282112e591a7a6987fd10a2d4de8ae4384ce229b9db9a93445f727eeec55a6fe5a9def0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab7000000000000000000000000000000000c073d2885eb125d3e7db48127178bea2c5bb0f09eec7081f15bc6fe6cba156914fe1b1fea6cf14a21a328d831523ec300000000000000000000000000000000010d93564b2facde13d29dac198c5f5fa314a0398f30c6fb7fc9575bc83d4e97edcc1c1d34f78728729442777718f54600000000000000000000000000000000136a4ffaacf0b4a607c677ed343c1ad41a1eca49c7c48fe73ab2f74084a07cff18f07f54a7f8ea1bfb7fa3667863bdc8000000000000000000000000000000000fb0c007a907ecdff7bfe2242097caf0c5001124d112689a74544fe4fd85be9771632e7267a1cc7e9f66d7e4bb4c954c3ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc000000000000000000000000000000000032bb9f20fdb19f578fac3008396f5dd0a70860f77f8ae7771fc6253569d47b72751cd56bd373dbc5eadf55b99578861000000000000000000000000000000000c4a4bfb5ca6f9c1bd69d7377c6da405afc3128338dfddd9aea19aec5e1e0f547e3febd28445af5e27469c87c4ac15280000000000000000000000000000000003b551547af253d07625028db4b9a8da2a857bc925620c5d561bbcd3e063eb460d9407cd4d4813800551e5d0d23a2ea40000000000000000000000000000000006d5c69a251e9a042c66bd4ee92d4f3cd4e79704b1b215c15b319e09cae0d798eb201be24f407340dbcefcf2cb87da5ae5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a5802e388f7605bbacd0bb65ba96689e223379214fd7a92de9a313f55d66cc71ffc9ab3f9979b75edf55647ad3b6c94000000000000000000000000000000000f86f968b5c20a81f18074803e1ec55ebd73bc87451c48d5bb61604ebae46538dcc9d21cce062abc07b4b9e89c85bf60000000000000000000000000000000000f9fceddfa8fb5bd76fb7c8986372c32ab9fae3c26e9fedae892bb55178fa2f3432e6eab5043496dcebef46b20bf5824000000000000000000000000000000000dcf7a118881aea4e6a0e4e305910d4e4a5f3d0a8800f52659ac26f122bd63c8aa2c5583f1121275adc9af1800a007fc97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb59260000000000000000000000000000000003ee0ba2b1de438abe66769124b97a951ce18aedc8d9ed005628aeebd90efd316e7a3c60cb5a103d6f72e7a40ed8f44000000000000000000000000000000000119597c99a7a16d8d35937ea15539089741363153ef898d6bb177d9a9b6c5bb4b79728155eacc5d82571f398ac6c32a200000000000000000000000000000000116184ac845a28c4f96641ec19a07e1f8326bd45e2106148f40277ae6fcf200d64e326915cf5c927222def8deccd4ff8000000000000000000000000000000000f890258e70b973c0d69492b2e7d10ccb3997798503c0943af4255c13b3856ca4007b18cb9d638d5d9cca71c368cdfccc54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff6735200000000000000000000000000000000171035755bd519af04efdd477d407267c5a8108bd32dd6d3f1b9555f15f37ce7598c096fb5301873809f0c000457a4a2000000000000000000000000000000000bd35595246a8337a426c50c02299f297036f710b0979c7f981c6909e835c0d9556cf64e2676baf952a787e10d604f210000000000000000000000000000000006600ff240aaa026941290f49ae8968e72293ae7c2af0df1b4ebb9373199b95fc91feedd2782ce819440286aeb2388c50000000000000000000000000000000015b2bbffac097c27944143cfb22e38ff8e50e79f2336e64c8496b0b25892834efb18a765e26f1408df1d64f4b9b78fb947ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001064bd04edf96a3c76d2ace669ff72ee5edd87d32592213cb5a6a4a482154c1723bc19c7c530d164c31626dbf758d43f00000000000000000000000000000000176ac06390e3629bdfa282bf825c0bca9bc4e0b8fd90fcf2d4ee456d5bcb3ac2882d8406d2fd59faf10c8327b1962124000000000000000000000000000000000b58fbe4e14ee0af03d9aac4131abfaaba43c7cd92d530802516cb67343b382a6d2af9399d93b43d6e05f7ec827d5ae20000000000000000000000000000000000bfd241e3180cd5ce9de831b24ca50db23685bea7e008be0c6ead11abee338618728968c25a8e5a916cef8aa516667214ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b00000000000000000000000000000000183ccf0ddeb8573923694decc02b8f02162037156a8f6523ed178c13113d094521c3d9257febcfbd8f15acfe3d5d5c27000000000000000000000000000000000cf716097aabb07979ee435cf57ae36a3034283eeec0771bea24c9a1a15ea106201af8606d3fc28ad8ffbea2cf274458000000000000000000000000000000000b962565763c4cc155b2d9ea104e754e5fb4745303240688fee7e2256fbda82dfb515a51096be5ba0b111637b1a25438000000000000000000000000000000000df04aea745b9df2df0e34153269958d3640c1596fdff3fba696801c96371420a3619c5ace9210af7e0de4f408b09a7729b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e204000000000000000000000000000000000e6be3275371e533a676f8d075bb2ab8b0216642ecde13425bce4ffa8ac51cb1b4c5c789d82387f5355c27f18da556400000000000000000000000000000000009fa3a3df5195203f967322cee54a15d1e0096922b6b881bb3bce54587fdb82931c0b87de7a9dd1a21b4389a34d161ba0000000000000000000000000000000014dd5455deaa5ea4f9b5a6241c2e8b2230fabff9e1ac08b359f029f4c7838201cb88a92a5b696ed47819e4866512fff300000000000000000000000000000000181085d630d1e24ebf79bfafa134c08c0e75626dd400ce500392adf4462028bc714ca07b28b8b8f15c9cf2934a299c3092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f0000000000000000000000000000000013b60e3be9d7d43eb42f7cc2c0a7efc81c175b696e82b034c87d1238db2798d9ad6534b86992653d86755b4f00cf989d0000000000000000000000000000000009dbb325624e698c76b9d697e4f7f03e502ae1cd43b49a0957fc067858e20e8c7ede3577f336eeccee58cad53eb727560000000000000000000000000000000007f2f50be2c6fbc500ea347cd14ca195af08b835814ca515d14dd2f6078eb6def2b9475c2ce370780acf394065032d0400000000000000000000000000000000109803d612b9e27be5725f162d061b9428f363493c17eb39c097032039387d96d0939a06466470ab62ff507ff762fba78f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000008a9fcb462412c1065dc7c3623ba5a980e6f86cc813b5d8eca6b1b8a302ee4176cebc233411f2c9ff171332c66a0d46e00000000000000000000000000000000058d2e7ee02bbd4896b5bcaac0f2b09c16d1664209710945c1f7f1a53e24496d7eace99488debb32afe10d7fea442cb800000000000000000000000000000000084d7600bcb68d5e375457078672fa07ba2c87c8ec5f9eb7b61a0232988b197aff052e7125b33c6657729ce8a1c668e2000000000000000000000000000000000a07c42468c7c65fcc984bbfc2f05bf452daf17d57e669ee5992ce67517e1c93b5f7f4c9434d40f3b9bbdb3446ddb982a9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000ec380d15e0efd71958978b1f9298ced4cc3322e472d03830ebbaf2a4601c8371e6bc1cad047b0e1e429ecf6fc628208000000000000000000000000000000000b278fcc53b7527545ae1340c24158ff662683919717c220e7d2838a853fcc84ce3915f105a932872ca7f64b7cf096ba000000000000000000000000000000001520798dcd146c0b39ee727e8276fd998de0157a68587c2fde56cd82a9779b6ffbf745ec151210d1e9143856f24f01d600000000000000000000000000000000175d53b992d750b34f9daa39aec918a0ebb2f539db8057eff1409492c90f79a00f14a4c53445c028bef5d6372c9f80c680b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000fb3863bc7b468f1a0ab0e4701ea392bd820ec5cc2d7d86b58949002f24c972f51f0f82400fadebef13b750884b35f9e0000000000000000000000000000000008fca1b30d4e01991811679f261d11723086753e816239c8c7ebb60ce9ac0ea207011a69cdc29e3336e8f589b71bdfde0000000000000000000000000000000010696ff9d78b48743abdc6c1f4b44b4c960aa516623a24da515206d95e65286e453a8f275d98aaa09fefea29e71b5643000000000000000000000000000000000fb4b5eb18b6f6f8ee7dc734e8bdb625a403dcac6d0cae363e5a7f3a834c8eed5f01fbc4dc752e228c41f3f9d992bbe01b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61,00000000000000000000000000000000165baa8b143e3734169986e68a848739ca05330786012de260148cfd0810ffd5659210855f19ca92566ea0d6c48086ec000000000000000000000000000000001225672112e0476418288f381165292a9aabd009b0d9e44d9f8f00469b2c56698f5f985ab6292c9dbcf73bcf610080a20000000000000000000000000000000005418cba24a43fc7edaf2fe77422a0b2e8b38a45415e13654c6176c8f7cf6bb2b80401534154cd3b23e977af589eda9e00000000000000000000000000000000067126ad59105621cb0931ab8f386570b54977563ffd69c2231c56e7961f6df2c5d7b114e0b1ea176cbfc1d657127286 +00000000000000000000000000000000139cfd67c3365c5b4422063d7901108c9f33e233bf6413ba2e5b2ad62d188cb50dbd3dac0f298aef7c1d621249d4b0c50000000000000000000000000000000012fcc0d5d09cb3d86895f76ac3d3e9fa9b2495110b0276e7a039d7d2fc2e48fee646fe331c1d8e6f019898ddb43dd09b00000000000000000000000000000000159356eb3ed0d4f146dc929aa6c77057be5ffbb064432d3fc35d346f19f6c1f8552c7079e27f3188bcf29941375e62c9000000000000000000000000000000000fbd4e9a57aaaec40ef9bce8b76b529bd2261d373f05fd69af58d1f23c089497473e44e937b2617a92942af1a99d031f10e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f66318500000000000000000000000000000000022ce2d2bee57f7567e9b52ae8e913c79e3b2dad381802ccad317b525be0b503bdfa92722eb0c21fdaa31fce2421ae300000000000000000000000000000000001177074350288dff9dd85dbee758fee1400cebc173793198a96c0be3bb810d352720e94b9bdcc6f5a8951b3a86b2a0e000000000000000000000000000000000179e21de58ff76427f5ed7c8ca3058d0e5e81e436280aecc75a3d989d1cf11d41734de22bda74cf0dff175ac789532b0000000000000000000000000000000016abe94a49f071fcd5e24b5f3a837fe3fe7c7dc53416f59d0469d71f144f71ade4569bac3aaa202a8479c794bd251645cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000018dee638031a3c9b1198cd4a4f267cdd66849e2b80e3d670897d9e058bbe772936d827eaa4e78283d42ecd25eb4b22e200000000000000000000000000000000009c04ef31cfda7c086a31341434a1698c1132fb5916d359a523b98d05d57bb38a1e2e2bb779d4762f9d4ec24fbf2564000000000000000000000000000000000a788450652e0bfae66889c66b0dab8d1972a626facb690f8e4ebfefc7e1a7b2b58f6eed02c1f10a74b140a49b6c5de50000000000000000000000000000000009e48b52f2b0548dab1c0d260144ad2e66a22e0f1781f94071b5a3a08311d11dcad6963b4339fa63bd82b4ff0dabe685290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e8000000000000000000000000000000001319607058637d4b796020cca79d62af5862b1c186f32d99c0ff53a830888f297ac4389582f9fd010534d824522e6fe3000000000000000000000000000000000608ca0b4806f17b59a805a3f9f75e7a33ac0791e05050d4eb19f2d4c845fa4e4c738c3309e24a4524b6bfe716949ab5000000000000000000000000000000000a6a6201ec077e113995acb81d4d07d0c4a085d367ed740d26c4a0c04ddf28697c1cf5e648b25148888617ba77ced5e50000000000000000000000000000000003eaff54800dfc8eb3ce647ec4ae8c1aab6a87d4853a1ea061a5e6367d8ebc94243837d4752a1933f7eee0ec1ffe68c8b71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff5700000000000000000000000000000000122822c91bfc4f761b65f4066a94c0eb1f53133a1355c019f04003e84edc5095523b2ce87ff24bb42425ce979743ce31000000000000000000000000000000001928bc315800ae9936e5b763bf29b19a9aeb71268cb47706494598e0ea057f9dbdda6733d9ea165acade87bd89b3ec12000000000000000000000000000000000a87c1ee17bcd7d348ed1a5022bbc7438bfad06172584dd8e3b51db4b3b09645290382ba991df37db0ce562c950c0e6600000000000000000000000000000000127c80da591c3ff8d300bbdbe27e0aa21b5edc1c1fd8a5da27f58a4dec3971b3c4f9631bde244a7072d9c19f1c0a46be250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000004bcd0a0321c3c7e6161cd53254353905c27d965f57c9783c3fa7cd5c55a5820116415ce45491d5d1ccef6017ea4608c0000000000000000000000000000000013a30e19c43a1f466c0c3ebb5cf1b57c44434892b18a7fde18a2a29b09a5b4d13d26cef871d689d9855a73a43d22119a00000000000000000000000000000000066d6b3c9a949049413300ec0398d605277911d7be327b1d816cf25543d1b2d7c31d912f426021e612b56ca288b462450000000000000000000000000000000008549f4dfdf018073cc4e32ac930397659ae7a59ef42ca4f864b26e4635c2b7669186a107e9e91c35f04674d2be46051ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000015900fb486bd2c066cea98e51d30424681fc3347a1cfaeeab65989d1adba104a362837bee51b8b953ebb520feb49aa6c00000000000000000000000000000000198ccab1f94fa910f755936e357a92d358e00cf406894b46adcfc301918c4fd7cf7200a1ea515343d577d920680c83640000000000000000000000000000000018d9380a8568adb92f8f9f67c315f2a837d542b32aa82d9bbf5db6dfea27260738bd0a03683a9988c6c3370563e7bb8f000000000000000000000000000000000528ad42f23c4e21a687f2303f495e962b0a90713d6ef3abbdce38ed166ffea9c132e50c5b002b2ddbbd4933e9a1aedf6ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000017eb2587aef34b03943a170d91d99aa16ceb2a36df3068663382ff4c135083c998743f9145a2fd5dd4ce3bb8b64cf3fe000000000000000000000000000000001256fb29c7482e5469d64183e3e848e5bf32f9c495cc495c3f8cd8e46f71c3f9880f875cfe429677615a6803f849952500000000000000000000000000000000146e2f329f86ddf5b0b17c37aa2905122f457c2c812782bdc15e132468af48c49b715e3080da504d59414ceb367596f100000000000000000000000000000000022a8e385972592430e76bd952a700df8d35b32deaf06c60173d0048d6ea22dad95cc62300bc1a60c6452c41b32b504a1271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000009c80b3191783d235814fc86653bf2f9a32cb7938111408087b6ab5bafc480583e7a2a32c6bee0ee4aa867ad5dbbf77a000000000000000000000000000000000a09af60eed6c47a6c2615cbfe62025530b35727b42fd812032671ca1eece6694aaae259b05906faf7fbb54362ea890900000000000000000000000000000000055c5f0818f41e5d73e8cd5f70fa77cf477cad8dca2a88b8970a3a25c8f38382268e439642518f1974c5b470cbf29699000000000000000000000000000000000834e44669043aed8ad47cccaaa7476ad830e38fc1def66aa7e8207e889ac0fa1a931eb1e90aa6e1cd694bb95056c3e63ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd000000000000000000000000000000000190f8da34caaf472ea9b0f41851f808bba402b9be4baa5d02d1bcb2f66acc3172abe78a49a653cd24dea402dfb972f670000000000000000000000000000000019931343d0e59f0f0a060bcbbeea92fc4670db510c017fd94e0650ace68c2925c627f373d8e755813c199b79c70369f20000000000000000000000000000000013ee811cbc036d2786d8ec0339627d6134b10517c8858f6c6db19a9319636459ebaa217649825ffba32a224175267de90000000000000000000000000000000011039d587f3323ea9d3c50027c427fbcbbf7e097533d8a5f7a61520f3eb548c399e401df0f51884395ad6a338c0a3500c6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000011d5791e9bc632eb63bff86aa433e6df463a84570b779c913f67e77fcfefb6af48f3df2174096a511ac35eff64e0e5f3000000000000000000000000000000000282716505907931bc93748ba1729777b959d65aec5a78c9f829ae6f2a94a022116715a8c2a653a832a62625473a0cd1000000000000000000000000000000000f694a16ce7a69f0261a0ae19478003dcb61bf93a2ff39f940fc4718a38b9f4b6ab13527c5b438d22499ba29c0b5461700000000000000000000000000000000031eab53440757e4065804896e9e811d459665598546796d67472054fa60e5da8685d8e847eae342e44730056757c6287275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c61500000000000000000000000000000000138862ee422bc0f38ce3e27ed3c1b71f71a03d61cc474d989b0cc824efc512ef173ef17bbfb2090997eb9435f4d23e0d000000000000000000000000000000000fabf1fac2ffa25d9c8cbd49b3db5dfdbee52adb947ebc1a3423c9fa2f9d3d29329b60ce0c1c739c7fc6d5a5d3b9e96400000000000000000000000000000000090d92e8763d4df49b8121a50affcecfcd632923b5fede480a3ee79128781f3f49b592d8f65d30adfc75d8a1922c41b0000000000000000000000000000000000074456b341565b13ee3862bd87b72f9d01754c7715751738c5b33ee85e3d8a6f731d7292bb485b5fb59bbf3ddf9b0d0dbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000007b85110889fed72b3654a8632625835cc041ff0a827f3e1b86c090d816d98cb3b4be66b6e573b3dc05b1998f2772f0e00000000000000000000000000000000160524507679ee021f4307e5a9fdaf01459cbb9a3fb9dc8be5599431e2a8bef38bf8a05d601580085da503dfcf57aab7000000000000000000000000000000000f98e2e7ae9cef2b1d954b7f26fa1755258112c496605c3c77408786d4b210e51c76f10870f558296993e0ddcec3d76e00000000000000000000000000000000068841825f5f5d8f622c1d43bfe090d11c6996688589c3d644ff5da47b94c0638128878d51dcf6d43637781f0ab21a68ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf0000000000000000000000000000000013ec97016dc3d6a3cf41edcc18f88f58b1b88cb2616bc2a8f96af3e7774ec1aaefe86a86135a20ab7592c874a33a8e1b000000000000000000000000000000000021dc7e4be6462d64ba6c09c2d326ca0164305dbf5ca1981f265a1e50f1a646748ce66ae07297230325937faf60709e00000000000000000000000000000000121bda2855503ef11b043301cf331a0fda6e5914e5ca657890ffba2542d908f8fb02c2c93cb4ac4fe5bb92eea757ca7b000000000000000000000000000000000386fdda56c778a7552dce451a6ade55cd24bf9eaeb837ebef898e2e868d05eb5edfe97bfa8eff8ab7cbfaca3c918910461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000763746ba87e8bb547180b0bf18699ff74f11154a06cd77a76cc9c264db7c48286fc52e3ef2d30ca914cdcc5c4ed46ad0000000000000000000000000000000018037afcabd273413eb4a712f5d1888249dc987a6fdb8befb92c02660604bd11deb33f283b37f88880cf1be2b2e71f1c0000000000000000000000000000000008ecca3d1652be4764720ef13a6ed6164a3ae89d160cc8c2c8c37bcbaa52db0fc0de84fbe2a19b93b8100556fce0fc80000000000000000000000000000000000c5727babfbc5c36c1d57b9f69c5b41823882e0196e9e0a89d5f4380c4257818d90b1fa6d782e774f2424209bf2e6b5fcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d00000000000000000000000000000000142d41630fb9db2f9630e4d5f9c13069242fbcaf1dd02f93224174567c3f944fa02b9791a409d9236d89df6ad785e8ed0000000000000000000000000000000002fb5fa0b3a7cef16e5638f217bb946085fba870836c618a7db9b4394da9144850572daccbff8208f14c8082aaf1ef6f000000000000000000000000000000000a6be9b4a6a9b96d2096eb3a95780f11be1e13bcb6e625517191822403935c52cd40481bce2e782c42b11321cff2cb7f0000000000000000000000000000000019e2d94e35d608a50b5c8b371044f6410dd6c1988ec7a677016d4b52cc3f21b82fbaa7db897f7107d81a177c31f8e52467de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda,000000000000000000000000000000000a6f3fcd812e3878cccc6967d49b104599fdaa80cb5dee7298c3fdc80477d277f2c68f1c941f6e03441eb176c222a448000000000000000000000000000000000a4007cc5586d677e7945dc8a5872b4839d5b256999166e7fe8efe4d56895f93be4659f43aaf68c6070babb6d3328168000000000000000000000000000000000cef5304a1077c8f31d72e6f1f91ef5a021d8ba64719b4527225b34e615af388d9b1391f65511eac209ff5e86244039f000000000000000000000000000000000c856e7847ea0b4a8334d124417b45a8689d5d9f113b99ebbe3af3f9aae1cefb236d751c40488a861a8f0e0326b42c4c +000000000000000000000000000000001805009a7fc3d1705936696191c163a07ea992cbb4bec66884a2d58ac3fc0e16b6e0d2292caccb3541f39b7fd6098100000000000000000000000000000000000f3bcfcb0c400d3d06184563204bdee465de167c7d17bea2e2150fe12eb9bc3285f5693b222fcd224181f8d193b7d95f00000000000000000000000000000000028d60b7fc3790aac7f6b3ec32c4be626a2c64c6348fb8a1f39e58ee56b81469e04886ed9be1388958550c02ca9a75b9000000000000000000000000000000000b60ed8052e43e99d3c10a4b97ac3197ee3cc04ad857c5cf4d8ea1df2671084d02fb683f28f5d499910351354d5e6288624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000002ddb1a9a88e3a0697540cb008bceb075e87e2331f6e9b68f8ffec48752d93cfda5fee121155ad2a142c0ec42808fbc200000000000000000000000000000000144b694018840835fa9c50fdf62c2e32261a8350d2ef074dcf7d016af982316a0c6f9e5d15d29d3a54d8d25aac5534940000000000000000000000000000000010a3765089ada75e9eb61328756ab9ca7b8362cf86cc82af3cf43f390a0745954f28da72a6ea4eb904a040596795639100000000000000000000000000000000056b51dbefab453012b35fb6e06af06ee92e4e84e92a9967b379af760fdca4a3f10f938684a646fd70a2188721c92e98edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c23500000000000000000000000000000000169effb324d60b71dc7ba975e3d5f18700b34cb9017f482f64be37c4df01fb66ee9eb5870e43649225c9a88a0d499b890000000000000000000000000000000016c7ad9c5f7b65a9423f642d87621a5192d7548e1099d774a99a34dd4ec9623aa1168b9adab092b3cf450f369bcb627600000000000000000000000000000000123b35bbcd791ce0d00148cdb3d35ba39054a7126ca5ad3351fef1437461379ef639896b271276a9561b46e270f7501400000000000000000000000000000000161fca2deb729fc55f1102fb75ff466319f18510fc66d6cf95a8256118fca618682f00318b0a5297be873a2f7af1915afeb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b00000000000000000000000000000000073ad8c2f713288313185c3b2455ade93d58e70d5df6b8dfaac8eccd990fca6843778fe42cc8aa6f34ee44aefb49397100000000000000000000000000000000012eb9cf288a366adc58d40c9ea5f2cb5dcc5b04108e3822266ff20eed71f56bd74f1a2727f20d55917adf20b6c4d6a1000000000000000000000000000000001463db177fe5c0dcb899797f89da963731dd4e9e8b2eb77b465b98415dc95f6d5569df51bd2b08a13838f4cca4b62fcc0000000000000000000000000000000009c0bbadad98361209f36eb23a9eeff98f6eafc7d5327fddb6bf43898a2be704520a005b84c5b45c6a68bb7c98d65d6dcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000013c63973ce6549ca3dfe8ea8e3bcd6b0bd88f7c73730834d9ffe2076cd4345090d0364d161ae8998af1048d102f22e5d00000000000000000000000000000000060cd24eea4177c9a5c37038d4cb62aeb709218fa8e64b9084e002f53a0c4c411825812c20df282345bc4a6aabfff6a100000000000000000000000000000000106ea864dd52933be02c1a79cbaf6dc81ae9a2d619bb368c4abc36226104f3b74fadfab906e36d4852a6412315223bdd00000000000000000000000000000000192e45153e4942c88bcce76098fa51782a81b53abddb4c07bd79a2391be68858e2d278969b9fe75bc652d02fe4db1a130b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000101ca1625e9d4a51e08f5eb81387b361f6445eb307d9bc92acd29d62735d4e5078b1a9b36b94e4ea0a314703a85ac4cd000000000000000000000000000000000f134c460c6d931396a0aa397558975ee973e642f1c4a32a3d397051fe250daf4215ff5ac4b2863d570c87f0e32c8cb800000000000000000000000000000000008eeb127a38104351298ad77481c32bf51bc5d3910b03da0cc34062dd2a8766adba6891cb9fc579672276666e1242730000000000000000000000000000000010c896ecd4bdc1ce010da81a51dac96409079853635e57e5c3a5733956a5f5a9c3ea6838849e286ce0405dd54d7e32d6730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000be489a1c71246adaa1c1dd6d2ddfae9523fd1d58d00d4f189f56d08632dccc694e63b371db6922a7f3faa05afbf487500000000000000000000000000000000174212b6840a797f0fe9e209b41f55aa5dbf169a2e2ecf05de48c44e608f6cd6d98ff5269e5412defb431caadc8a09c3000000000000000000000000000000000f4501715c0c511703f6236caa82479b3368de430f2c2d95b39193537be0b990fec1ed8e4d94634ee6233cfa359b043d000000000000000000000000000000000f3b4712f95005004d99fd739affc532d2c4c45970316c1a43f76fa9b57f6676c709e8791c276237b92750f5bdc94492ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000003abd36736fec3e8b89863670666365b169d8510090a89007c7ff3a82fc62ed371544013a1444fedc4358e92ceec62470000000000000000000000000000000008229855468fc63f4024938cd6f41c6e6a5653319cb83f38ab7efb9e9d281166261e7c854bfc08f55a0a9ca47e54dd42000000000000000000000000000000000463ccacb341fc5874f6ba2d44efb5cd24e9409b2ce7f43e9d39466288dc833a45988261f45d34332f416a68c5d10ce80000000000000000000000000000000002baa086177394203a04ce1b46415983399e60986531967b690b1a13cf8ae039b56f0a00bf9aff357d51ac57f8fac8b282b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000000b25a203268100df0510e4155c594a144dbdefbb0ac95e02bb4b3799aee4e738ef4c52f03c6937cdfa7275c28f130778000000000000000000000000000000000c432347a2534e86e90ca346a7b8b40f45075727847fa3ae2f2e297baa14aca88ac6e08342f0d248a92e2c272841fddf00000000000000000000000000000000057ec8099e1e30329762ccf0641b45e1a226f7b66b80644fd551d6fb1f2136afb8e8ab5c6905ffc7c24e67d7f21863e4000000000000000000000000000000000a9e472aa993bea05961affd6782efe8f50d746928efb8fbd328fb50a254db861c90db8df7faa7da8266ceb47fa1a13a2396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc00000000000000000000000000000000025484652f18e2b32e2bbe79916c8bad42902db5528fc45993e04daeca008f3c2ff38fe4b48c292f70a7dc57654233400000000000000000000000000000000008e403f472b60a6046fd190544a1d6b249dc97cbd8641c62613f4de0e0fa9f5456d843ece4ac2b9f4ffa2c0278e61829000000000000000000000000000000000824e0b9b03198597fa54252b3df9690df678e9c6d82301848939dc55ab25a7751bcc2b99786cd31960ee7030bf68ac80000000000000000000000000000000018d1d8c7f2b20f0ba66db616322e48ac8f1d6f4205f228ee8ee6cd13d1f64be9af338c11f511859baabea3e15d165fc09a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000002845c4255819ec6e97abddf4c9db7d91658dd1d55328ab0565144b377e20ca0743d93fddf68acc985ceb7f7431e30b0000000000000000000000000000000001577a5691f2425e65ffd59071c2bb167ad05a8fe23c11c7f7464764442ebb2f7a75a8d02594d4426c1ff022f7a6e19360000000000000000000000000000000012c6ffefcd3964362f1373348404d04d1849e98ffbef7b5ed5704d74b9550869e30a4df26e74b5304b85c7503f7487f1000000000000000000000000000000000faf3dc42113f27ac27aae36725221d04fb1ab46b59e16277be0758b8fad706fa237c0c7627771d8e8d3ad610f63619bf20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb00000000000000000000000000000000044de166200ec06bcb88720e57b84cd8f9534d1fe303a26aca08cc35104ffd7e81a6473c08b28037118dd8a61d090e910000000000000000000000000000000000f4325ebaafc67945de2418c81f5da92da4e67866ab5965eff0f392cc527fc34ba4e7e16b91c26aa370b27eb6a07f6b000000000000000000000000000000000e1d77ccc1c196cf1cdf0dabbee4829d56e937372e9f5613e261ca07e19b3fcf10f7a45c490b98b5a64b955eab5c4f2a0000000000000000000000000000000004ba2e81f901b0da1ead004c76d43278d372456c0c0a8c6752597823d44994177734ed3f355aaa22f325ea36b7c9eba1f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000000c115e270ffd6f2cb9bbb2a62e04c3bf7be9d7db783d292bed272c297773b39e9e51c75e5c79a6606ff7d0bb9ddd040a000000000000000000000000000000000a57b637126b16b23bdaa6a7cf2346f33778cebdc0c9943eb2985ba5c4114674cd596ecdb6959791139c36c22148ab8300000000000000000000000000000000177c7ed16c29d99d3d98c6facca9cb5ffe72e6aa63959dbb51d9382f0fa49b02a1652a398eb223e093516ebf134448c4000000000000000000000000000000000d6bd518678828f582fbb3b1bef725e66f442c4d3e6325fa571e13db492300d03c0188399a2ef9d5687a76e647873c0f52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e00000000000000000000000000000000085d4f90336987f99d250067c2331e7de8f09a80d71fef0570ecfd99e409c1f405058bd3461c9f8ac5ccda406db89bca0000000000000000000000000000000015f310660ca6a0c06b458d0b840a5c1c476d5175d9ff6dce6334466d363d319939572a2b00662247be1ed0f4e6676f8b0000000000000000000000000000000011e9352c0f81bd3857806db678bceb2150848f2224ddfc43fb0c733f0689ab4fffde50d5ce04d54055d27d7702e5d2d40000000000000000000000000000000005d835d04dcf4199130d6a16e86cb97f4ccff58c496594b83524dcd88f5570212f06b744379288f2a737c7a82e897cedd32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f000000000000000000000000000000000f7c4e621c37bd3068a972b9d4211abf9026e438ac7f8cb341516f7e6aa4d8bfb3536389e9155029ce9e8d5d376eec1c0000000000000000000000000000000012a46cab2624797513f2acaefa26fb22c4bf29188881690c350593fd1949cbc243c9d1d7d27d9d76aaccd347359a45660000000000000000000000000000000002dc383d4f9b75907f74bace1769bb5bb1b27a597c9548310f2b5f90098596fcce6b5fe0c72bc8be9037fbf31050d74e000000000000000000000000000000001900deff7ddc62ac302c941e1d2a28a4bd2351edd7700042ea4c4a48145ef91688666d8d7de503913ea259f0b58809f21f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad26940000000000000000000000000000000018ac8048d58f7b1a9407d3101824e3640eb20633f8ffdcc97d43d1b25329a2a1e91added42801c03635ec904e627eb690000000000000000000000000000000000b499fbdbe2ed41dfd6c454796e1ba57021f355a4de8f60964c78dc685e2ffe9c90f5a1f6c9677514ae4a9c95c8d6450000000000000000000000000000000009d10e5e2bb69ea6fd820778f75a2a60627802a49128c3f999d8c1cc2ba56ed18acef354a2e06fbbdfa7e7a4ade7529a00000000000000000000000000000000082839d66a18763656c2ef7196a1d83bd162e1f109b54c5a6095cc7c436e8a4888c4001696958270f54f61b81b00b32d83052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692,000000000000000000000000000000001463ac5e269d286961036db48ae33fb868a28b0dd828c3a66592ff9dc115303bdf3ab78a8e1f5df68ed1f3b4c6c3f2440000000000000000000000000000000012c64ca0ac10ab616fc733f75fe6181814e9c204f9e4eb79487ba49e3a9746b9b7916a1d768f2ec573a4c4e226365f48000000000000000000000000000000000a06b5b745dd92adbe1f4cf30c79ce0c48428b3e3b05af1585c4ca12eb2e763ffff46b55a060913e1f77fc9b0b085c9f0000000000000000000000000000000006271931ce9c8b9cabdc932297f3c87128a5af25a9f77e71ea4e588f1e88686638e89a8e212c92f6472692be2e05fa5e +00000000000000000000000000000000001bf5b74c1ac89d4bab4663943e19128619731e315d2d7b39675f7c43493b338020190a72cc7a6edf0b8838886a7fe6000000000000000000000000000000000713f413bab7919cd57c2de3349394121d6bace3c10df0e41a0ab895433d225b05cdb1587deb93ae6e56ec26a29c39f4000000000000000000000000000000000dfb11c9c0bab7e4d1ee39941d5f6b932ab473567be2329c94bb0b146c46fc1c2cda25dbef8ff9b0066bd4ca3b6da67a0000000000000000000000000000000014e399169243bd619be7f2120b2cae5d19b2f04185aebc7d948007c4d3345a9f45249273b6290c2e86448648868ac552c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c7052000000000000000000000000000000000a67bca1f8a0b386b2a67158e80262f025b225535a294394584118f9a701e31e91b2c7eb8fc7e28538966b967c139dc400000000000000000000000000000000185e8aaeec9b9abb9f0d6f34e2480e9abc30208eb1c6e023d4986d544b356a387c323c9edb5c52f5a2f0bd59cca7df98000000000000000000000000000000001877ce1ca6e8b30df86de688d950755f2708fd6f933c07ae45fad1b3e43337f1a8454ca5d2a80940e8fee98fffe953a700000000000000000000000000000000117a0ac9d27292f967ff5bff2ebed5d2ddd9f453d6aeadd9106eb52b53447974561b621fdc1d973c055f1cdf824c367bccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c00000000000000000000000000000000009fc4e9e816ff495dbfd4f745106fc90c023d95bc64b809801d02dc7cead905177ede5016f537243660e4b7f54a02ea200000000000000000000000000000000180aceb6e9851a11a1e34502897299e7db3e09f4970337612634fd9848d1de2bb3de8ede690ca051a75add5810ff777600000000000000000000000000000000199f3c43d429fe8f73e20f81ea00c4e78294eaaa29fd67563664381db3cee2186b387b880089cf96fb99c2e22c95449d00000000000000000000000000000000040b20ec4e685f104be188d0f15a79f27cf34dd01f813275f6019a9ffac56e234b6c967c80745294d9fa46e0083cdd907ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c3963100000000000000000000000000000000189fba635109ca215bf3a09c3e44ad65f7eaf653e0929aed39042e3b9c8b1132c5fe7cfafddfdd0646514aa1f9e7e1c0000000000000000000000000000000000c28f598c80ac262ec7a0e0d1c867e01ef26f182c5df9ea7f88fdf8bcf3a5d2f06128526b1ce72cead8ab4286a0b8d030000000000000000000000000000000008051be3328df43b79dc9040ef0a0263d474acc0edc023f300cdf7c13088d1bb21b5f37ed81b38dcf8718bf6441605f8000000000000000000000000000000000d2d474723c6c246dc59e683be147b1a6bd6e7d3cf12aff7b636802a99954e7a13c9ea429b19833a985ca5649b1a998f6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf01840397800000000000000000000000000000000058bf424fd68aac77c42a046f78a55729e6b5b3fcaf436d0d98354b426a95904b55cdffdd9a8892c9f56f170ca8811a600000000000000000000000000000000142c1ded08928fd155b89bcfaf9c8194f4569b4cdeb3bc7286f4dd79e822f5db497768220533b71be8c71d121e557020000000000000000000000000000000000a9c753686534bfcc295eba0a617f86d7f9e78d3fe6d52f26cede97a5b1f107210a757a2d89361645856b7b20e89185a000000000000000000000000000000000f745541841cc4b5352f659c2b7cfa8d51b07f91b0cb8c787b4492bb4b94ea27117695416e2806e57c38d7e565b9eac67fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f0000000000000000000000000000000004445d4464b51d6b12f164a49ee3b610f11738d60cfa6e02f8c33b168d9d5db90e6cc558cd12c56069571567d91183a30000000000000000000000000000000009e4b96c2b533a16803a36f8d1f179313b7adbe6c4b90716855474ffb2fbe087df3fc0b4ef14cda7d958efc5c92574ac00000000000000000000000000000000104dff7c859eec61a0ff8e0d831bf9667226d5bdbe298400b4f9e3159a64b1bbc7cb9f4ff9604e3ced40bb0de0455ce300000000000000000000000000000000134bc2461459ed6f0d96aca02b62e3110c2009e1ba7d3258656e9cf97c2a1685faf1f61733ce6ac3af7ef4d73d0b43b1dd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b00000000000000000000000000000000104e5709f8edd71f50eac1770ff1c2b21f5ee8cf5a310fd1201109d1b73cab69913bcfa2d27a8ba16d974e9841586ebd0000000000000000000000000000000003a4bedc6277c61825f6ea1f438c058a1afd494c384689a8479195646888eecc7953b8b8aec849fb5f19a20071261336000000000000000000000000000000000856ee8eafb9b3d25fde7e38da4acec624d1444337b87b0b1a660bf497ff37929b1ef9aed8e1fb0ffc6cacd8f0d1a1a00000000000000000000000000000000011b52192c88264df56de3d7b14372443e25183bb816ea1c0346f15a1f324527ef8531e27aac3112e2a497a0eff0d5485b4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000a696c83010719161b6624aa7756e6e84980518416554ac045a93b63c2561a68ca2ff2fd5b6d2d667822ae4e3b3a2ba2000000000000000000000000000000000fb8fdab4f177b0dee52bb5ba615b1d548130deb87b14d05d427984ec148a7a94efc4674804b3660d0f7aae2b49f7b1e0000000000000000000000000000000004914c0359c8e23a7e431e517cb83e5735cb2876e8b53ad45abf1e9eda06e736378ce03ff75002374d47f1bd45b08e8900000000000000000000000000000000139abe340c2d773cc45cfc75c47ff31b2dcdce27ada3e6d6c0823f37e4e693ca30342fe41eb96dde464d14668eb72c5e457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd100000000000000000000000000000000180345fc01e3fa349c45b1a7fdccde5f9ee70d7d65510e8b4bce654f2541fae7641ad86f9bbc1f02e93e94422433f8b40000000000000000000000000000000006cfe7026cd423be189c5ade8de197aecbc9aefd4cdbbd2aeacda816247ad59ae06a5c49b0e29bf1140f400d46845191000000000000000000000000000000000cc4f240a317ae9ce75b44fae87c92fe9b6de10e1191cdebdcc37ac200957683849d8a957216676db1af51fa0a2a1136000000000000000000000000000000000ba84d595661e5d9bdf9d268a3cc575fbb6b0d469b58b3e43f80694c78f4e9e501c4a4f9c42ee4518ed7189a1c36ca0c19f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000fdf7e2372b01b5d926a18ddd06b4573248c02d7debf944312dc06f76ba08a7be460c451d296b71e9e81cf0956b974b80000000000000000000000000000000018326d0e1bfb4a62ab6f772b47ed7188035a62141e6b2eccf53a299028902a172771e8e46c0b1ac4833ab12045922b3600000000000000000000000000000000072107574145c6afdfc7d618f2dba2b8bb01d92007dafd476e4ca62e6053e5e9f2e34243ec2dd16ffdbe3488b925a0f000000000000000000000000000000000070e8491a835ae96087013b0f8da267a7ca5b0a600d71b8c76fee35f41d8b5c1ad82c5170b0e8d1cacfc7b7b13938e96f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000018f4da37ff63f66d68c875def8c758d9a5adcdc408f0c12b3a60ee4a285e6702b1d5b9326c61f443dc71ae83c7bd21e80000000000000000000000000000000013a665e430141cff62c25577798473a645d20321490bae7689de6ea223a434c7d3b16ad004b24a82e2c62879b2408cf90000000000000000000000000000000011b0108562f53bd47d9f8ada54166854bf758ef3769ca1c3b7b006fec8707107fef0b6c7e59feb727646b74c27ec699600000000000000000000000000000000028799b52107d8965066e2f629b30c0edb490a0f4d0b6cdfff89a9f7763afbe6217bd42c2059042397b6c0443465fdc050aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000c42cb42e389f32926ef09584516249ae332641b573ed29bc0884feda08d35c1bdc6c3d4a69fa15105de95010c6cc24600000000000000000000000000000000006c57fbf93c7959c562e0f3ef59966c1640c706fd18a6b539dfd711b0ad79643642038954bc866d42d1c04be375b95a00000000000000000000000000000000039ca3ad23b71693e02af36a4abe6ccd0dd4f4aa709f74d900b9fd015a2eaed55bdc2bc0749c995783a7615971e8a1f50000000000000000000000000000000009a08596b29da34466c8a7f46b805f1b6f2e48bbba614d728562981d3d4884de9a3c1980d398eadcf69e90c851d48526d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad43400000000000000000000000000000000123dde5bb9b7ca11da9e08a9489cf07d147492be8041a5ad0b70715147e21d6017a58af23c47d77885a7830cfbbe5e0d0000000000000000000000000000000001527cec3c393d03e74ee8a7b1d6a8b6398945cd284b59a93fade9839863f0af591c287e89b3b45e6048f2f9b518208e0000000000000000000000000000000017ac3a2d9458bbd5f38d584b0fe4b35f3a452e22161564a7582465d2068b3ba4dc5e1e24a996596b1fb553d641996a4e000000000000000000000000000000000ee5ed5610a78dee181750e35a8ab91c001446f04124930c2ed85de74c6167009af45a6cbc3c59c4915334d7853ee12f85c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae0000000000000000000000000000000014d60c1d436e4486f35ec85bf2655ba6b752a36c86fd9088c0ce46363e75abd636052f876986fa0f4a59152998c0e4a800000000000000000000000000000000083328e38373f1de1049deaba78f568db818b1dc38d981ae92b968134d369ccc399bc3bd55c841755beb484cbbd60f4b000000000000000000000000000000001788850a5508d81df9af1f087356bf8e63b3c8a4e209403c4de7b3adda07684a08f9de6f1f8fd8dd4b2bb9b75be329cf000000000000000000000000000000001506a37d222173f0098f56b7c443e04ffe08b376e1563344e7bf22b1c9df0a1292f70ba51cbe554843fb93a7f535a4aabacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000d5f153952defdea9309269bc996a7714deab12e7644f8f8344140fe53034de538aae6c3af7b06687684edcd2c5dd19e0000000000000000000000000000000002da67345153c87ca65012b8703acbe777900953abaedca4770fd893275948d150ca3d6694d58bbbc9e62904448a8d2c0000000000000000000000000000000006e8c95d22f01fd9d56178d754f0892f46166282a27e6b02826478cd39119636e811c03fd835c714a59bd2f7da5ce5e1000000000000000000000000000000000b5ab6233d8dff50648d89cd65793640c06ea784d00aff329e882ae04fb466506cce3fb6c381b4eacef8b5305953f7b6473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae000000000000000000000000000000000595edc440a5c94506a79f3b3fee818256d7c4185be40c1953b46765b2f925ed16a476b07a267570c727592dfc4a0d8d00000000000000000000000000000000079ad05473fca57f26fd068ed659e4aa4919847dd96e683e7d4b3a731cc9ae0562a693abeea4fd550e644b43b553118500000000000000000000000000000000176a9751dbfe727a442797551254cf904862c4d590892e019a54b72f6a5a124d268777b82e19d557690ccfb81cbe949d00000000000000000000000000000000164ab74c150cd151b70fdd7d63d0404214fc9cdafba3bc642aa798b1c301c287ff6d05ee7b3a3ce997072b8189d54aa62774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a,0000000000000000000000000000000017d73e29f1d555a10272043ac0900e80883c185ff7d087ee7f5a3b762213e658a42d1b4fdd435d1acb9d5587fa7e8243000000000000000000000000000000000ddc440795d0e4308577fe8439d43418641538711972c9744dfc8a4c206c193aa17958404bc387c7c2fa30bc678937f7000000000000000000000000000000000d7e43c0f99adcb02db99974e7615b4ca0de72117792ea515bb04c4bc8680a3fdb0afcf6a3bdfe16bf54c1d7336aa185000000000000000000000000000000000bcec1d7fc9f2210be80e90631810987801fdf60890ce197db041b6a62682fd7e181c6110956c5f5e9c196049e39100f +0000000000000000000000000000000015425cbb7075a97dfa9409d7b014127396056dee6d4bb63ea285309fd91280fb691f9cb9572b544b332324f6cb3b1276000000000000000000000000000000000c5b9634e6748d5819396051322d9b7e0377554613a7fd8dc0c71cfb7886dc0ac29add7265af84087a9df5ae3799ae30000000000000000000000000000000000534226ad7324ed5600b5438b659c7b1e96f27ee1d77163f2d3073418f7ded5c613ca4b1a686764ecc43ce3388e0c32600000000000000000000000000000000198267e2bd474dc0415f47f5c87a11fe0945a91cc0bfc37d504ac53f9b9b0d087cd5dbc9b03972be03d4b3f9d2123945d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f02667214000000000000000000000000000000000389a084d95445af6e0afaef21d3676794e45986b9520035111ccdbac4ddc1b23974a686a900616f878f3a06eec90db500000000000000000000000000000000064c75d1129753b5f399c1a5166a0f6a8f427d65ec2fd84d0c7339218e0a396681797bab68b33653ffb9820a6005fa7500000000000000000000000000000000147e199e8c08b9af38cb457b623d0fff32242b11e695f2adc0f136c5596db313b03c2466fb58e37c94704152e5c8f9dd000000000000000000000000000000000e8fe5436baf3470a19891b85d15486d1269e1b13098d837b0a510e71b0e6260700ea85f0bc6476217cc73615370cf003e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000a39a2032858a57ccbfb940741f4ae21b318a56d5567cc0088ed52dddf1e0d5de60bd2da9b675212a9a28ec17fca7c0600000000000000000000000000000000039e2a4bb1b417f8a94b02cad60a3e1c4c4bc5a86a23def7cfaecbfd97d89a5104e0cd13870c9fbd010dfec3ad9b1df9000000000000000000000000000000000bc29c5623f9f18ec2af5bc651a65d89554705a349923ee15a9bfb82c114246b404a1dc1c24d65c8749e7c9cf62d963a0000000000000000000000000000000001496d76f7b8583a64c1627151589af876a2f5e7677611ea15f14606538f6052c56e9fc3ed145c313acea69a51547fb6145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f0000000000000000000000000000000015f83b2f998691e504aa740b4db38f5b0236ece3bc1ca933b79999d55b737bfec51e590c2127d57625a9b7c2960c06280000000000000000000000000000000001b7b117f5d722e320b7e90307ac1423aec5e30c29602d314bac9e5272ad3990d31999bf3f516ac78b2be0e16c0375d8000000000000000000000000000000000fa7992cd7fb679eb5f9f9a9febe9c3cf41a717c8f6fffbab5748572098407174f09457e13468165f1c7275d52f6c84b000000000000000000000000000000000737e95f62aacd12f8aebc288c5cfe052f34c4d16e7b44df4497d9a713b77485fb0efc09aef11c7b86eec4d0cfd9b03ecf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed000000000000000000000000000000000004d48d72ad4e77954ec6a5a62299f0472bc52b556cf3857019f8efdd694758f13029f9d6832ed672cc210f32033da8d0000000000000000000000000000000009b2394755d0319741d131b012ba0ece7e2044def20ae73fe73bcc276af9d807ad75be79202963f9a5c512a6ca53197800000000000000000000000000000000128f856fc4790d9fa68cd2a3c152d675453dd81dd64f0ab084c6dabce456f78c2bab0e7f315439b34f86e8fa61a33ffd00000000000000000000000000000000173dbb908ed617ffffb6aeb212cfe6c03f7ee51c84134fde67de2ad9561a897e28a0efa66257ae0c21ebcee3fe4fa68cd9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000bb7b84476d4b17f4ada0b6f50d34dfaecd611356862895c8d2fee6707c4aedbf565560d4207e43c179c5cd33cbb739000000000000000000000000000000000112d8b10c775218d318090dfcef55a903953f7466c50417125ec0b2c20a24fb50bd172331c0377d4f47aec99bd87a3fc000000000000000000000000000000000cf4e4b3c600053f45f350c8860e47621f50f3849872a91ab115f71a2b04657991217e2f0844b296d3a6bc33ee66e6a80000000000000000000000000000000008f625da164bc9d96be3e78df63bd1633a2951dbea0b98e359c6317abe6ac5799c4bb00bbc2c5d02048539e753019a6241733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced1575867000000000000000000000000000000000028db057ab9421eefd1fd481c91153b5c1ceb0f2dacb0097298cac986f036572c6ab0c8709325b3bc25bd494bb46c55400000000000000000000000000000000024be09301c9be4f726fbf7796e8336c50897e8534614c25f65c37bcfc6e724d530c2782bf483668fd08e91ad09484af00000000000000000000000000000000037bfdaa11660111ce0a9c3e18b5da74c004cb44882b1aea4173e18d3a17f04fefa3b319afaf4af9dbf3d4b9ddb2c3a00000000000000000000000000000000008f2138bf621237a286229fe762968a224358b030f6c20db58043c13727b516097b42d47781bd0f0df2b155197ca3946b21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000b4e2b058d6e77cf95be093375233e5c9c8ee0cb2a3aa93172c08faea111df81b9721a506180b7b45bdde4b58b0b7368000000000000000000000000000000000f7025cc33424a7c11eef47baef888535d938d50c0f40eb83ae86791834770e5dd95b30aebdd2c13eda3447d5730ce3b00000000000000000000000000000000088270ef05480ef8aac5c284358d8e06c3482c26279734b8513000019924cefeb396ae79f5d9bd863bdd9b22e3ac3c54000000000000000000000000000000000df75afafb138fb06bfd905c87035bc5d18c45a29267c3965131083d7e0112e10556d7693d424172a53e8d3120f0cf2aeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000122fdd3c83c01c7cbe71f54d783181860e7dcf8406e3966e910f4d0ccddae3a245d6b1f94b1182d1917fd63960cd75d400000000000000000000000000000000043592e5797cc1409d6d42dacad628448799b24320acbda83f6ea9d232968efd021058f540e3bd73a7f95761efbb5fc400000000000000000000000000000000025b5a8577ec1064b5c557415a50e84c2302df97eb65860f979e5b1e261f47c0f305461681beb07e521cf03f0e21fd030000000000000000000000000000000017e86f3ffe72bcb71d46661a1537918d52e886e362d78ed756140a6b5083a4eebb5280b9eeb8a25251dec43a5cf509b13d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000122f26b4561d1f79a70bd0e401f25d50891c0fa0320579ef21aeed7c191fe1c75403a09260c3872cf74b798eb1587ebe00000000000000000000000000000000039a261d9f48b9eab6e89046f333ac328cea287993166057e9b99fa8a7d7eb3e7c34ecbb353b7427b235084f47f45d1100000000000000000000000000000000015d5e297317684bd0169c795d9dcd209452d024ef9a450c41beb0f6c7e6dc5fa0f3ae24c7cf2d7eef97bdc51788188d000000000000000000000000000000001487564f0e9d3e0d2d30ec9930a00f10093e29f2f195344f567960be323ca21231efd8528108dbee4d5ae4de3930ddedf6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda4000000000000000000000000000000001965ac3a520c1ac39b86832ecbe226ae0474b76659076ccbb550a0daf41c40d424ceda084dd991f22cc53779085828430000000000000000000000000000000002e970a4248823049bb4339d21583fdce9540ec103d6e9530b89e39ea875b1c333f7f5f859be39baad34b374055baa770000000000000000000000000000000003460eafb3e54ec03fd5cc1d460e1359b97f5543e6231d61614c1225ab7545fae079ac8e65668b83d022031a7a54746b000000000000000000000000000000000321394863e7c70df3934d874613b7c9d6c331e59a599be593c82edb7a26eff9bee8e4befbf122240d2deb2d527bd38c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e10870000000000000000000000000000000001a5eebe200ec041476457f8585cb4ccdda936cca4977d7701c44e0d4fc5d9c206682a23348013a055117028c16914400000000000000000000000000000000003519bd1dea70245e521988336eb41870599a877380c0a9eb19301f9b2caf963eb559070e23eaeefa4de0173bb1fbd8a00000000000000000000000000000000125707f5a8e26b28968dab97ef4654c315b0a118c20935e38a5a526d9ac0a0e18355d8c9f3f58c082de98691957e2d5e0000000000000000000000000000000010b58dd683f73a16d8bd5557b35b7003a761bdf7d90ef576de8acd420bc74f5219fe7f9d35667feeb3ddf1d568b56bf1f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f00000000000000000000000000000000018267d8b83ca59d4efce7ee3d73f7b984f09556ea4fa5cff5997a1eeeaeb8bdc9185176d77ad0f4d86f2e429f4015350000000000000000000000000000000014114344d6b7c976cdaf2418d7f72c120c2fddcc65c3ead067482e7073e2a3a239af19f862ad247e3181b13f5236d1040000000000000000000000000000000015db961a093b248e83deea0ceeebfc3dd57c7cf8b48cd627c5c566a4f9bea30ff0ef9cab9287a0f520a72b02d9092a0c0000000000000000000000000000000015159439fbfb91d1e24af611563aee3eb498fde666a1014a9f645037995d72dca0ed5569da7ecd084208b7c228e8a2b2ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a313000000000000000000000000000000000e1780b32a7b17464cf514efc4bdb02283af396ffcf6d1ae023e07fae02becdcc3c467f89f8edc9173a71aad27b200da000000000000000000000000000000000c3e7fd95dd823338bdf3d82fd46c265a3f794d4065d83873b1aca66da5f80c5962c9dcf537fc315d024d8cab7bed89d000000000000000000000000000000000e4eb722080e24f54fac7eed4b94e7b1eedb081c3edd7aaf5433d00829929d8bdef940aedbdd7dfb0376b3ad5544d9cf00000000000000000000000000000000158c1ff057f7ffe6492097e339cc4ce56bbefd39658ad55e08d5407619d1cbea7c83b977a1583ee48897a5e9c0d9ce3e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000138e8bc8cfaecba9fd1322a3c1682c9fc1286d78e5b6718da00acc69f811fe9f94c9f0dc9d80e9002c0022c6dfcf156a00000000000000000000000000000000021da679a068b2f5f473ceed588f07adc7f485003f7d2286a18c07b09b835881f4ab94c7d4ec742c33a7cf01801116fe0000000000000000000000000000000018a62c2f4a02b73f5a91f503b53332304afc9cd8769f236259789277599a203b8b304b38993835a87d7cc970ad514d2400000000000000000000000000000000179396865f859386df7c1b8fa84c4ee71c14daf695fc0841c293618e6f8c87fb56b924f3f91a273b969e8635d7f90985d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd710020000000000000000000000000000000004a8cb437297722c0c1a9471ff083ce60ec40c908af4ebb570c87133df705e725e3209152bcff26a0d6e4602030610d3000000000000000000000000000000001832e55a9e703d727156e4677ef4f82b86c6764123c3ed1dd94ae3b46d7eed459114993968eaf8e21cf24c59d042f41d000000000000000000000000000000000f606d5ee57b188636334ad60057cec4008ace88f14ea06324edaecb26da627670b44b6ac57b9fa2717d03096010785300000000000000000000000000000000145bf70f90a9d98f56ed38b3506556a48a1340ca6161806d055d7a1382eed54e294564de7fdbf525b0012de3d25ab5c838f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0,0000000000000000000000000000000018ca453b9d832f029ac8c7c70df846be97b530e6e42de3ba6943a7d0dc00296942f88eba6a9cc3352900ff124efaf7d90000000000000000000000000000000002e4514102aa3f772f2659ae9f1e2a91c7fb749ea590a3cea2c1a2e0f7236f71e182374cf7ebd2fa086dd921c29013910000000000000000000000000000000007c025696cdbf403494c5fc7f9a10ad0c549f84d1e06c5c4bb22f7a039486909c540776224bcdaaeb3880ae9d745dbe5000000000000000000000000000000000b5b5b70fae8b3953ee6661a0f4a1be25596839482d78710e584d3bcd93dff2b0bf4c8b20974744667e25fd8353cec0a +000000000000000000000000000000001265e90c564693db716f17d1a8815a8449e43b5a2d5446ca65160d864718cdfd413d5aa024e7581421c7222c29eb452b00000000000000000000000000000000133a6558baa53a2b8d239198e1dcd81af1ee46d55137177be467a99edf282edcd47b7861a3c822f9bd0df2e86aeb5dc2000000000000000000000000000000000d8287564bcedb1e57c3d74b0d484a9b475ce3f5b0322bda0e980de8891e2e8663abda99744b58032b8d7d3adddbac9500000000000000000000000000000000013cc35410d7fe07eac96abd2b35ff656e17b6b1eba2bd1d75ce5c87c5e76755ef9c2cce70f05cdec15d1bc44bf902d4cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a00000000000000000000000000000000193f0cd6b4051cfd89f358cf6643528f0f042ae30ba3627d297b4fa2c2936426a9c1b65145b8192f65dfaad1f2fbc358000000000000000000000000000000000a92ca8943e64a391aa39126f093f2b530f556c1e3ea1b55bef1c264909dc93d260eec6420fb7a4e4a45f932d57951500000000000000000000000000000000005c7dc5832f744089d5fe034bc93e0bcca042ddd1b221cdd5958be86214831906ddbf82508dd91dccee467fd1625dd740000000000000000000000000000000011b11b3d24f44bcafbcb9baf62cef3f18b56ded696b73577375dae8108dcfb663d437e4cd9e44b7e6bf49741e058f8cb9a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000073872ce0d74ea368df132897617aa8f941b67cf3fb395ca6c2f5bb2c551f17d68b0c6ef11e742206d6559796f06426c00000000000000000000000000000000156cc28eece7bed943c8410a44af112edd8576807e25701093eac0c9726f93da68a19c1d7b294f3ae6c84e32e7c2d5ba00000000000000000000000000000000050fe5987d5fa678be3d34c50fa6c5296f883e65ac3201c333b97ec0de00dee6187d2790c357a3f8822a174a534539a900000000000000000000000000000000177fee6e2d3909c0536acdbbdfc716f6ca19b6bfee7920a78ac9725c85114c69cd13152467e72270e35006b3c6caee8c74e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e000000000000000000000000000000000dd147bec9e0d1727c9d7597dea4a5b6b15c0a603dd1b586835580468148a502289fcc38194b2fccdcd8fdf0d8ec1904000000000000000000000000000000000186501fa4f3a20e80bf297e8ef1885b7d157617701839a3b524d61f35b2eb843ff0af13e253bbdef653a83e07a5871e000000000000000000000000000000000023eda2ed9d34aa253c8bf2f3b66b3c0c2551cc0e74f43dde2e429d9dea113a62572d245b44708bed79d662d9cba487000000000000000000000000000000001041cdaeb244803556e9b20db95f2a66830cbe47a68aea262865da50ab15ba658116657625318fe46fef393eeb6f3e2ec23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000c27b1feeeb38068ee52b0fa440af2e3bcfd16601c8af983d259f2d15316b513ac3e89069bc141f02b934f2e474253ba00000000000000000000000000000000183f966cdb28f344ccae4cfda63ba6a6f29d00ab942ae7db7572cc09305e4f80c11305527b8ba38c40aae5f23165cf9400000000000000000000000000000000049cf59bbd6c26ab3e25b3cb94878271c73c0b4436573d612311feceed0f1668f4d79aad92360c1c97d60b540239ae630000000000000000000000000000000015f35eb8e4c40cb1297f7128d99b109ca75944c1943abe9158813432145a4a2a5663b55dbabfa48bfd9dd01907e1e8d3972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a3264000000000000000000000000000000000e5d9a671862733804f517dc9cae2190ef0005f26394e3161fbe771b9a486368871f4b1f10f405e45048362f437238260000000000000000000000000000000008100c6f96ae7af5fc86d9d91fbbefcc1bf5873dacaba9c3adf1b2833dd529d87f303a55e5d4098153377effd0f8114500000000000000000000000000000000010e4863a9b037d4ae6dff827a34be04c7f1627670b40e5cafb1fbca2fbf56af9ea6b24548db58e3119db64553d18cf200000000000000000000000000000000036a298ad5e8b32041a18e3f6c5847eaef20a5b63ddece41bd7dc4c4a54deb9c6d7002e6621aa01d78d64ec9991f68fbdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000f62c0bad83c41887bf1ebd2644cef0577d793c2f3d67cbe43974f460a4afaf2e412fbf9ec97404e5e882ca0b23bd1a400000000000000000000000000000000191562ec9ace63ad2aae1f7fa977b9e0606e1da9775a978b2caafada4f6b3d9104562f2055fe037cd06df6093123a08e00000000000000000000000000000000156702c3feef1baf5ba202a25b9dfd5c1fc620e837501b0c5bcb85ec8b6e3e92bad1fc842bd1a0dac363e4bdf0fac87c0000000000000000000000000000000013a4b7e869ed9bdbf9671a5d8ca9145a2e97b6885d2a93b33f378e649e0e576be65bfe849119381057337315363bab2f4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd330000000000000000000000000000000000a4b273438168494f0db235f535bf31893bb70f4119dc4741aa3c5e63e93b9a8bc001faaca10e37f36e130ef53853900000000000000000000000000000000010936551b148e16249dd934fcc83dee55279495c2a70d46dfc45945a69549657c3dd7cce00d8136e28d64b0c800344cd00000000000000000000000000000000115c053ac0b68573c3abd5f047b8fcd897e3d514945c5fe6efebf1921563d0079eadf32f7428ecb703d9163bc7811ebf00000000000000000000000000000000162e86af01daf552589b62be849e6176d74fa5da9b214a5cf2285802dbc44f346eaee5cc3d93a085740f74cf7e1b17e1dc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b000000000000000000000000000000000d77cb5045f7d4578621c76bf5b3db076661c72174508279280de3e92f0aa57057ab50180f0f908561a87d412636d964000000000000000000000000000000001853f9cdccf5e6e4b87231b153ea5257f52ff10dcb24cbaaaa95426d0231dbb355f9c47475d125ec1079b9bf26b23b560000000000000000000000000000000000fab825e06c2329a19de853a05c4bc65f16fa047eadba8e79607bb31b84ed6541b00f7f14b15687d67cb4cae0ef9c600000000000000000000000000000000005deaebb5f31a62fc0bc1af13da63d0af3c716df8c9bf00f1e831af5882b88974c49e8d35db2545747c85ac35156bb668af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b0000000000000000000000000000000016faa5e91048badedcb33e83684d2670051c82b7a1d0ead0e28f4dddccb141a8ed1fa7606e4b6a3a893c55344263eb4400000000000000000000000000000000019b2c8758abe5d339afade4ad0c1d44d651f185f8a0030b81b136d5972510b353d43cef616ce04827d56255419831a400000000000000000000000000000000124b1e87f343a890fd690e384cd156da57f4f0fc5b1ca99c73bb0571332ec4c12d3ebe955e3ae792efadc1d5c0c67a410000000000000000000000000000000014cef10e4a9a41bf117aacd2fca5f1364a46b0c4aa0723a369fc6ede09dc76dcd8cb67fdf87ac49bd4bd9981a2e589647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b80000000000000000000000000000000002862fd5f38154dd452f65de0d3c1d54403cdd2a397ef416fb92e570913c543d3368a95fa114fcf48c3bb4b68895ba33000000000000000000000000000000000e7185443e5dbb656fcb9ed100949f8f7052ee2cdcba4f5c687a65a1b45bf66ede5c60b0c04845b9a870e004f8af8450000000000000000000000000000000001817be6d13cf2a67225b2eaf073e9f1614f3bd32cf5572766ace4a91f6b6be56f498b989f1c3dd3dbc9a819c029431dc0000000000000000000000000000000001cf41fe428b088a17b8ea93a653677705d5c024db530b8300752c6b100f2abe4c46dfc24afdaa2b3d53cd8ce0df1b6a70cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f8520000000000000000000000000000000002eed272430ca3176988272e6157a18df7151bbfed5b90979752a02619ef467af8083208dcc9c7d926490b1283baa21f000000000000000000000000000000000a644f6137bde232c3a909b742d30bba096ef88b711ef100144276d0944487f9ebe8331483978a47c07d3a42c441310900000000000000000000000000000000042c67cdc10efa8301ae95d6d4f21cf152f04b235bad2dc5a61724cba64083f690b3158676ee6ef10f52dcc7061f7c7d0000000000000000000000000000000007018d0aed5abb744cb998f84140331fb2cef8d9e09c76176def48a85370c6247c2ac6fc726eea891b2041ad5edca7f0244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f00000000000000000000000000000000141c0edc966b7c845d4e68272c6a71f8ffb7fd8d56b7cabcd556a98422f830d7a81d123d701ce1479e84047328ac1f3100000000000000000000000000000000105c1164d721b6dfb05b6b69955b2f25db0e9fdb58600a3229dd516076087aaec05b837ade68bd2a19917eee7b9a22bb000000000000000000000000000000000da3dd97e693948fd6955ae52d493b3a2d2896dd4ad00a0b549d4d392e81593472e4f9435a8b7977f3d58e324c5b9af800000000000000000000000000000000068c531ddb26a2299cc584b5bbfb0235fd774a2447134c06e7de8b94993804958bbf1ee80728cc6db647e8a244462372ad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf3000000000000000000000000000000001881432f4742dbe41bf774930413c98d49a781a48d6c64ee1a18f3076bc6c0e1214f92d5bc84ac65ee1c586c437d697300000000000000000000000000000000067e0a95f3eb826f3efeedc1882ecfa30b8b96c92f626aa324f4044ee74531fbfd50a221b1b0e0182d759d149d51427d00000000000000000000000000000000173f5be7098b756ea84f030e374973feb4f8811118ea6673db1db75ec6909303e571ec5a1d55a6bddf32fc80480cf103000000000000000000000000000000000f28540976a6ddb277df5951fe58e7310861af837cf31fe31c24f7b979f72ef1549372e7ea1ced15b655d24293dade7854b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c600000000000000000000000000000000188f296e218719bb9cabefd4f33d5728a1d280bc59c3d826a0f3b5338f92e6544a4cf36f1a493458e0adb246c01a415a0000000000000000000000000000000007dc8e4222c7ba78190a8e72ec7e6980e2581f51a8d6c41669b6fc9e16d50a2bf4d422af73398e76b2f39705eaf8a6da000000000000000000000000000000000b25a44523323301cc01b50d58726768c2cf61e691203dd34a0ce8d58fe4f72c1c33abfb2a56e0425fa9b7e2fe48e870000000000000000000000000000000000c6f11ea269d9061d2f462ac37401def1b2b28c47b84344d04d1f026add3237d99a586e3fcbae347a4ecb5646c8c569fae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e49000000000000000000000000000000000c41a02e937f8cacc0be5d9f2d9fff0d6d4302fd252f32145974206463854b3a7d09b3b147cdf2d7536e970dc13613ab0000000000000000000000000000000005f9367f4e31f7e4d6e21664ac13d55f501f5368c1ca77fc439db60e1846861e6c4c3c44909469f88e02cd973499992300000000000000000000000000000000131fe6df7fff97f132bfcba1d2599a862c1feb514a05b4b7b0bccf49e00aaad043edae9346bf726e2eee498dbadf2067000000000000000000000000000000000e59044f0950a741da3881282697f4a1a522b026e493f6009227da4c0a963de622d5e421c30e0023f4118c9a036274f859b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4,00000000000000000000000000000000144433ad3afca0a9581e7e87220a4944e26ef2eef6b887ce77d2a2559ced058e7349b36efa66c492cc75b014b3448ef9000000000000000000000000000000000267b90e45d7001edae01fb198d16dd37c43cadcd2ca87bd7cd1f0f65a95148144f5ddfe75d344eb4573c1376aa2728600000000000000000000000000000000050ade28b09b0394b08d128c089808021e4c65dac49d9fb45efb93792a4faf210230b650fc3ce810fb8d11947e9af5060000000000000000000000000000000003b1d7dd7c6d944d16724fd1bbfe0f53b6b50a70e133dc5998c82b51f817f489bfe1e0c361be36fa41f5af7c1577f2ea +000000000000000000000000000000000a081f037738b0d812da43a907e7c624e331108ffb72104d82725b9c14dec8449f5ba0e8c1a3f1379cad2c3e7aa99f70000000000000000000000000000000000937fb5d8b3c258b7b28555fb59620f114816f0fad46818a5f100bf7dc3332a03d285eda18e31e4047cb2606bc53b20c000000000000000000000000000000001574e355b7570043bf36ecd52f9c4d9ff556146d81a1e9d088444805db9b3b678fb55774865ad34d21022afea2c154590000000000000000000000000000000009f70a5cc658cdab280ed65e13aaa319049b9534a222217a08168047ee2491f25a9d2620c7343a6426bc54a0700bdb4fa53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b460000000000000000000000000000000015a76e89c8938b8a27e4857aaae8c942371b6979605adf774827e9438ef739428fc53b65d32e4e152cbc6a4de42b8bf30000000000000000000000000000000019494030ae0507eeff20b69b4913596c1b9ea6927157945c8295e273707013ef1f2cd08c058f6b469a6c99ad73acc28700000000000000000000000000000000122ea7ac21a27ca7c4b00207538bf561f688429999332c45de7545046acbd6d9e96d31f5f6a00595eeb212918a28d2920000000000000000000000000000000018b023e7da67cb8d9159746bf700f9e151fa60ba8f5a28b3739de005822929cd28c49b9dbb4ca8a10729dd24771730ff4d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000007811c759634904765029e955c3deca648fba6a9da6433b50a6d2086a59e65811d52d41ed8ff2e9bd63a4c0828bc702c00000000000000000000000000000000182c86cddf5e20697462c829f41c7b49e7976880311b01ed4d12d7174340799f19db0f295263a2617182bfd1b49e0d1b0000000000000000000000000000000011824bc20bd1b27876b4f48aa8fe3063f826b6b2c3dd777fb8999a25d9139f218f6f288955274884ce96ef2dc6d34d120000000000000000000000000000000000dd310d5e141e4eb13380db828caf74f62878959b6b2df998bebf9306965f723fcd4dae7c25bf2f79ece3e8e9b92de61b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c740000000000000000000000000000000005667d8c4f8dc3f4aa0021d1026a1d0dd0bc3576c49339262e84d20198fffe33a389d28ab1d782e9d19af761a2f097b40000000000000000000000000000000002803d5ad6393d7072e149f1f2ebf70cd8961ba3bbefd648916a8ac5a5eb893b71bb6015e201dc241537ad5890024239000000000000000000000000000000000122e1d0e0859b04143f23c4d2d2ffec09ca2ce5eaa9429dd0c047032d180bcdb10c106071d9f9701c006e5eb8ef88130000000000000000000000000000000008347a7bdb3b4f381b58ed3a128134c09563b345380ec948943e738347de5b5737540b57c28d00b9d060c60942446617ee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a5000000000000000000000000000000000b26aaa46a279c482fb395ddb84d5b4c9c70102c336cd565ca9eecf62cb96f59f634adf46af748826590fe65beea752b0000000000000000000000000000000012cc63256a9f73f450e86ee38c54ea78baa5bf87d3bc01320f7fbd85bf11e19f75d787b9b12b8f2c7634368a9023de880000000000000000000000000000000006392fe611835f6fd50229725d71d435f704f78cabd1b5569e1c5a89d4b11f911f0e34ec034369f972a80eb407938b97000000000000000000000000000000000f4ff2d6a991fde9093000d7bd9cecb289383d259346d83bc9bf5389d4c39c82a0e1d7deb84b90ef370e0a19fce28d2b3e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000193752c40fa0f466f7c8bd26658f133d0283d2ac3b02eadd27b3e9681329307f91a1512fbc53e537f9e1025a3d68a7ca000000000000000000000000000000001106d751c9e1637f00e51e0be856405e6b69421d81bb30b9b8718cbc9cfdc36c80d2848bab0d5246da84f10b478fe48e000000000000000000000000000000000827a83f28678c4e39c4963e95c2404a70691885788e5457e149c0c45d4e8c74eef55223ed15cd75fad9f7209a6ecaee00000000000000000000000000000000072667f02b781c8e0a75d0ed8f3d55e668ddcc8c61937c80653e240c3a744c961055c782ca41b15211c0f1e1ba800bf5ab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001419629aaf0baf779feca264d0d9846b987506125b0049ebc8b307c4e3ffe00da1284a94a012bfd60456a4a937b2e0e000000000000000000000000000000000119a801bd0a5a1c1b25cebbbcccc7d2bed9baa4995483f4ae94121a8c6cd0c3f90a26234f51590d66cc38b8bef9020d3000000000000000000000000000000001125bd15fd9814ddd15be0997a6961b6f1c05ce7944514371f10c8e5bde271c4b936d6537d91ebed740fbefe6b281a0d000000000000000000000000000000000982a2904a524b1fafc50d540506b8fb07c3b4978310bf3cf53ce570b1b05e746981bcfc06d59a78d170573b09347f3fc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000b1da333e508ec6b0329747fef35cb926d922091d4a45eab7cb5358f20496c66e17e46874ed9600cf4252432c29aeb07000000000000000000000000000000000c757daad8f3ed7dfd64782548eedfe904f7ef3bcc11eefc4781fb37159d07825a4c9f3fdf9cb3d8f3944277bf25f88c0000000000000000000000000000000011160e21503d6fd61a2ca0212a7d48317186f259a987a17cc3eb04a6d9251736e4a66b739a8f3095684b7d91ce6f79730000000000000000000000000000000007440ec0f9197352a3148f9bb3d3dba9b1d5add903e48b50ef3f6879859b22ea0e31b46ea4ce566930d8853520abdd14ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba00000000000000000000000000000000070ffa4d522df8b9f62aaf36132bb1b857e177280a7b6d3af6bfc79b73ad3848241df18ca7f8993ae3d67005ead9264d000000000000000000000000000000000e32b65bf035bcb11f86c60a334622d2367797d0226761b58a7db8c7324fc4bb498a558eec509c2326fbd0e7bb8d3d19000000000000000000000000000000000dd291a760393c6e962818986727e5ca5d46544dc47eb49dd828c6f74caf0599e88c4293881714c425b0697944faa861000000000000000000000000000000000f7ead0be081467f3371ab92c249cea73dedfefcb6aa16a162c06e30605e104844c3dd194b4a89ad5230f596bef64f19e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ca545b53836899e507880329799e4c1a1acc17275f5d71d87b9e41ccd7a090da854f9936254448c988ec772a813bb6e0000000000000000000000000000000016c9b03fd01394560497d6a03add63c034f96744d96a13a4ec92d28719018d1eba1465e4332e53f37f2aec4d93d4ab7f0000000000000000000000000000000007019f5201dce326d5a6a1ebecf3fe50e22335593bc9d3e62256351c591f0a1a577d916055d79c0b4abe191b6b8011fe0000000000000000000000000000000017acbe72fe30c386e463f3e9b35a474b902f6712b30af88ef340e6fc6ec0fe2e606c7e26432c2a4de33a12e35ce41868f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000057f856ae648279f2b6dd17584e1388e4dfdc9e870db48ee6ef5f58389ccd4ba17e074b79ae12b728c59e2f91bac5709000000000000000000000000000000000e0f39f4beddbf05fd700458448067b52c11e963b22603f10d697d6b6286b1449b1663e032bf7bea48f2051d8ded923f000000000000000000000000000000000022cfadc1dc399ef5f12afe1349d9274cd595a9ab6ef7ffdd68f8bd2d170a4a783ce0a7303878d809a16bb8073d79860000000000000000000000000000000007e301565124eb66d59a70897f2ac356e7b0c1bfd4e3b57e508ba0cb5c9c881f9de86b91fd5133aa2977c8e81138d66971927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000000edf3fdbfb03bc07871079aa4aade538a97e1619b54d0692a7f5f73d7fbc8abbf680ea3a99325e03c0501ef174deedd1000000000000000000000000000000000b8c1b5d3c926d7da6e0583f67d981af5286a04429e857b0aa4b1120604f9c8c93f04e763da169137416dc9ec4839a910000000000000000000000000000000006ca2aa4c7109f043da9cd90bc801404685db802eb8bc925d9d098e7af3d9f95ca490790b2b1c77995c050aaebb935db0000000000000000000000000000000001f40a2090b63f94f93e8b61b5ba1ac62a37548342ad81a9bd99ce8339435a7d7477c3b9cee9b531a1ecdc85a72041555ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000021464dded318cfa86db1e4329f302bbeca7095d910c4260799cd2a60ebb20e60152868e67a48b86f44000f267d11c33000000000000000000000000000000000ae45fa46fc8e043c3df99bc0d87ffc5867208fde0eaeda782230341a8624b101346f35fa24e1dd67ab200f5d6fbc8a7000000000000000000000000000000000795b9afedbb128a46c1eb25c52a71375903adf7d3520535372d9af5023dadb1dfefdcc0cb546e9d218890123252946d000000000000000000000000000000001852511855bb368cec51c54d95b430259f05dba6bae53b5c42d69f31371c30cb611037fbd81393a896cbdb6240114549d37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000113b883c6bc41b0673145bfeccda414af45efe5710f436977712e7227f38911cbae851dbe03928f38e310033458eed72000000000000000000000000000000000853e32773ef1f95a3936aacbca50cdd5eed3d08dc467d7ee834487e445fbdaeddb0df394bd0c91fdb06d2883c4dadd60000000000000000000000000000000013a7f9cdebb2ec37fad172d31a717f4b538a8ee74432c5a5e6410460eaaa3b5f24d223b76bde4277097e93087b7136330000000000000000000000000000000003d6f141b56e1e2e400fe821524017cd972678a7d64f660c313e6a8910b72b5ac04328d45945077aa2946931c8dbd11706d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b40000000000000000000000000000000016adbeb3530f6b451d870b2d8292a01143986cd9890c79a64764383575771b8608ea61beb2de87bc034d3b8a085958be000000000000000000000000000000001125d7cf83239e4341c286fe0c8739e7013b234814b26a079ffbffa329ee4705da81fd12f34f49d821690a11b8f83c5e0000000000000000000000000000000005873dc5c0baf0f3297d884ac7b652c749abd0405b96ba60fe396efa179a79fa55be76924b0690c9a528c605ad4f9e120000000000000000000000000000000000fceec23f479c72e0fea0d10d3394d7121bf1673250cf1ebe72eca60af82f232fbee342e2c8705434394d4e519fbb40f56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af0000000000000000000000000000000005ec966cfa28e105f3496f977a2f046fb206a190fce1a6062df0fa1946f274cde9f6fa8a71089af8cc2fbc2b60746cf40000000000000000000000000000000013c77ab66fa92a2411391d366a331a40accd120db1c6a656bdd92858826fcbded296293c13ee189ea3f34635de56732c00000000000000000000000000000000162795b6feaf6a63e6ea2d34f2bff2a4985ad26463b8fac69f8525eb0a005bd377fe7ff4aae820d361592d2d88f98f5c00000000000000000000000000000000044c9d5d3bc0d99693f5a0605ed467cca8b5dc7c7093294d14015b59bfd8ac6bd479b73ed52fd30d8bd891ed971912c571e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c,000000000000000000000000000000000c3bed2f51a60f9afa6655853ec2f0e9d46bdc1277bfedffc468d9f36cfc7ad9e70365fecc84a5a40d863dcaadabf22a0000000000000000000000000000000008c5894a4f93b02fa1deda8b556798fb7d71f53046ccc305588bfc00b68bdfc34b3f0bf154ce7cb50c9536ad45e65f300000000000000000000000000000000003699501ebb9698e98dc998fcdac54dff895457d2e4e0a0e2d65d275b5798dc016e921bf1f65fec0f284a563aee66ca70000000000000000000000000000000010389c73de7f6d860c972c1f09dd24137c898e92935c45c10565ef3da3406cf521647ef80688f6e799eef4879ca9a6e8 +00000000000000000000000000000000114b9c33bd09899c684e81a5a4e620eefa4e620c01c391a4df5caa75be462ec7ab027a9ae2c31d6643c48e3d75b6ced6000000000000000000000000000000001925084d2a1f537329e23c77b8a820c385ec5e12e4a145888882ec611e99b05b789d79bcab48326db4424309c24d1688000000000000000000000000000000000a1dc78c25cd16211a38bd0c70d24c84da1b83adb219e1b9c06fe6a6669d6e0281a155b4cec32d32751fff653aeef1990000000000000000000000000000000001daa74f19cce1086a87232464ba903938465da5e3e1f9ddc05a4b4dc13f1026e1b07af7254d515d2ad6960ea62dca1f77f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da0000000000000000000000000000000018810adf0cc793c21726e9a27b7c558aa16b81af73f22629c478d293208a107fbfed4511d9cbcc25fbc2826bf004e7dc000000000000000000000000000000000356b25cbc7cf65107438125c930dff24b7786cbd7eb744d7f27967619d5cc02799451ac8814782eaf9aa331e6f8dbe7000000000000000000000000000000001164ab32ddbeb11c2c8baf7f311ffb01bcc367395bc7ecbe5642d344a8e879c74a554b3f9e4b6ed7db4ea0f872cf96740000000000000000000000000000000017704b1dfb111807d1f5d90c370a5b2968008a5ee9fd72262b6543c93fa168285c04931198f5195f1abca648722ebdc5630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb0000000000000000000000000000000000a336a04a8fd8e18dd9a582da897016983d9beb0fdbcea6c88b7c0640620be52bff32afbe700599e3c08669c457b760000000000000000000000000000000001765fe4faeeb13fc2c007682c031ea7ff2899090e16a9a11959c5c3ae7881a1dd2c6d2b7f5f708a92349a2b0de4b92d5000000000000000000000000000000000e7c57db660133ebeadc2cb2054ab4ed16355466932685d4d11038e1e1f47b0349b68bc4e918dd48ef8e1c5d7cc53f7800000000000000000000000000000000169b629ddd7add588b91d9866a750570dec58662e43409031a5e25f1b2913c5c5a7a7cf666953c99835431f091ab1b140969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a51620000000000000000000000000000000017d03e9855f3bbee719a15208ae24324ebf1879972ac134b027c9e03444a5736863bc55604158e81b38c7fd78ba4bee7000000000000000000000000000000000468f7c5478cc0faab7098dbcc455bf18525b56272c2d02cc1febc1825579a613edc6b455764ffc71c903a0704224a4c00000000000000000000000000000000067104ba5366e7e11bd4d516565d9cdd93d4390f2af3c1ef2ea3b1e84ee8e5c0e0fd8ac11ec9d2553e4cc13b277d473e0000000000000000000000000000000012e10495ba15b29c669cb9683b2fc7a45fe7ddba743b4a39677fbf85aa738480eb9da967eee69b02ef14137e102e240eddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000078f8c17427847ddaa1665d206866231a5f36d3a7b4e8fa13910161566163006b5aa5d9696f423d0c44195de65326f21000000000000000000000000000000001613c465b65940f43c61b5e3c93313ae49d92728518d9cdfc57b49d6924479b70e281e724e04fa5f165b5999f1c1ed3100000000000000000000000000000000031741b6830c16d730619457d42767a51037fb4118e00bfd6cfcd8baea35ae76a5159bf1f4639fc2951f0b57446110e70000000000000000000000000000000011a618ffbafe4bad0a435d04084233495e5f7fbeaeb66d0d49a8177f562329b52a5ed4fdc680b791f273a7b0d3d4b349191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000043c09eea638e524661c60ae3704fd1c18c46443ae134a0ab7b9a98cd398377febd9026c28b3e1e50de98766aaf0083600000000000000000000000000000000105918aa1476cf52f91b9ddb7c23ac18af3bd5269dbafc369713687010720affed6b12af9414cecd521cf0c7f5416c350000000000000000000000000000000019ab4a3eca904a15782f560bbbc8819dc09275f1f6d7c3b8e98aa0a96ec33dcb528284636b0f42ad0d503489d17161ff000000000000000000000000000000000a2abada18e79c548d5829991a65491ebcfe0e1a2c89a1e05f06a0ecd197797c5ffea0ae90b61f54c6b3fc844e0eb3ddaa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f848000000000000000000000000000000000d457cb2c77acc8ba4b19ade0c724a2b6b0966ecfbbec8cbea745439b9bb7f3dde2febf9fcd6c5e6139fd7175e57b1720000000000000000000000000000000003154466283addb0d0b5d86a9633f8300960cbe8bf6a1405a3a040472542e9da63fd4f79a43d641a47c2b69a31298d3c0000000000000000000000000000000006599794823797f8ccea9daf0459b9d26e0d207f5fb95383c6b61eba38516b272e8ae6ddff2a9fa791e69c0eb25f3e470000000000000000000000000000000018be316bbe0416ad7deced1486d4e31490f5dc7e379c17542b7d3e9dc77bbae9c992e657c884db320cd51c2141a4abd2049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf10000000000000000000000000000000007ba1ec5293d169b88ca4d2d92eacd51f0b8cffdb403632ea8ffdebc37f3997baf736771231335d12717cb45b51be31a0000000000000000000000000000000013505cc24222fb2ba9e25f5f3497653462f5b10bdd0dc88f9b16d5643a99ddd4a7749dfa6b566f41cd2da7c2b1ae93d2000000000000000000000000000000001465fdced698ca76d5faaa7e4faf1260cd5c4fd2939b16d3593e3588c92de3d003540ec989be9632fdba4ecae889ef180000000000000000000000000000000013a20cecd5e8f161ac70e40b8e9ca4c23e2b267690a3abea941c293b03acbbe4fc68a1e7b6d35b79ac46f65edde73a3e0502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f000000000000000000000000000000001936436783f02f3a5307bfc0bd8c0a00ed8013508a440d040ed4f45b37a4e89986102964a328e93fabde6d9dc7ca424900000000000000000000000000000000000f16408b869303181b4b4877b554353b26a7b4750b711f3c41cc4b6682b2113cc772cf9bfcd0cf60e59ef29a5d0814000000000000000000000000000000000d5880e2ef94663ead736687ee725f7ce98fdc594230c1ac9e8345d39754bd616e261076aa5362776a6026129bff105c0000000000000000000000000000000006865ce3cdb5081e86535beb990d95ec3d75f67c7e881306607e4876c42714d627f8d548849aece4382d1c8f2b693bdc9787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef523713750000000000000000000000000000000016d941b6a0dc023fa2699c836b74e16c31b4cd51538f73fbb271d163519d4de1cb0f6ec2f8efde22c74ffb532c576b16000000000000000000000000000000000d10a7bfe9541a7b22d455f1b68cfe2422a83a070d93476aa0844670f02aecb36e9f41b9d66e8e9d0d67c0ba85c99f44000000000000000000000000000000000d7873f96d45fa8c9ba9cb4913a7b01c8e38876b6bb2a05506d23df0491bcffb42983ef663db85bc3cf755f476291a79000000000000000000000000000000000c22fdb83f9991c85b3577d1ed5a171f28460d79dbc6167b0c30b200235c512f999066eb1fa449115aab55128f8f2dde10b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000010faf9cb9d0fcb487c9e86a2d2123105baa8691d82ebae8f5bb7d5ae7b7d8154837120eea86dfcd35ea5482a7ebf7f8a0000000000000000000000000000000014e40640eb6e8e38651a2eac05165f6cf5e0178b3711f34828766ff9db951e1348f0cdc652a78840dc24ada8b1c835c600000000000000000000000000000000129db7482ec62873591018a8399a8c5e4bf00e8bd9dd78dfa3d0b4cd1d93ce5ec7531e56d58b7a1cb3e58f062f6895ee000000000000000000000000000000000d8db3b54b6e71497faed107b31f5e44f328780cf01c62cb5ca00f99f10385ebb22a367cc89505640d1106a9ceec98c4072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000011ae9bc3ce04df2add17e57f260a72f88f19a1e44b0b074cccb7fd547035038d19e5f2228db46843343a69823decda370000000000000000000000000000000015ea64b6147ef76212bb5223d6d5ab9ca866799365683720866d8ce1117f60bd552a8e9981c095894258ca3c1bb5150500000000000000000000000000000000173bd5cb455b80b78951b15180fa7f8fb4725c1a12e5c53df1b9b31b45a29083e66c7116741d9aa93448c81b5e6014610000000000000000000000000000000007eba059855ab058c2066c643ef5268c864d09ec9962537d65a1686322c374eb5ab8eba4c4260ad0919dc18b4289a694f3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d6000000000000000000000000000000001598471460ae082c2e2568602c99923193c913b9e803cbb7a4503ceff369e8c4bb3a19ad245c08192e12a2e9b3e75c4e0000000000000000000000000000000013b289bec9d97c529382388f7037749c10a64f915746d23d8f37e15db9dcb173b3a6d00bf45e67b8c70959472148321d00000000000000000000000000000000094a99f9b031a51b7d54f7b8865621b204c85d23fd66fe8ce007f0b852f8b5b895010745b2fc469abb670e38fbc41e50000000000000000000000000000000000e36daddab2134f65696ede36c50f90f9a1c56165e09243cd56fd3d9902d3c78cd85e7028f6dd466f6a8655da62ecefd393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca17700000000000000000000000000000000050a818ce247367e8b57673d205d6bff8c650bcab7bf794dd32494669eff865fd4e05d7b4d35eb579eb475a3a0320ff80000000000000000000000000000000017ae5d612bdd46e1351dd1367c08c16ceb002a29832eba75e48d4c82e364f17c58525ee653a0940955b874da6a5bcfcf000000000000000000000000000000000eb2075367b42a0b3dfa30799ce1ab327eb583316d15b8cae21b716e6c7fd8cab96c67bc39e353f5e842e74995356c070000000000000000000000000000000018ca4b533da1baab37f05afc3ae0afe976e4f4530401d2f97176f5c73de3eaa75b8a34e8c6c0543ca0a08aeed28e478bdb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000b3f4bfec920018663bb39c5520491da5c538f82138f03390c768e088bbb2880287196af937f1f70e215edd49d1872ea000000000000000000000000000000000037e7607a60cf235d8e4ecbe69d378dc02f0a8e40b7f23745e15a73fdcfc971cc8707d55a8c5b91d9a5f42c2f49c455000000000000000000000000000000000467df75c2703ccff1a01fa5bdebde210b61b5f3fa33e76e55be5dc953f4758c3a2c499cbd42b256ff5a2005949d9bbf00000000000000000000000000000000010d574c69050ce9e909dc23a76e9a2106870e8d8ce2a0e30d42cbfeea56ce3167535a9af1d453d4d8e6a450eff870638e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf24000000000000000000000000000000000907fb825f247c85d93fca36dcede9c22a409fa82fcf540593e8247c17875a1385fe009f0ff43853c404f6c96e2809ce0000000000000000000000000000000012bff10bd4162207870f6363342f2541804adc6a4e3f7b8be51d361be34def7a85fb39357c85a4e8df670fe39233bed00000000000000000000000000000000014f7e61ccd52bbf6d050c9d506751e03c8771b320872179a9f0161ac5736edc13bc133bda6239abba1ae09bd6c16f0c3000000000000000000000000000000000ca78624563584f8929d72668da70218a2da12b42c4b894108e6b103201372554fdd6b3bbbf2d94a9d0cf4053eb07d460940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c,000000000000000000000000000000000de8e87899b294575392d523ff6e153a7c2038302ac74574bfae7fb222558f2b4c9556be1bc2757b83ebc180ae710187000000000000000000000000000000001881c7688debe3ff795788c90397c3fe3d6d9f56da9221778d7b12f5a54d8c0a00e1a8d4bb9c0b1d578dff862516b5dc0000000000000000000000000000000014cdfdffbb956a20d8521ccdb214adab14975d22ffbac107b2c15f42e97bb823c6a3945a5b299d3226e2044e64f8d1ed000000000000000000000000000000000eb769b301cb7c0c976623badda4db8ccb18dc7322472b5fdb969393d5d82b3ce94bfa59dae06ece424bfcb88e24207a +00000000000000000000000000000000164227fbb787b2d47ceea93faf1cf7890f48107ffae3628192235aa57658d9a2861db13fec0e58c347571c2ab0cd11ea0000000000000000000000000000000015478417b6758826b1d6fe0c562d43451e289dd50de31ef365ec70faf961ebb65b510c4788b6c7da2dda9cf56d3c8a74000000000000000000000000000000000f9e50d802ca8cbf80caec6489fbb24a2761db1245d9f7e820e6747bdd0855902ff211c427c00157ed9b1bffdf39eea900000000000000000000000000000000128f69ef5dbea5f80dbb9558a25f133b9ad77492250e0654f8fa5b55266f2fd26826a5c373afcd74990ebf768d6d8fd20f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000005f7cfb31492dacae51caf4036d99d917fa13b0d2353bbce4e6547ea744b3a49b162deac2f107149ebc2f79e74828f720000000000000000000000000000000015ed4627efa9b318cbb52f518b734327f5d1cfbb097adc6184c5034620504181a298ac7e52759586dae2e107f121a9b600000000000000000000000000000000023e832638849599d9d7854d3ae18648e67e8938ebf606a7c86c3a7ea21cab8d4dd5d9cda5c482e05d351ea3ccd854710000000000000000000000000000000001849665396bc36d0301f4c9adbce81fd2f2d0c7f89925487d91a25c6bd0730ce31678694a319666cf42162608ef15a834680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba20000000000000000000000000000000012c9b607e29e35f260f3c4617b4217d5dbc6953eaeffaaa903710195e080d593972e7794897eb176aae3539401a483b10000000000000000000000000000000019cdae8d1d9035d1fc4b4db09e7da3c20d3b8777523155d407cc6565a71a6c951eca609d328ddbb165c2b5a3e6b081da0000000000000000000000000000000009c4629b67c1c50e5fcf316136bc645e9e62ffadac8495c084f97e32b0a3990b3b1019261f78de576ff7ffc89e36e2af00000000000000000000000000000000070a49e8892c5b523f5914e2341dde63127b694eef556de6dcff603da109a53b342363d9a854dda3d2833e25afd5b57eefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad0220000000000000000000000000000000018d3c9259f70312c803dd6bac6488541f92482f7eb61ead71fa42bd5e2cca9338218d62835051bd308799beeed3b422b0000000000000000000000000000000005e0da6859601b6ada82b1826a455a846f8b4e54d9f22c3c639835a8a89e17ea2d76e2f49fb151f519de3e9adb78f0590000000000000000000000000000000010113d2fdc1e8ce0027b651cee6f9f6832b531d843db3ef7bf209aa00018715c1c42c68a82c53247a267929ea3c9363f000000000000000000000000000000000e7d1152af6448aca78aa7983013395f0dfc298848d86def6f017780e9cb144bbb21540a14a4d47b61d7a9b8c62376fc2c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000014125c81d4d7a8ea18004d798311f0d80c41c8e3a08366f686145e867192bbb13244f9f77217559cae72a150faba12a6000000000000000000000000000000000fdcaaf79c0607ebe9c8ca309d29d32284f3567a18dbbd23da9d96bad7269395ec2445d153711df4c883e8e7f7b02ab2000000000000000000000000000000000d34dd6636ef18b14f011fbeb62d33ec4358166f96f38a54c36b8797b51c1bedafa43d9f51fa4afcc2acc0cdd991997f00000000000000000000000000000000017337fab49d545caba55b763c23ce9bb3d3cc475f5ca37a15322e94c37825fc800cc7ee67bdcac66f9b5c22b03bf6558b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc930000000000000000000000000000000013434d32deb96edafc9a0e855281970b7c748c92b3472b34cc758dc3c17c4e6fdcf3190c910fa54a0259ef8bec75a3b300000000000000000000000000000000137df92ec14dd2fc02c0ec15a4e63547492154b4d4809e25f3ebbf24fe84255babfd6949770ba61637cc67e8ff299a2b0000000000000000000000000000000012fb20ef106e8cf3c79173e15dcdddb216c25a4de6797e411fd11d5632aef1304b36f8135c915c8c38caa2d778788f060000000000000000000000000000000014ef5cbe5711a815b9ff845e9201745f4117149b54ea3c6d1606060a192d513aa8ffe73425e37a42537773796b6fac8f202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000000b1581a5def94e95e565bfd402cb84f2f21c181639c047d8f91044da84bb7854f5cb4eb3a6cdeb66569d99410ca3ec6c000000000000000000000000000000000d8029828f4ca245cafa7f396c25592ef08f6768e1a5b806450be6ca5b548cfb212d8c4787c3f15fe922f466dbe518c0000000000000000000000000000000000f51e01a044b6da437e3850349476437e4ff8b94fa190387099b17e6462040918cb2eba3b10d6044ff2123242005bd6f000000000000000000000000000000000991201229a856f88348381e1f2e282f0487e7daf1e5a4ac3854e66fa3d1303e3c20eb9eca605859e7d46dcfdd7615cc8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a00000000000000000000000000000000011f1bff5df413ade311b0bc3b46c4ecb11e386b886b71226987f14bc1a3a4b986412c2bfe8a4618ad5d70afacf4a3b4000000000000000000000000000000001972f49fa8b36d11d9c9d4ed6197261506b892ce6dfa932b87e686cb197560dfb8718aa413c38ee1bb771a5618c17224000000000000000000000000000000000e563bd240f5e18b518a792750c00aa5dfbea1f79b80a71369238ef15df9885d341d6901fb9168a2e74249f036e9a688000000000000000000000000000000000670e59ebf6e30b458ea505075840ed5348563efd536c31003d8d0bafdacfec7ba1ed401c616a3bab431a0fa71bb6188ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a7300000000000000000000000000000000071319574a93739586eda876ffd3be5d982e6fa04f5667873dfabfab83ddf603513394e0dbb9f418e725b02d2dc7b876000000000000000000000000000000000c6a8e0261da2ab499bf9a639a6e261e8c479f3f2b2d12992b41a3267e034c25373d4da4645626e6343e867466bf3626000000000000000000000000000000000045a0312dd5fccdd19edb65e24d5ba50e44689a9748ed9ec208320bd9eddf8d606b9340cd34ebf983e69a65c242fed900000000000000000000000000000000090b3dbebc7dd49e9f764e99c43b5915b67bdebd00d22c80e36e08873e5c5186bcd082dbce94f4f230b237d60cab7107b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f60000000000000000000000000000000007b04063dc315025b8545cef11be6b601fb4ae02597d75979b4946f3872764ffdbfd309f5ab3b36fe47b810f8320c1b40000000000000000000000000000000009361927d02192433a8d3c3d7871d76c6d88361774913067d16b68625aaa60f5a4ca19b6fd4140a5a11f92dec57d783e0000000000000000000000000000000012501f19b73fc6ddb4d194895e5cc2b89ca84defb7ae94f3170f25417965102fc195f38dfb7a2d88aa4b24e4a2fcaa4300000000000000000000000000000000141d0a0be60c32247f6cb0e0114251ac68c90fd43651d58c3108c728601ad6efc27c27a331a2f086d55aed54b3585fd1b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e47200000000000000000000000000000000043f74a82ebfbbcf4abf3fd02eaa4483108a3446c9cf041bc67f5078d1774308ddcb3f918d7999d1e2c0876177cab6790000000000000000000000000000000000da7d4fa72dabb314ad8f68b61fcfa38627d1d7719bc07767f596671c58cca16e005d36e42413d03da3c643eb46b1eb0000000000000000000000000000000019f3f8f1a4008f9db1b604373d3566ae7c14a9147f80597a31839b83f0f8dcdfd829f7fa933fef3499b671867c3121fc0000000000000000000000000000000018bba4bfcf7629fcfa47935e36462cef4fa3751c7affa2ee2cb2fe3e3532d46ca1d247393ea190fb3f48077270d6a8b22810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b89363890000000000000000000000000000000005a1e0e3a023f67aa7ab0109814f130a05c8c739036b98c70c8a8ddc1828d2cc4e2fcd16de4ef038a7373d15c78e81f10000000000000000000000000000000019e2bb467409b3dfae0b06244b4140de7f75cb105ab897d1ffb999c6b53bf3b60a3d11354815621c5d9f07962a237ffe0000000000000000000000000000000012e745499d5ed626b4762b57923bbfae7f1209408e7ecb8813a545c4ece0ec7c48a4015e0e264b47fa08fa82c39d3a110000000000000000000000000000000008acfd3c2a2e17be41a70ebbd1ca2cff2eda8a359e0969a389ab0a6fa51db5601b386dd035b26232be08d704a02033a7175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b646000000000000000000000000000000000fb464af51161f9c2758acc09d16754d4d8ac52a37baf2fb6ccd3bca3058bd3cd204de6c8a0bfcce8822f16ecfcd0601000000000000000000000000000000001819075eaa6d9e3f0568ecc2e507370f938a65169cea1ecc40c9cb4d02c83d7964254602e3d041ba0f93c24369fdf3940000000000000000000000000000000016c179832739a8129d2ef184f4d1231d24bc8d4093670a63d73771983152ec322b6a8c954565d61c2af76c4f6ef5e8a2000000000000000000000000000000000f6623578a4fa45614f4b74768adf65a753a35dacc84af005fa4d7328d733a09f12f709a7bb7f89060f60d4fac85780ae7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000189724a2a0723e7727d224ced126e4288f4743f6855b035722f2aa36cf2f0a6fc23f6835c25222b670c15248884451b0000000000000000000000000000000009a57d85140f31ca58e38b4a99c4ef103f0a4af0d5546d416134fa8adce6ecca6588c3c56ba06b2f59015acc1a081099000000000000000000000000000000000dfc67b7644851c3e928ea33aaa0f745a18983edb7488b148736e81ec0c62345c11e3f0dfce729d893dce27ea249860e000000000000000000000000000000001712009a81e06a85a225a46fac056b139c8da05e6b72074ee4079316e490a06f51c62241e380909b86239d867d631be16aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea5000000000000000000000000000000000976746ae4d9325d5e8300b57ce99650f28055b5e020700ee5f124fa76ef3bdb9923101c3a1f46b6985b8203b4e8c60600000000000000000000000000000000057310c3b6cff6c849938f533b401b0cbe10b6ff3736c79a968009b2c0b90708b6b9a98b8e594cce09c579a64ead846d000000000000000000000000000000000d39511e47f33e310332178b8a0210e76e4d4c7408ff5c2374f5e7bde8335525e03897cb3e2bdfe59bb76b21cc6411df0000000000000000000000000000000010c46a621b7fb2e7ceab8943b3371475d3d6f132fb658b8c6bf299888711f1b344ebd4a5793ffe6a7a7eec8c66c80303985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000011ffff38891ee56cb1fc062d02f6c9993100f991a556445b5ee1b1b0d56d8e64bc6eea4d7f69a6b6dc55ce7d8b4ba300000000000000000000000000000000000d6cdd95d1ab2a11ab424d7aa596cc7e5de025c57217da0da143887d7dccd6fda0addae7c2fd9e0996bdd0d23128e807000000000000000000000000000000000499b3e69214fdb4db7dbecd619ef9c6b5c8343c808e4953f593cc89adba02b5cbc56a5e7a3046c6023c5cf305e54e85000000000000000000000000000000000d267e21606c16479065e47da8e3c058cb59f55a1316a87117a73dbb067ec26f406eba6a40b30ecb00f506bfd3c32f4da3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010,000000000000000000000000000000000650fe9f3cb3620e0bf1654a7f1dee503b79fe2218739bad608dba9f1e5330f325b4fb7c340f118eb10dd0776fbfe63c000000000000000000000000000000000bcbf1c6a684dea5ad6c1a540b3525cbc64c7c431f37213bc8b08c8d8915a331c07bc899d3a2ea72a9a4bb2c539cf56b0000000000000000000000000000000008fca1c364333f558c7284afa1be486e84bb035b049a2108b0df99395149de83549de153a784e4df2b0134317c85292b0000000000000000000000000000000002784cc1d11667bbd0759bca35a16a1baf49a21765c6c2c3bcdd4fc9697ef20f1274be5caa0f820d37e843bc38c68957 +00000000000000000000000000000000051646993c3aba532988d7baa07eaabeb8366853436b8b19c0fe3e14ed45fdc65448d749adf745291ab5ee62d4e824880000000000000000000000000000000002cec01290d8e51ccf751183dcad20bac20b8231804a2b6f87f886aacb61d31b14f2335629e97af0ae0546a17a4cca49000000000000000000000000000000000762afa7b94ed580fd07d5141a8e1299c6ec439bbfc6c1a4d695d9aba4ab5d6dec93dc4de47096d72e5ad87d879eea190000000000000000000000000000000014769208ce8a9682c8e0340f68a0290a7782c2b04e3c13027f0b23966eada2ffb2156f6e20539738535fa0ef097f78d6709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000acc4e3ccc3574285c19d2545839d1da9db6770b078aa399262b7c91a7c41fb4c83fe7dd0aad19f4e3eb2b56273f664f0000000000000000000000000000000017851c99881677b89956fcdf1b8c5ca5dd0997d810f3fb89f7378dbf7964926cfde315f8722531d6d715b4932179eeb40000000000000000000000000000000005e374a4c7118a76e59cdaadebb1c4e635b4dd18665010249f3bc78d559455d27d547856573e264c98ba39f6f3abea69000000000000000000000000000000000a532979cfd5263c774f629027f7624799dd0f9d6a77f675d790a85fccccad6e93c00ff2e5536b8e9a92443af14611e69ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e00000000000000000000000000000000056f109801b7a4a36fcadbee7219c06ac74e4a3f7b81616076c33ba2a71d7ca0776b596fb25d29992fa26d416272a4b4000000000000000000000000000000000c02d7e6ec50b778a7ff36fbe5751ba32beb1c2024b17bd99b46239e6dd5a708d2fc689e8e8924902e0d80287cdbd6e90000000000000000000000000000000016f18df97f48aba4d1b64e71eb894904d02ee7f6ba425e58f38a08542319e2498cb0dada8dbbb81bb398c9c924ae44270000000000000000000000000000000017dce98b335f536909ce01647aaabb918942ba2468d9a07c5516cfd347e1baa02029d39de1b2602932630e4819f2f00f391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000005b23d6f76b8bd4f334e91771383856794d1dc65b365fbc0c94f21fff049761d7379f0d512c42ce13f878e0661712d100000000000000000000000000000000009dcf70c16f524ff540f132b35074cec6ed7dcc1f319432a0dd09b3ded0778ec9ad0f05d67ecf3ebb7947951fc4b25d000000000000000000000000000000001075fb15240d532a9543dc59cb0098cbd03da77c3bf85a0ef8be1560958f8ab57d3777fab5836ba98d67c721a4a8cd460000000000000000000000000000000003511525fcf6fe224eb87b13999d2548b6b8bb8069fd354f298a025b04a33f48be72d8e82a99b9aa34ce5ccdc1f1a59c94f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000019039c69d52a66330d2d8572a1308bd88159f0383c041ee7605d0aa86f1d0fe3e884d0a2ad9c72405149b5fd204ec3db000000000000000000000000000000000942163eca08672af3827dbd876b9c1adeefcd5ae74a2768fb55f1e8b342aefbf76bc6546853a2b33e26fa866e60a4e9000000000000000000000000000000000c60c6bd103ba5bb5323b5107373cd8d706038bf5ec2b367a43bab72411523bea35985b974c756184c346626ab2622d30000000000000000000000000000000016c4a2fc8a9b3c54f65cd150c80a3bf70ae8dbacdcd37128514b4a881239023e427f0b0c8984ce219207c458bb380da970f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000003e3df9a8ce220be05f15904a3321a6805ab68bbd539479be56b2a870c3d61234e9cda8190bdc89f48e7f0dd9374e1d800000000000000000000000000000000040446db3ec43e3e67dce62efd741a4157e8ea2597a143f7d6273b66c7045daf31f72397b4b9d374328520893157c1f1000000000000000000000000000000000c3a7dde5b02df5f7c1e750a9ee5314a580cc6ed53d326a9157b507ebd6c2da314c37a7f1837f7fcff7e8754ab603b7b0000000000000000000000000000000005e617ca4eced853f8f2e9fdefef810c97eb27d5c8bd06c5b4ea50c03761c01e8adddfe27d2d72eed8cb25ea7514a4aabd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c00000000000000000000000000000000104ccaee210aa8196010a6478702a54cb7ba49c80a98ecbf5c0920408ff8b4a7568212bfbf3561b6a7790520bb73bd42000000000000000000000000000000000870ddd51dcc76c8a97ac4b4f23819df48dc8a8798df0450d7a45d273f830c908541dcaab7b066bcd668b289c846ea000000000000000000000000000000000012fdae32b020a346ad5edc3bab360fb5ba55004ef3dfe5f437e841b5dd7284ddb3880051956c8068e49a3fd165143ac50000000000000000000000000000000019081bf768dae314fbecec408d687df5b6ecb32ec24b41f9febd583c05693f80345e6b9d81322ddc72616c1cc39a86811a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001316b5ce5bcc168d76d2c862230ce604d02cd3d242c51c250bc6b6fe5c380c9e83fe7041049f2272481ab38f44648f4700000000000000000000000000000000079acfc2b9629da9c9f3394874e64aa00527de21e726f02db180f86cc0b9a97138c2c567832e287635721ca40469e00c000000000000000000000000000000000e11807dcd4ac69fdcea71e3e6a93dafc27afedf12c2998dbbb2e4f33e37ea736df73af791eae69bff84f3bb212bab47000000000000000000000000000000000e834a34fb63d9df68d683a26d79ecf8ff67066586e5f760d4468ad196c66d4ebf8605ebfbb7bde201f47b35cfde3a5d8ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd000000000000000000000000000000000990f115519d2125d47b925b613edc3303110e9040fa705211e0d772edb2e0f7f88ce521d1738a5f65c9d158e9d360c2000000000000000000000000000000000bb951a16decf9be8381d0c88726b53d90bb32cd8aeff962d48e43863e4eab1839bd80d7434c7eb808bbc0e32e92a4290000000000000000000000000000000013dbd5bdb7caaecc42ffd81f14be0ff3d8fa228ff121ed4f2f3ad5961fbce617d7cbc8133fd49e03caa62f7d1567541b00000000000000000000000000000000195fd9b85e19d0e3e1c93bab0380cad6f6f3bdbdcbf5c6ec32b7de7972421d0065cf0b265f6250c02eada67e95284bce26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000009ca977266277bdeb985750df47353a6b81c5f0c473eb3369d25a01df67610bebf66a6de5727a465131404025e90441a00000000000000000000000000000000054410a13287ecf4aa18f543916fcd65b15cd5d54617433217b0a2b91a79fea764b511b3b270de3e8985e8f6a2fd8c380000000000000000000000000000000009a9802a03a7c9fb63c1eb13972cd42ea2df614a0972b914c4015c2e8630af319d12fc8108b4c88db9508a9a77d9e57d00000000000000000000000000000000094d83483bca296b20b7bee124f538ae9c659a84541f5c9d9fd22e98251d2b48051ac55ebe07bcc9d2e9109f526d60a6c470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb380000000000000000000000000000000003f2d93ddb6d5983fd5521c1d1726addf662af0945aee54788855037f47a013d2fe595231792a05e1259c5e5a8c553a900000000000000000000000000000000004f4f4e7df5dee975fb440b5a217c27d9d1eb83a5ae280a2b147896f6bb864abe04459c17ef56d784d3c4a0b7ad3f3900000000000000000000000000000000069da36057aaa89cda458af4ee27fd9ec969c8f7612cbb153da0e010d67bfdddadb2941cfbdba8c43019a9f1aaf9c296000000000000000000000000000000001545b8325a80176ea148a3d9301debd7046f33a1b419b4ed01916a3d0a072037fd617d96e0bad32b208983ac3be7dda4e53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c9269000000000000000000000000000000001536a81b203df2640bbe7e695b5fde186021d21685f24c25966cf11dde554d49bcefca64f16697509a9ca86e58b75eff0000000000000000000000000000000014348a2bd4907cf081f2f7bc944a98d3fac671abde029995377df190f7f60319b8de1698b99be39c821328e32a449c760000000000000000000000000000000000e18d4da3823addb2a6cef8336c83f99f390e23d7129365d57035d4363aac7e9c4da9f8000f086f7d2206666f990dac000000000000000000000000000000000d6ba54e2af9afa57ff4536a35e9b61c8d8fb3d431b653a0c66a2a4b8f11d9b5c45389f894d64485233d4183895921f3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f62000000000000000000000000000000000d88d5719e07e2332c54ba41f330c7763d2b2b7c4140d19b8b0972fae6ef902415de5f2abcc2342fce24d3ed8ffe156300000000000000000000000000000000163aa2c768eca58194fb76822deffc37cefe04ceb70aba38a51f507be7cd64c0755abdc2e49e7db234cd5d68575c2d7a000000000000000000000000000000000e443d9953468b8cea4eca4f5968e214888e2b95bc20ece39483ac551d4e180c0b0a41c4668c8ddaf761a0ac03fbcad3000000000000000000000000000000000691930530ce86a1354d73cb21ee32d968e6d89b12e5a09a7991c7d27dec302348af7f49c3e0de91e1a1838aa11651e795f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000d7c6f9d4aa794f34596bb9af4d62363462d9804898ebd7c7db7544be1f46b4bde488ec59004adaa0cbe40aef525ce3f000000000000000000000000000000001094629b1428c4c284b7a64d0623e10ca0c4d395bccbfaad89d1a737a3887c10b714541f2681c33e674c3b99a36b7a450000000000000000000000000000000000d6812fad9c5ea365a64ebd3150238349d88b76d041ccaa7e637fdfa6c715d9d6dc3d3315cb95fd6919fe419d028783000000000000000000000000000000000eee5cb772ce02fe2a4883008f17570aebb902ad7c40b4024a5b24ff75b3aaa2b54ace6fb4601b1c62837a20204194dd740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b0000000000000000000000000000000001026d21e075fb8921dd849c98252a565d39ca9f5a62a825e7e3e77ab5be6620e76e45047e51350c48d9a4cf98a1222a9000000000000000000000000000000000f6459a8287bb2da77404a515dd7a35f46a4aa49ef72cd2cdefbc5e5242872df5f7b7aeae6848d59afa1dd142ae7caca0000000000000000000000000000000011e3545151d4e0b034b950cd2f1a3fc2d29e9d53250ade2482b7ea6075dacf7e8e777afa1e8e612b45028205235265970000000000000000000000000000000017a869d75144ece603c04d39cb56a487895cc882fec613f40f6a66601bdbbbb7748ec755553257d654d1558b1104a981f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000011602a23c9b5cc091a700114e5d3557bd4857c4fc44cb8628ef327ddeeb728927347438f123e2011f9cfda9b6dfc42e4000000000000000000000000000000000c4fad264ca95827e9cbb9783e36cb0b683fcc33038d47bc7ab6b65998770325588e5b910e811cf7d61fce13c3378d6700000000000000000000000000000000009b4711aa67e84434cabc289a78fae48ea86641a162d48b79bbcbfd56237705dd2d1e9ba3a18d737eec29eb8e940e58000000000000000000000000000000001160fc9e2a488ad9385140bb62ab48ee613c2284208cf2f92912e1b973ff81a5d3de338d9aa6881cbe437907890258fc8390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9,000000000000000000000000000000000cd0d8c746ecc8d92fcf2232793282d7e0e17e0ec27ee851487eb7788f590db3487296061075f36c24f67cd4c4bbf36f0000000000000000000000000000000010c5e1d05070c27f19c228813051c6a830254542eb71469664c842695b219670dba8ddff858e2d147019847866f01084000000000000000000000000000000001799ca7d8f2637da761622b793a3ed3317d50b902a1cabefdfc776b0d0ef88b707b8a5c36786d5ede3d8a381de4e069d00000000000000000000000000000000129881a3b56e0014bf1dac4775f509f309c33406f2cf22df9a0ccd15c87ea48a868d4437303923127bf580b8d6ed0a8f +00000000000000000000000000000000000d087c1b98f8c67c1bbc4389f21d9dab02faf46ee4223c609e7b9eb399132ae168bc12847c580f58edbb9255dca3b000000000000000000000000000000000065ded24bda39d2b830639fa511bce8dc770eb95e349d6874ce63b3355d23c1da3ee9771ad44e57c6c661b7453076fe7000000000000000000000000000000000fa3b2ef40a7c3d41f0c3a5f86afec252c6ce89bd1bf1f2192026e22fa256365360589c788753033658b1ba151797feb00000000000000000000000000000000105040ff4dc2bc435c2a82e1174e2ee0b94043d69074f01e8ed013da8c431f33c94a438a93b06774411780cdb72abbc8414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d00000000000000000000000000000000129c8c1db08ccd0dadd59b04df67a91fb6547d97ce23e59aa57cd3d38458e6baaa67285800809856e7e264d812e584390000000000000000000000000000000004a0be934248b4e142fc51745233b6d0ab2c46f53a8f9d4c84981e5eacff146ee6227de289c713e4ce24a4341572c9d70000000000000000000000000000000005916d14a8592af57a40418b10376e8e20f70929d2ba568c1fb70e343a1dfcf3e63c791cb639bec49c50aebd2f816fdf0000000000000000000000000000000018682c66a461a69b11d7c32f7aca07749e05a23fc46547bac121752aef64e9bb98a274d15a14faa93af8f284790acb9b99eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef9420000000000000000000000000000000001552053742eb89ae3d0b95be919c84e53919c898ada92d3eaf05605a19ac910091fc08a65e9764f3108877c837d478c00000000000000000000000000000000118e5d22f6df0e6bc7447177ce06659f94315478385372046b649fa6d39fefeeb492e6623e0160bc47233f4d3143e326000000000000000000000000000000000dd02c30cfdea5abd3550a9f28b546d82d5b3043f012de622d892062945847748ba820555fb811fb3382791ec43ce1f700000000000000000000000000000000050373898b396d9a641e2f2ed832c7619515fd9070852b891b4ce0b5bb5ea8b5e24248297d53e9db7cb946e76c4433fa49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee0000000000000000000000000000000006b465f4b9d60a3a14e119c54a7c35172bd648c86a7cf331e80ba849fc87b9dcd48410e3c9a07b634e83fc7dd71e5b9f000000000000000000000000000000000283ad9c77f549042f79c47b8a69e72164f0ee77aee50c20519d2b89029c63ea86dde2744cd21eb5d37e896c3abbdf56000000000000000000000000000000001668b08a87787928afe92d941240e503da07b646a34cf82ed09d4c2f4d479aa24358c8475eebd9bcfaa6bae17c430cfd00000000000000000000000000000000150e5b28bd901f7a2a9af44bfd6b78cc84900dc05e334de306f9a45f1e67708adddf4dcede8150a39670054f97a643436e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d00000000000000000000000000000000060d75764a92e30e80e7c1a6df1482585f4de901bbc36dd9d8978a76c12c739f85a9ba16741d0b19ed480fe2dc331e5b000000000000000000000000000000000024fd15c9e5b8872d2e9dae9ae96102bfb0e31d15e92a24316818862dd8ca7a6fef271d499fed5e0db6dfebc4c72e0200000000000000000000000000000000058cda551e1fcd701c6a3880b276a2f7536a26aa366a6425a1c42cf31eec678551f489a27f23ed5dbc76f19b0fbfae43000000000000000000000000000000001152e2cfdb584295563af8120c523a9f4c01cf72da64fcbe0a90a284d693a3089f299bc760166be062cf9f8efb6a951ad3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000116fc24e980b2e7ad6bf17bcd7c4f06e654bbf766ea0238a66d738bf3c2d41c8c63bd52f81553cca5fea91f5f9b74a2c0000000000000000000000000000000001078f19ecf785a5e0d3e764b7d6ea47b2d077b5eb222f4e6a9451f134ff0d77a0b9a3b53caf599705d131e3b17b6ca9000000000000000000000000000000000e44c07f00a1f198583a8ffca43da45d8e54e1f2a85bee7afff6c1c733b5d0b5712961c4b6d344869a8e4de3b34218e000000000000000000000000000000000083c78b3568cdf808b75d9ee2b03b98cd516bb16ca8cc35757f53f12119747bf6b5b0605bdffb2f079cbc69e99ee0bad6de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000087ded6945bd6fae7a0aebb1ea68d3cd34588035531a6cb00fcf1b83e06f7ec21cd3486580165c1364027b43e238e34d00000000000000000000000000000000005a2fe8a9871273bb60cc7ebef44a361300a1033f3f0230a731f5723fca124ec9d305cfde45802482a45942154398cd00000000000000000000000000000000121eb94a41f9e133adf082ef651272c178d780a1c31ba8797f60a208ad36b4c703c9b6c08be845f8844dd14d6406734d000000000000000000000000000000000e5e3da7c91ab4cca1c9286020aab9795e64e667d55a5a700241f9589aa3519639f168d040a0027ac057f334a9f740aba28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f00000000000000000000000000000000041f9117b426938acb40c905bbcba443c043bb55cf9b876edfa2ca051b6354124f0fa54d6a88ea172c3f5c10c6d921b3000000000000000000000000000000001828dc0b9533274db6afc802b2fadaacf57f28126094b6b9038ed5f6bbae0112c873fe5eed15bc49b970461abc2f5c3200000000000000000000000000000000107df6da02f106ae47718959aeba7b4fb4a8f0e2651560e2f2266a62566e13a5af86430b8800543f5eb6b1e96be79c69000000000000000000000000000000001628fd4a598813133de75cd7c96ff3711b6bc826806b96d07e5a89cd549592f0f51c84aa9ee0642cffae5630ca1ebae1a3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c000000000000000000000000000000000e8fff44163cd9c2a4e148eef3cbbee19ab8f648da1a8d438be27d2b0bcab393fb7d49e096d9a7abed3d8f82c11c4e03000000000000000000000000000000001274335d8bde3d14924f8d7ba18fea82bbc85427892f18fb741c8ecc5f2d6d7bee74c68058164c55db3cb8da8597bfe40000000000000000000000000000000010c7fc728c094e47569f0e75446c399d20a1239b511e34d8d6193dd32df607dfaa4377a1825b3892a9f74ff4efa0d9df00000000000000000000000000000000067d904122a6581b5d5a60acfe8156dcb6c10ed083840e506487b5dd9117927663e0ad883fb91b4914778ae082de0a7eb90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d000000000000000000000000000000000a909706e3ce45c86f2c30de5e820c8c9eefef207e530fd504511827f5e6422714d3f4224afa6bbba22ffca533d647390000000000000000000000000000000013ff61472ddc0d70207692648087c283763ede668ae380b0b9d6ae6593498b0adc9d4e4fcc73b5cce250e7563f7577de000000000000000000000000000000000a81db69eca785373c4dcbafd8635b23a9f41265e91152f309fb2945622937e65b5c17656abf8aff042a1fd1e5e50341000000000000000000000000000000000c66269c3ccd9e91766d1a640789bde6de752d08ffe3b2955df8dad3d2a0b6cea9013af235cbfbccee8271a7242e310614f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb51800000000000000000000000000000000181f3f4a16696980bd0eb9bd10ff1084ffe90bcb65f12f505b25f0a26dc1d4e16987d486b2c0b117fd6f2e356b83a5250000000000000000000000000000000010d7be6788da3ec56c87acee68ea8a03e7d467f816060207bb163dfcf8a4e7721651bf2bb23d5bc390d50fb1ee6625a900000000000000000000000000000000196c1ac817493f51d9ca891b55fa65ad5192df83cdb63eb1a634ad54e2d627f7feaa68780418f5354e6cc09cdf2f6c5800000000000000000000000000000000190f36690b8d36f2e295b9625f23afef9d9babe87c1ba0303f60c6d44ec952ba6bf8356469cff9d952f8e26bdb86ca06e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a1000000000000000000000000000000000f77df606f0611856c449c58393f4ee7a6225a5bee667382a48f59dfc747736a895d598f90ab26002dd0ed3a5a8f5a200000000000000000000000000000000012aa50d0ec440884fc6c2f7a0e8db8a5e79160f0c482209ae1a1aca2b9dfedfec6d6ea09252a373ea57905130220a4820000000000000000000000000000000004773f46165cdb19cae49cc42663316df39586c62be5b827535f138e1fca8dcf62ba42ab60ac6dcec85e8496f32b9eda0000000000000000000000000000000010c91923c2c7b3eb2cd9aaf0455c0eb035e38e5352d218b07ea23f50040ea58fd548b373c1bee9113d3d44fcb25f6ba08c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e0389300000000000000000000000000000000044b95fd5f0e049abfdc2adc699646afa5b0f64464779efacce85a5279477697090615933069992bf30036c6ac70dfe50000000000000000000000000000000002778e7dacc5566354c24ea1144613a5ce8a38eb56d53d230ca145ce83d5ed88596afe243df22cba10f423e64a7c103a0000000000000000000000000000000017e87cd2752d8674c373c557ab2b922e02620a070aacf6f5b3d3d07ca35d89ed2666da7246b800717c0e4763dc35f5f6000000000000000000000000000000000a3ed312e5f309eafaed486629d953970cb73f839bf30f506c2f393df4c283f299d6c643ae6c229430d919e8aeae8bd839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc3652000000000000000000000000000000001082a0edac6267151c8ef11fac7614b74cf58b39b72fb71e4d66467ed4fb3264b177c691e569230f2a13a64b4a48c6fc000000000000000000000000000000000073a8d5f96ee580741bee1f82cacb6139d962fec34c44c648c8fcd0322796429bbaef083a11b4c8fa376d4c00cd79c00000000000000000000000000000000008d41e51dc2822e0f14b992511de799fe4db3783a05ddc1026a53faa89af000075ba5aa830ceb7551e51f0fff144c1360000000000000000000000000000000006bc4bf0bdf350af417160d06e8aebf2dde02c9b50be39b0c4dcb3a045f9e04f1f041f6de10328e287df6121247dd4e9c395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000802e7b71127a15a279a629e89f194b51d19c4f329efd8ecf9fe69d340dd06068c8467da6ab39be25c194077d3ce2428000000000000000000000000000000000250172c787afe866b428748be8359d8e0bad161832abc108c850362c5839237483fb38678d77c94696260508907726a000000000000000000000000000000000d46223c1666f314f9a1e32a94f83d8150755d71252e19af91a3b460ab0ade2db2364d8c6217cb422095f0d9a1ed648a0000000000000000000000000000000002fc2849014717d1c07935efe601325e1842ed333897222f6de322dac8b50bf4d9859eed8880a34676af0d0e3277639053ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e10000000000000000000000000000000004151d78d65b0c9eb26822e20d90ace8fac209a1f08f62ce722ae3effd7fcc476f4c0179e71b09fc181db96fb2ea4eec0000000000000000000000000000000013d17ef429483be98411947ca0771ce671fc38e27bd0aa4abcfd5ddf1af9e138404d86f4c2ed74702f80a573638d92f500000000000000000000000000000000178f2a7eb43b9f88acfa892b5868d7f7c5787a399c1c566de39ecedbfe88357fd5256ec57e1ba12e9784382c14331756000000000000000000000000000000000253a391373974beef746c4397654a30a68992fe9163f9518ff0ed9b7be37b858ac60c95259ab894bb6acfd123333b7fbadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa,000000000000000000000000000000000710bfc39e92b0b9d15ee9bdb4959daa3a78f66aeae29eaeb50a0aa0460f3ff703c86eec8903011b4b61a0dea725ab08000000000000000000000000000000000856fe7a074d37786237cc14ff1bc53c735ee8133b231dd3fc63dfa0dbd1979304bcc7b55cd1bb66fd7529e15d15db5800000000000000000000000000000000014757f1fbfd4fa7935ebfe65e150519d6eb4f4831890df4b236dda98804b79862fb6699b587c3e568fd6de1e582409900000000000000000000000000000000000f7b54e4961dab9e94b1c4b897177dfa74be9937694a38207ddc9d6290dae1d5e122cfe4c8c31d853db3783999a7f0 +0000000000000000000000000000000003bfd2535c6d8ffb44670bd02b5aa6f050f5cfae7266fc3225865bc3f34320820eaeaa952f80da51671f6d97b3df9d4f00000000000000000000000000000000026c1adc0ffc3fef9ccf018ff9a647ef5c69c5133fb4a6566cdcbd3180d9ee784f34d667edb1dd54ae292253b45576b4000000000000000000000000000000000ee90fb541becf96b4728f1859aee5ae74e30ba9193b90569b66b0e1d087eb81f30c21774298cb06e7dbee8f8aafb1930000000000000000000000000000000000a4361867bca952446f64c273735764e141eef43d156d6cbb6e68dbf3bc940e935f7bf3a77b57fca4bbc74bda2f26532c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000004bdef85b0da28e0531734016e5953256c75c3620937736cf65de5f05b8beff294677668047a3b74f0f135b846a95bd6000000000000000000000000000000000b754df2aef855b4a0eb6f6aa03115ee8f38a31fc852381deef2b59bf23e2c885ae166030ccadd5673bacc35482f81e9000000000000000000000000000000000f1d760ac6dfb65b39c999211d4e4c3623c3fb8ea59cdcf926249a07285a8e4da1890327fed20ff07f12359f6d9035980000000000000000000000000000000009f2698239c8b452748126ffd83abec768edffb83dfa3dc7943fd499c8980e2d9aad76dc38b336a4a63eccf5c4150ce0b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c000000000000000000000000000000000cd724c51fd56528dfa688df46f71bbfc9144ff98958b559fca8fd05eda01c38c28630ee19579012b9913a393264cd90000000000000000000000000000000000aa1e55f2b6d9385ec6a9cbafcdbad157f7ebc06b2e30e2380ac54e71db5259cb919e17042d6ba6e045f1358aef276ea0000000000000000000000000000000010181ce9ffe235b6b271d570b3c2d6e1be60c53b4a98ef5e8d7d00b463e5bbc9d8d96dda881e58746090983d6f8edd35000000000000000000000000000000000333deb8b14f499319ad675f482fecd80f9a69ba369425decd441cd2ff5c3c77f11075f61bb1d90d0be850ff657d6b7cccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000001581a5440fe892ee6eece5fc2227fe072dfbc440e0620a1e5fb505ff0b16d9e6033d83c83576b4b6ff87a807dc81b88400000000000000000000000000000000099b070a0d7497f33c1c478ac424d5564fa645d836a3d572d98782f08713d8e425b571433fee928475688db2b3a9a04c0000000000000000000000000000000011e1cbaa09a6361aff9e199e21bc52e98dfacc49ed83e732d4b4f2503b3bfdf85d029dead4412b6f3d7ea447e20d669b0000000000000000000000000000000005503e151d620e9a5a142e4f7940ed88375e7efc1109214141c191e9f38a32a40d3a92d6094584e763e0cf13cbb54bcc99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000007c780f119bbccfd658f3f1b69ce9c56b1f5269bded713b6827d97d32b2a6deadcc02c410138d984d977527f3609cc2c00000000000000000000000000000000095aebacfa33928a916ca7b0ceac699c71620781b35cb2f3b254bdbd1544b728a2ec1fb35416ed7a8a3a630bc07ff8720000000000000000000000000000000012194abf7e411f4961b6f8a1e2ad052c27624ded863d7a9132d9c7ecd3b4074ef0060cd86adb73056323f4227ba5fa9e0000000000000000000000000000000002fde2be9ac1e8265f258a09eec85a70112ef1eadc3a91429c9206555933e2b89aaf7493fb833e33e5d61be28a12a1c2338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d000000000000000000000000000000001586c65405e810e1d5b59304bb4555ca43c04a593671ec64d5ed2d2e626b1f8a89f48a4b21d38fb49909b8c614209a460000000000000000000000000000000014528cdf994e774b8fd54090cb45b68098c1ad9a351bc1f36a9393f3b4364f5beaf58fff6e5f8b21a85b67bc427c0e920000000000000000000000000000000000b48d8713aee51d80c79109fb8b4e0c6e32e25a7ca24dd3e7700f8f3195730375208b241b2c722af3c2295a1704cbb3000000000000000000000000000000001913cf6328429cf2966a48117dc74db0d45be7800f93cfbebf597fb48a8bdcae4fae2df7835f9536481f67261755da2a1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a0000000000000000000000000000000017e45a481449f167fd579accc896ac65aff6f1f7392df47d006b404de3cb7ebf6cb59d0913438f3a51e55a0ae3d446c9000000000000000000000000000000000cf4b7db343bea29af6e244a71880538b41b826bfd1d06a21512d00ce58f5d7500ab1ed77b446b1e3782df736bf3dbb6000000000000000000000000000000000525d08e134779ca7614784818876514e14b65e799b7832f61a63601fc491c8b9cb25430547f961cc1c22100170a2065000000000000000000000000000000000450cc2156c4716d0343f32aca82fd2d0712389b1aa984b31d51edc2aa0545c88ff52e470b15eb6b2c22e30f79864dc85202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000004147c105ee8b4db68482b9d7f6a716ea1474b6c62efc41b9444ed1ef9e92e2b7010a1c1ecc59038ac37b385074a6bce0000000000000000000000000000000018a600a85c5c38be835d2e91a35cce4b59e5f5ac3b735fc007bf5498062beca9befc9c8ead58f9f21f6e08266b149d800000000000000000000000000000000012a476fcb81ab66e3101de2364cb609b17e06eabdff5246bf736eb9d5c87fddd404e8867578262f07a05731b04069164000000000000000000000000000000000c54a888678c28766ad17a18507e4bf5dc57dd394eb6e9b69abaf15e645cf4779bf6ccf4314d2756584647cf27af089ba2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f9722000000000000000000000000000000000e809152c44cebdd8b40f0d22d57c3b31f29700e0cbc3e69f660bf7270e59093d84bf7ac358be7e45e799a75cf9c13df000000000000000000000000000000000c6c61f98bd4e3b7095fc7f1196baa98139087df00fae2a795e76544ca47e453f75929cab07c11cd3595de6ecbbbaff000000000000000000000000000000000171c70446c19fec3c152741925c8db28ab0d140720cb6a6c45e9bc66c012a421d12271889ea43fe1524944ff572fe6850000000000000000000000000000000006e4baa09b4660c69cace151e60320b771e56e7460b01442bfcf26823c17779034ac241b9365dbbfade770d2056eeecd892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf000000000000000000000000000000001917a23350e94963e3a7488ac1dafefe9ab11856d405eff39d655e31ba808f02954b63e822613d3c6e5f358be04be4a4000000000000000000000000000000001620211b06288c16aa02f4404192e9f57a048e900f0ec5db9b478475f13b142f924c6de720031b3fc12cf869b422af470000000000000000000000000000000011e8ded9ad57e46713e7ac0044ee4edec12689cdfb98838a74adf1a35244e3d9a4a34c81323b089c10422abf26b044e70000000000000000000000000000000006f85c7478cec590fe3355a8d6e9557c5be084c161e090c72f1281be4ee56f36aa1e3c9c844eb45d9e295c15c4cd903efdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000d555d9f23de97318dafb257cf444952bdd3e844e9ed5ce193c10b76f5179f0c6851f93af1553b128f34d3a7e75339f3000000000000000000000000000000000132704571a12a58f629dab48f1a3956392b40f801c2b3757c15f7be46ef1d9115d89920c460c0e2bb062b3cc1aaed7400000000000000000000000000000000152829eaef900fd2f19d6fdbb8f7eb3b02df35d218b494d075219b69016256e572eb7f555f6fbdbe17c59a666d190055000000000000000000000000000000000fe5c67c949b7c89a867301528f0ab24b04d31d6f18f575c475ab5a6098f7187eef20a9ed6e810684da9afd8de96ded6660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000128bf3cbb5208d84dff719ced229921a889c9a4d02f5a508187662f03852531fb8be1f4c2aa9ef01de7720c352dbd19d00000000000000000000000000000000158d89a44b8fcf9ca8c96a8e516e130ae8af19ed71c2b8487ae300c3cdb546e248728bc58fd9cfef21107e0dabf44fc20000000000000000000000000000000012b70b42c8af4551267a94a795fe18e8d054291225438adaa33fe2edafa87742fc3709abcc7bada5d26e3a14649cb47f0000000000000000000000000000000015a853160b7666ea7d64aacd931314497ac7068a4b8bfe3a7deed85df2bb8dba277716a9d1ee50c56b2970016ada509d1ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af840900000000000000000000000000000000087c7bf08e085e19f0cb301d2e36478357e835620b1cde6e132c237ff6fc63e6fc16a8753550d50fb93a0a1741302cf9000000000000000000000000000000000615299ccefe4da879e5f4b01d6b6ef8358bb59ed8a2b365ec72003c16486d3266243db81f48855d81b6a25440bb861a0000000000000000000000000000000001498fd20640f39dbc03a474f4514e5e283256ac19468077af1c9ddaa40759dcf93afe256de1e49be6469fa106394193000000000000000000000000000000000cba50fc4919a29be2f4e74c261487dbf855db1856e8d5d008cc3f4ee5eb3babfdfaff878adae49b96db99d424bc4dab2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000001369dd82ed013474581ca1ab2d2133341d7c1d52065060d72b8317e899e79e9077bcefe6c76c3c7f67e54f76dd3c246c000000000000000000000000000000000405aa84d3ceb02bf8eae989a9cd65afa15451443af6f3cf5e70f5cd7bb8d413c57ac3893a7e8b888ae93a92dcfa2b20000000000000000000000000000000000378d003988f3c6c16d3b12ef47a4a49e2d3d2c7c67e384bcd510939581770aed92e06291ed3b7c742769f0d1ef740c100000000000000000000000000000000048bfa6550711a17d52f48377821baae6f3de6ad99ccfeb8302466047dfddee8005240cdc65b3ab11ed85b11f128624957f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d0000000000000000000000000000000013370ab697da0ff0a0efa8ebc7589b465374c983c13daee7b5451e8b299933eb5a4d255ffe4aa46782ae0916fd3990230000000000000000000000000000000002ee77be6e0b6fd260ad660a96100bf3259329faf2ff9796102928e70cd52c2bda8d0d1da1d484d7b023d3d59725d12b0000000000000000000000000000000014482fee88e02e61b847c08e61d7ae6fca2d993bbb69bf1653138150d5d7fed09cd5cd4097cb4b6368ea8023383477cf0000000000000000000000000000000009d0380d0d6fa39c9e242b9a67336d86445551658bc29fbd594239a76d7741ba388450caa244fb186afc36d35c8740e93017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f000000000000000000000000000000001537d4a47247af8f60f77d309666056c412ce089f3f011457e894f74fa4ad5168baafd36ed3294f5f61cc9cd8f87554500000000000000000000000000000000119e43382a846c8945e58dc7723a0f24b24d9cd487d436a156156a6da97795cf3f4ce382d21435695949b5137a2bf1d3000000000000000000000000000000000be5fd015998bd6043f124048c82e4d848e1b8c87442d0021390cba41c294de17648a47dacc06268606ba73cc95ae6e70000000000000000000000000000000000e05a3dbbf3da8320c40d51ac44c6380d56ecb460b0e7094819aa6af4d7c70d1541d4bc1fc5afd453b165f3d48d09a708bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd,000000000000000000000000000000000b00b5c14685ddd17ee99c74598e6bfae5bb1c103f8ebfaec3a620ba57312f3093f9ad5eac820d81096dfece90e72ef8000000000000000000000000000000000dd81552160d449cd787ac27c76685ea0dc993a9fcf8ab182f1ff5d8a484a47c14c1c1a785285b44336c7f6fc0732a0c0000000000000000000000000000000003008b6d97a12868554d294faa26e2ebe2920add650f841adfbf0ee89af72fc4da5dc23b45b7ff191a58c17971b50ae50000000000000000000000000000000013f438d927f35b04bee8fc55693d5c97229c8548ff9de39fae6e26c26f89623d3b0c810b9be8dcf0445910e8eac5c58b +000000000000000000000000000000000e1f825b71cd9edcb231c178e160e37bea70108b369afb248edc7c6a59114c22e843fb5541e0f26c77a2b589ea88fb3d000000000000000000000000000000000d65d777e91920b17400955a4afecf82f67cd13f3e7c5d9c2076c4a4d8f7f26383d22d9977dfd0987f219a625c8a621200000000000000000000000000000000045716092850318c343f0dc5337df1a72f8c74dd729831d12103b46127c9180fb50cece34986a94fee6119e72d16a55e00000000000000000000000000000000083fac698ce800786719d1f6063c87d9f728da03cea2545b4ad8831f6c24bfff73e80f2c2fff1532f6d1fea60e7d438ccc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d00000000000000000000000000000000171d60b76698d4d3f14b4eacbdce9fa66b8c3cc7ecfb989439330fbf0d051d95f3007c389113346e614f5ec8cd170a2100000000000000000000000000000000151a96beb250bdeca3cdad1b07322040bf1cf2105dfa854bb24fa76c8abc25ef4fb924ff995da641244f9daccff2ff970000000000000000000000000000000007e5818778a8331cdcd1432b46abf1efcdf7e4aa8907fd42d5e7d14b57dbfc48125246b57587755ee1571be8b52d2c57000000000000000000000000000000000693eb562e22fa8ca4a655b76e43b50fe487ca1d65cc3867eaf793e50496f0b4658bd92199104c2ab92e4ac53c44db6f57b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000001059d23ec6e472937d80829256db506d2d2deb37d4b750a980568cd5b0db085358a4d610d59009b64db1a9225f9f6f5300000000000000000000000000000000053d9ffc47672f1058856aa08e51aebd469111dcd129ed542454d6401e7893323f8a9c63641f499cd8617c7389518f8e0000000000000000000000000000000002b9b30a5e37b18af4bb02ae8cdd56f6a87820716ea1522a174a0d99c3716295ad0ff2daf663697cb56bc6053c9dba610000000000000000000000000000000019d3230c0bdc228fa0cfd5e0d8bb88be959e70e59d931d9f9e3683d5e65d8ba0d121fcea329b23c5905b80dac34de33b03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000015a619addc75f425596f9a51c6cf2259087cf32afe9b1f07e346a2f4e1f8caa001dc10098d1287b89837f426d073982d000000000000000000000000000000001660598fcd3ab6a55423138ee72a4ca7b57277f6ce140f9f992dd9934bcda78513516df0d309a0e8ea151b2742dceddd0000000000000000000000000000000004cce7d84e0763fbbb54376833ddd7408afe3f741bc2b7e42fef3789a005134cc5540981a15a9f256e0e541ac58ff3b10000000000000000000000000000000019c20a0064f89d37548e06d63d8ff4fbf3584d5bcc2fc2757339b7c89db6d5da76d43b31da7364259187ed602e79bf4f976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f000000000000000000000000000000000d7ec5a27ac44daeebab7658011624c441e45924cce97d5bda354f1daf9362f5bce2ddf57151fa07f78740a7db170e8300000000000000000000000000000000121ee325f4252ae5cdd3e3495f36492d68d9dbe13249039d1185760e6e48a789744b2a9946a3d6478a64b378f76b0de300000000000000000000000000000000014c6c5b98c1e214f78b82f1b3be4c32c5013934b1231fec942b5591d3f0440bf63b1505cfbb7a8fa78a85ba58fd4aa90000000000000000000000000000000016aaea3bd0ae91b9d18ff89a40ae27b68d74f3a227383138ed737d59c19ac578da03df83f04c8d962cb9d6f84a15302f3aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c8300000000000000000000000000000000188e5aed425a768f89f5ce09b2cc909b28c6a0165787c8e3750fca8e8162128ecf62ef0ff853d206d23bc076335008e70000000000000000000000000000000001cfd330da0d1b5b92b6533cf5a8b6b70bd93daec4373f28d669f5e970a947fd813ab1d1272b61afbd2748922b87c8c300000000000000000000000000000000002aec750fd085c99c3b9c3af62b6deddd85e49eba0293e6e8160b26a3945af546a760b8f8f85120d6a51d22313cd33800000000000000000000000000000000162a109abce2edef753ca6351aaa9cecdeac20919681c672dbb183b5b26649e885ff081b9d3687f802dbe20fda43462af9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000f7ad6a1dd9f8cf52bef02ae1e82b0d20dcacfaa5c169a485bf8becec8b51373fae851ca29e64385f0b7024eb0bcf9270000000000000000000000000000000010412a7a710f842fe836414e2729d0ff2e145709d8f7b5e3964af3e0ae267ac53dac3db1e6d2b7f7671ec34b18c844a10000000000000000000000000000000002d3b96fab0e3b8fe44e316fcc5e35f06dab83f2c531a777e162f7521cdd5767ad0b6f877f876f73d2ff663d9b71f462000000000000000000000000000000000c09a98bf623e82a4d2d4b63fb867fab5d3bb1f85a0669c4c11cebaeb357c0717a0f246a9ce4064b7351dcf1e77cdbd393be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000114270d35ebff55c0341776086d893513595aca3b200ab98c8b586029b19a360a04f2e77e90d382174296443ab8531d10000000000000000000000000000000008b88849c3cda9a23d37ec9f4700904edb24be95fbbe6d9e20ced0d52208b597d44bb9269830a1ac5cda35d0c0a03c9e000000000000000000000000000000001144466b13427c10ad7679567067dc47c671107064fbb9bad287924c9bdee653c395dc2654caa5b3013ade932fddd5e50000000000000000000000000000000008e14e3cff3bb57f0d87680a0c09d745c7272bd3c216ff9fde7c03df2caffc27e0bfd9f99912855c156a787200752c125d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd30000000000000000000000000000000015cda76d42de9fa86f900a5180ff016155f31b9276c617ef664202848d2efd2876d412402516c0c3d26d49f71d894acf000000000000000000000000000000001307fa2b963fc19583b7e4ef2e9dddbe93e2505e8f4f00ec52db26ab411002136c1f646b1cda71e19480c767906a6d03000000000000000000000000000000000ba87b08173c841a2bfbe424584d4685c39bdd0f83f278f9fbafa8111102aa3acfad5aabbe032c7123631fb8b454255b0000000000000000000000000000000016c525c1dc247fdf34344168b7cc245579585fdbdd6fd783cbe60b727cd11ee97b87a86647f78dda207c98e65c2ee7e6854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b24000000000000000000000000000000000403887fd4429f44f8da7f17ca072f867e88ac046922ebe3e1e6c4f9d8e174399e7648aca924a557dbf7b29c540db33f000000000000000000000000000000000522324700fb6b2c43eb5b39e0da94cb60e234369543f530ea47f4aa510ec0fd79cdf4dd3ae046e21d78b9c0e35107900000000000000000000000000000000015e946b90984257ffe3814dcc3ef065fed1504f0790f3564c8bfad4e97cffdb61c0d73bb0b1dbe78c4266c773abd56b500000000000000000000000000000000078f604630074ebedbd836c463f3879cd5d4a2c947da0e47740ec369112f4fedd787ae59bea69aab61b91f05d92061036f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000f722bfebd55f75f3bbd0a55492499c3a3f637ead0e54270042fcc88853df5bc5f11a3677efa26d31c28368e00c8713700000000000000000000000000000000182618bc8a4b3f6556d79848f90efd6883df90806a8358cb6852bde465a27a70644ac5d5040d4f64ec355763f1a384990000000000000000000000000000000015f717739a1cbb2eab30e7b1bd9b25f57ad56f36016b59128ea1f2089f2d1dd0128b455b1b0e9e3b320f68a38a1bdfac000000000000000000000000000000000b855788d6b6a7748aa923dea3163fe525a7b43f4619c1eff3f9219ec3d98ceaf34b97bfd19aa6f91f7fcff728728978191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e200000000000000000000000000000000120048ace47bc1ab3fdc07713b91a9223fe0fffdcbeaabc8a61351d756f936e18177f672c5a4db7b9dc29bad16bb7c4c00000000000000000000000000000000101275492a6e843306f2927b6ab540d7a5ee925bdab40103b4ddd885e444e6a6ec2d6e99c061284a1967797d8a2e9e700000000000000000000000000000000002c12f17a5dd2c56aed0d308367f37510f83c94a4482e5f632161dd0517dc2d4f46a90bbc13034c63dbd04fe4c616e320000000000000000000000000000000000e4b9089155ce2178f26b058f4bfef57b73aafb83b0b78138a01890a167709f79100a1e4d797c5849473eb3486cffa4b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000018180eee7e72b6a4bc2e60555236da335fe05fcbe2b3cca4937e73a550aeae6274122ba84ace78eff84d323b4196f58400000000000000000000000000000000147659347e0fac7a16c92950ea5fd115416072f339d7de3cc0f00ef369f5122ff050d8515effacc825c807f7e19650e10000000000000000000000000000000017bdbcae7f63052af9a7d8bd71dc98b6eca7ecf5eee7632959fe56ed51278099690c534ec33be4ace4612b0f516794aa000000000000000000000000000000000d6fa233be4d6d783bf973cca3740cbaf0f719827d7f9310f38d1dd9d1c1f125cdfca6d12fbf6a8e8104f79bf30b00647d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000082f3beaafa575e86be53b4fe7b93835b00759f921933402282e5bb0e643a812e0e4b676ad51ff2c6f5332d777641cc3000000000000000000000000000000001760b87bc4d2c13122fd7acc6d629c9f9db9bc9a2c49634aaf33e258ceb3106bc2755b227c6660a1df1d92c60067cf5a0000000000000000000000000000000016a819d7109c9a12199eb98537a730908a693767cdb35a69b4c7329761939afea766f0b91ae405e273227330761a53dc0000000000000000000000000000000009d14d7138440349e83f5ded46d18b886ef3cd63e0e5bfa0a8b50985142b21a4733813ec347e40cabe28e6ec1e068c24a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000001676d7f489219b56c198f8494e156fc0672ae28dab20021b7a6018436c7c0f107efd2493ddc2a1cfb3ad490ef146348300000000000000000000000000000000001106e89fc098ce7bd8bead5d7f6432bc54501370ae6544f34cfd996b3b610f9cfc7ad366751ae1211b848aad7d93d30000000000000000000000000000000011f8f0bd037365b5427e76d57b018c1c644034b28d06c8f68c59bff45eb4a2c4d761d066d96c13f7e73dfd80c81704a0000000000000000000000000000000000fc826b5957613f35bfa36d3ce088dfbbd06c8f2e88056a22a9f35db561e06fa0378ccff29ba8b81cc12c7a504f8c704c0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc0000000000000000000000000000000014f0f64acb0d9638a68278099abf5b5da3aa087792bef15192cfe3689b69b7ec1aefbfa14e659358b5410d98d2eedac50000000000000000000000000000000015ca79c92e98cf8314a2f6319520e1eb7d4656ca6e51278710cefd9c768a25691fc58e983aaf858d3c8d0ed73e2beec300000000000000000000000000000000007a5192f1dc906693568291f163e9632c53e1f418a87cd25656064adffbf31863680468f3ea451fbd22ac990dc870b3000000000000000000000000000000000131d2e3f6956da8941e8340259b8a15aee9fc6f23573f9a348ee9a51bbca1308dc54e7b4675357e3a9c5971be3a5c16af833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94,00000000000000000000000000000000163da4bf7e159e05eec90318a8ddad4a59fb02d7ae2fe18795d38c3ccaf797188fa16577e6a421ccfb12ba1ed573c4e6000000000000000000000000000000001256654eef3352b09e0027277aec042519d99eb2567fce2cfa50a0c251c12d3593604739128171bfc13b3bfd1ce8f9e8000000000000000000000000000000000b8a46123bc863bed525f97166bcb77504eeeb66d2db207eb8342a3d18f7f5a99910fae3e6423c6e84e437a2c4b24363000000000000000000000000000000000b73cf08023c8572f48c132add67dda7a15def638a01b198361b9d21a4634ba76ceed9819b37c12e24f148d255483856 +0000000000000000000000000000000003113ba8b216d933fe0c81f23f75942c0065d21d8f009d73b1f698281408874e33dd2750fd4298367b81827cf6fdad34000000000000000000000000000000000b8a921e840fc665a786d826f83ca5a9c8f00e7c802bce5473a7d1ebf63e8bb6cf5c4b426153508d874064d1f1dade09000000000000000000000000000000001492ab584088d23d3b0d1283904f9a8f29f9efe47950c6e9ffb9db2123f3f9820b906d672fc7f97f0bd38b8fc0ef44520000000000000000000000000000000010d321c2538f92aad4631af44ae39e63dc06becd2460f0cee0e526328d167fd6cfbcf4edfaafe32d13b5fe66c009533bb16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000ae75d01481a51294003041afc4802326ab878a3a75eafcda43cf873cc65e300d28aa986fb82a2d1d649e5be00f956820000000000000000000000000000000017640eeef8982250f88a4d187dcabfcc9adc3ee9194dbc3c04c741690fce5bc7cb07cd0b7c3497191d9ed8558fd0d24c0000000000000000000000000000000007527fd8dacb81b8d1abc746688db6a47211fa71556155d38361921c4bdb2a9e9921a3a540bcf55c6dd751b84c04a1040000000000000000000000000000000008de9109ba354d7426a5313d66cd747a54df347f0f86a3c0f99e9e4b68fc79641fcf98ab39fa23ef6f1a781c48f53f76cf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000017331b8367f07756e789f7edce4d22f6886656fed78ddacef6987a2751dd3d5d49011a050e7b2a3e11fc8d90266c9d710000000000000000000000000000000016959c303e11f23392f95c1402d1d1ad7f38343c711e96f18d03f832f76e3e81de789a6eaff797ae51079b13571334d40000000000000000000000000000000004266fd13db1ca80196a91263c79d1583b717fb61fd9ce5113e4cd94c59e605152b244e10e364b468c5a561c6fa9715800000000000000000000000000000000026f67cb263be83f3163856f091e9346651c29d4634e242da53b22eb6e66018d235b0f30f8833310dff9f3020e5bd3811cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f42129000000000000000000000000000000000114d8babd11c81ca2b8a7e193afbe0a8fce426b83996bae6f77201870e51c9355c319dd86b985272f73e0804c0f53700000000000000000000000000000000016f5ea7610891d0e72975816c08e6e25a75c7c42500655f26efdfb384241bbc825358a21caff347d00c8b2391501d15400000000000000000000000000000000199c8c74a79ee90c3606906bbb8cc163c214259e4d0127cee3283bcd9c1ebe4090ca7d7b180201910d3f6f51566d3bdc00000000000000000000000000000000032c785165ad4c1a2846e15318bd7cf5b42ce8b675cb18fcc4232e28701f225f1ea384b276e7a38b2c9e2e8b112f1911d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf0000000000000000000000000000000000842c65006caed9b53add048a2eea89e1b4584e1deb4365e3dcf8b9ecb02f337bccbe5d6929ef8c20461847f171fd4d600000000000000000000000000000000100dc23e6c1c6f6756419a9bad3133bba052f408a424c5239b8528ad4429a2bce64b72f1463625f7599ce43865581e9600000000000000000000000000000000125b4d71333274a16e52829ad5eaaecdda5c206063473dedec5a8ff4424def70e6f650926948dd2158b403f985a3421b0000000000000000000000000000000006a031e3c002702837e4ad28250b85cd94d42cf7b0d765b980fab95edded7636d13bdef1be63e66682c4e297d0cb2b0302e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000003f7091a25da7d5afe6fa6b254604a1abe7a0c6ea11cc1a4167f5f648aa973d888383bc7e987b620d23e688868d318360000000000000000000000000000000016637f888efc3e057227cbefecb3037aebf8e330c3a794e51d691e3bc064237b98351beb746868aef977a83d1fe163ac00000000000000000000000000000000126d2884487984f851d1bd7d61bdb803321f263918e88e0677831563bacc9f5207358d1e9c76a5a25a66f0294f459e3900000000000000000000000000000000125c61b387a4462fa3bb2f06a4cfbd7df082d20cb23ee974aece2ec9a3b0c084d13a7ea83725a05d9f31b8033d2888ef9b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e0000000000000000000000000000000006cf2bc7c691c4f8a64d0aa1ca3760d715b3188a2dd299ab09c723315acba8b0b4bbee819ba06cc564f0c875a63a415b000000000000000000000000000000000bded3d695e471f30f9d723f55826eda112eb0e3fbfb9a377cfa07d6233ed84108b92a79bb491a2971e9afdf83db8e9a0000000000000000000000000000000009b0e9928cb267508d4f9444c6ac3dc6f64f49a70c82c0bcaf4022e97854e5d9ec2612a2cd4d67642dc0451583bcb24d00000000000000000000000000000000009347dcfebe93a2f7674ad02ac48794e7cbffb04dd85b0c8c192fc85cfb9cef40fd11def6f63ae9a923960424eac6a02f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa040000000000000000000000000000000013e8215c7bbdca445555c9fa0ae44e1905703334bade3294fc047ec262b9e4903880d52851967339eeadd666200b25ae0000000000000000000000000000000003b0bf4498103ac03601a8594b154b59a2a93d663f98ff8dbd2c85a1902e572a9456c629a12651aa87a1262102e1c770000000000000000000000000000000000e8bfd7d3fa0f773e6bcfd0d43a5c436862d1cb6a4ed715093c6782cd94699090c4bde597f65768e963fd0f8644e09b300000000000000000000000000000000064dab4d0d0c6b94c58b067337f2fac7d0d922cc822562b6bc941a794d96aac5ddb83d1d5844440d21d0a72a69303b8b2d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000098dd9a20f84fc26e78993a9de4d519aa2f8d343fbee501af945e5943e88425d29beb7ae54481b04175a07bf69b260a30000000000000000000000000000000007ef43e7a56e4e7d532420e152ce566d9055eadb4ef13d5698c49da905a4977fa8a7d3f51c8f5275582e1647984be61e0000000000000000000000000000000003755ee4432ea90f2197c7cc2e191dbbf7950c52a2c1b723f26d2aaf7a38c1b97efa29a312fed599f1199cf186400adc00000000000000000000000000000000150edc463f0a55fc70c2ffdd1f73a3abbdae459eb16adf79e96d18849ca638e6f41c6805b73755968be5cb110d81faa4efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d8000000000000000000000000000000000f7149602cbb3e5f2c5f8edfe59fc0fb8e1f03f89ea192bfe3990d87ccd28d4a80d7cd3003a8cfd669e1b6ff7e3cc5890000000000000000000000000000000006ffbc965bd06de07d8c0a9db8db5ab82d5f11afa1ad8eb92ed4453489f5899cc8c46ff02743956bed81229f64cf6efc00000000000000000000000000000000164cd3271ace4809eadeb1c0f769094272f3b66968690339bdb5da92e920cdc80c9d577ae4fa5b6426a5a6f46fba80bd00000000000000000000000000000000098f0a14a511ff424847d2b4d1b80a049b1f05ecd40af96b7a81def54486e4969011c122ca7dca3444029daeae2ecfc79848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000018409c0d0f37f4932cca87e24eb4d55e75dc98f938420ce036d43689fbdbbd839dc608b21d12a8af1d0a780aeac6617400000000000000000000000000000000109f2294669422a4946f926b1f106c2887893a042e3bf900559429c7fa484da4909216c8dcf826871534981021256741000000000000000000000000000000000a1ded19846e603b958d0bdcc9b554beca784b017d2a35ba117890fd0dbf729428bcd9823c7a378706220377c82a215c0000000000000000000000000000000000eafc89e30e4fc0544497e27674ec5b37ec0849fb382e608e09d0c1c94cb78bcb96ef4ea48e374aad1038881706fbcfcbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000e02cb3e099792ae7508321ce7afa323fd499de90c4006621ef5ce1054d0c934ae058a97ff8aeae0c88709c4d8ed0adf000000000000000000000000000000000e19318f5890320f17d5243adb4683a97e3e9763102c4fc93e3c3e3d24f4f61e0500be916c249dab00094b4ab048fe99000000000000000000000000000000000989faafcf6156472368b282313e076613cfe7ff135eb131b49e58932cbfafecf6585009d1f17ff8941d7f871be23e9e000000000000000000000000000000001167419d097ae8b96993b2e67da79b658adde1e12e43c71f27835845c7077f385612158d3e59fe2cb32b9418463e672679eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d63000000000000000000000000000000001579b7d03d3d2c8a280e8ca113bcc98afa6a2705a5d228d92807a85cd5a1ee97510f632293a478c3fe0bd383f4b69cdd00000000000000000000000000000000107cc2e6bd02251bfd565b4b848adaa84babe9d4f083e827ceae6bacd9c9c221f0dbbef53278175bf27ebfe5949fcf8c00000000000000000000000000000000018d187c566690e4edd8d8abe5e0a448e352f622c96680378051228b6d081a4914aa51383326aedf45e351612ad6c5d000000000000000000000000000000000197427117a52f82aa6e931ecb0c5ffeec7f73ee8f44c5816935d26c06cc8285200ff9240d98cc244708e00669460f98b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000019bd947df5a437a7f1ca2340bec628f2783cc1760dbc4a97ae10093aedd9f64e25ba79d9f4ce678f4fec91a3b1eef2d7000000000000000000000000000000000770e0c39988c9d8eca076464a3e10e274b06b1d2f6230e6dbd8dd59dd9c062f8958c6870c44ff196341bb9f65b8db38000000000000000000000000000000000a1833ef19e2b8e31577e5cd26e0a7fa46a5d25355d8b3dc0605f53714a60423556f3bcf17649745695f68f26570de0b000000000000000000000000000000000f449aed4120f3bef05506f2463f4546c7ea67b9e9110d3942dc256400d063dcc571305b1d4cd2bc3f18cf25319286e8eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000c1f9688ea64165f894e85b21761a9b2bfce891070103119ae71ff7acd164a57b0e054319631180c22f19eab8607f5b40000000000000000000000000000000005ba18dafcd3552af464acd469b133896e90c9ccd7e3bfc6e05db883f3c6aa1cc4610ec47f6354f6a7cff4385c56d2b3000000000000000000000000000000000fefbd9d78f48683b378d2d6311bf7ffaccaf7aa73a0bb4ce019a0c1d2e1673e52c724bf3a782729ec23d258043efff5000000000000000000000000000000000ea47ebbe3e858c5fcbf5b0cc9017d6ea23bda36e235d2aecbec827fdd2e4b042d1108d5f645b6dcdd786304e6bbf81b52b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000a313e1bf72d9a176bbad609631192c779e94c293463507edcd1c38bee8f33cfe6104d7169457ad5ffd9f045fce1cadf000000000000000000000000000000000af8db18938c51742b351fffddd74bf1137092ecb50a7e749391bacc9c1a19c7b9cf235b52ed577e7855d4ec1fadd940000000000000000000000000000000000febaa128de79274ef11d3e6378809d5b319796c653604723693c335eda175014b645604271429e3d449e756c85bcf6f0000000000000000000000000000000006adb29cc4ba053fea56d07225d2f7735651c0046f5cbe4a350dcc20431ed9457651d46a5d23d946959cadfc5500b7eae83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384,0000000000000000000000000000000019a5b588aff8853adcfa959afc5135807d00196a75acb3536ad4fc9a9df3803d919a2de7cbe9ff762913225577ebdbf6000000000000000000000000000000000ac8bde939ba2f164795804d96dfa8d3a1c4d9e4eafb000cfccd956c24f4d594b30bbf961917f625c86270cbe164cc5b0000000000000000000000000000000002de09fdf52aec0b91bbe99fe2eb9043b19975c6fd503815264ce030dd5e5444f0f4275ac9a07a49de775335d52ea3c40000000000000000000000000000000012457bb55876c482e5b907c765b476dfe6ebfe8e588cb7f630e58f78942bfca57e6c0d5d7b0ce80e48960e297863d212 +0000000000000000000000000000000008f3f1f04fb80a23d348e3e25dac1d732265fd4a71ab8dad3718d268e49c79578e8e1ad1720e70357439e57df0791d64000000000000000000000000000000000fa4c15c76e395fa706a55d1909ede2163274a68b3e7afb8d2e0bb176f60c06f5a921c9ace35bd311bd79ae86340ba5000000000000000000000000000000000173633369e00c8c5528bd5ccf95c6af8b012e5a31941c134ad4541099c7c33c5ffd29a5a31e18be720f7ae85132cd6cf000000000000000000000000000000000800f5eaf7c8b1dd2787305ecc637a0bba8eac807a7b449410e48aed3dae2b4645b8459fcdd477fd92fa5ac6291b800ea4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b000000000000000000000000000000000ea8057b2d609ac2130b21e0b4a41f0aca20ee7751f55d816ea42cfa4612b67c3c556b01b0bb1c5912a74c50a420407f0000000000000000000000000000000007fbccf8ce8d1a92756fe80b15c7d9342af4e166d3c1c7e35ea2fac34851cfd983633270c877224749365720fbcea54a0000000000000000000000000000000000885e173b73118721d28fd26f3a9c562bfbb878ce71091d7ae4b37c1f2625777d67955a2b7458af71077db7557171f2000000000000000000000000000000001754edbfc3f2af94c92e6754d6bb096bbc4b39bb1128dc6bba8b4d4d9fac6649598be90b06b9d5db44c4e77c0cd1537cbd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000008a1a751b5f9a08e2bf5b2a58f62f0af6b8773f88e50f658ed220c0134e83c7031a288eb50a8a35016d2362b431d809d000000000000000000000000000000000d7f04d4a6c36cb3d105dc3915cd5d57f56692132681b3abca4b00e078c700931848e34ea1b7ec663f3886ff766fef41000000000000000000000000000000000a06c3ac81d6d0466e1ef21115150d04c8bd6dc3e4078e46eab213203c3226bb0c6500ae4fda591d6b8a791de598edb90000000000000000000000000000000014d849ddba2fa79b6a7107efeb46e9b6231d65384c69ed183acfb922d55b790d4fc7546afadc190b76f7da00103ef565efbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000014dfcb5fdb38cf09c1ecb284dd4f2de0c3d70f90d7c167a442d84e9a29bf43be62cd319b2dafdb6ead2c6596443a00090000000000000000000000000000000006220fc05c53f48e7e4104422b0660ab67fd88a695a201366de570f0ac0ad30421d5e37a1575e6b5ba35f45b441b297200000000000000000000000000000000077cb8ec1cb83c4974f6452ce0de630afc82e283eeb55d3b7e9969bb44bcf0404deae617393f82ac228b836c3cb6f95a000000000000000000000000000000000e2bdf539eb45a125112836008effd104e881aca397457004fbda4a40d152817801bd259434481f0509ab1838cdd1fd060d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000a09843630131cc6feeeee8aa8214408235655e4733badd6fe20c5cf1e45f6a61a5216e0cde937799437962706d3bfe2000000000000000000000000000000000ff518501614ed4a199ca9e9aad4e8efb8e9cffa9b4fa683093a49cef4669198a7893db998d5777f2cc8f4bb130c84360000000000000000000000000000000010ea66fb5224f4508ec100cdb611be133c4895a8de1b4c475b097494ff0f1ecdc1bf8fe467c630233cac2ddc07935fcf0000000000000000000000000000000009d22c0a45c82b0a19beb94eda0b93cbbe1f2e5f2d61279e1e1c93ba073cb766f5637195e6964a4814e588e44bb03f03386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee80000000000000000000000000000000015ca795fc7f0d169ba8abdafb1dee80b67e7dc616e824959f84c61284d6b2e0e8b9f99b414f5bd96d0e59b66ee706fd800000000000000000000000000000000042f473d1fa228961aad526efd003461935954abaae347dd6c9bc7fcd68b5f5138e57ab2a160cb19d1983089b58b51ab00000000000000000000000000000000188eb160cb968b4b048ce14bb72be27c228df1a6c014fa7dbec09a30aed8c71e8da59d3d5f8073b6a7d70d94c0e59dda000000000000000000000000000000000d467e6b05f033f3923667a82d5b489a5c90c99c5f68078aec700fc67a83d9bb4c09f3f00b9fc2cfd62bb098f885fe295ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d0000000000000000000000000000000003bec45d94f3073b2ca54d6332d36fdb8f5c801d9f70ccf6e3666b66ee06c0fdfd741f74cde1997aa205fb0318c9c4760000000000000000000000000000000014009b777b660264eedb35ec2e13ea586aa9438c47b3fbfd095ea3d8688a89c85bb4052bbd3edd450c19acea6372d0070000000000000000000000000000000017f26d3cfcb40fd6b4f3f1acb6d47a9b54c232aee484c7a8992a3d1accea794dc384fccefb0418d43e1fa7b399bdacaa00000000000000000000000000000000153c6cafbff3c53114c96d8caeee2880dc063d7db5edf5f14157117387f368c76b739553542bf6a9bc4ace3694de885a92837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c0000000000000000000000000000000005d2481438c03493efc9f1e8e9ae6ab05b7430f7fb82e108aada0e886b14d769969d54b17b31e5bbb63d40836748f541000000000000000000000000000000000971deac599b2161a4baf1178feb81fd4798ad5cb063b1a0cbee7cc33b8fcec6c3f43d1d46d9ed45555187db636af99e000000000000000000000000000000000222acaf8df647744859e04104a5fcd546949feff6244e192a9031fc838f368aa465a3799779c637ef0087183f30731d000000000000000000000000000000000b8e8f1889816f89401b070db687aae47f7264c9be192a8d6e485ee71a5a688070d57ad8928d09d9a4925f1050e2c69e127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000b8a5b0dd422469a8d6d7603e9f3179f443ef3fab0016afd94e93e2ea9e84b332da4b59f23a5257b99460efdf7d2aca7000000000000000000000000000000000c28e7068769c3a79bb8d92c3b89eca5d6eb42e3e18c2a7154f43a671f8670f878c4b110990c2e2b163ba4d1155319fe0000000000000000000000000000000001804302246fd07d86f4bb23f610af38deba8e324cdedbe5e61cf0941281cda8fb5dc211fbc0ce6fddf30aefa9563a0500000000000000000000000000000000015813fe0d6bbcfdc8e7e40b6141db21e1b490d846ffe82eeb3edcd9a024315193259612155b0179a4971e205738af74ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac614890000000000000000000000000000000011ac1ea4dad0f650fe0844ac3ab9434ebac6eb70a5f77c8f9c892cb4cb06639a15c63a9b820ef8f7a720040ae5b9e49500000000000000000000000000000000117da7999552e7886a25a939ada0944cdb15b5c468e9d1c3bf5b6af920e470bd648d24f3cb7f91e670f57a52cd65f7b3000000000000000000000000000000000a24147ef5f2b8ad888899c1db8df0a601eca9d76f1b934b1627e7eef3efe542f51205b96b5c00916688579ece81336900000000000000000000000000000000151863d964b12287ae4278c905341124985410f1ad6a72bd5c62230b7d8b0cddbea0c62cb2a7147afb5bfb03348be53363da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f0000000000000000000000000000000001d32ea5faa6303c530790146df7cd5cdee93c0933b4cbc1c2b8030bf0a8d2600dba1907df1756152625cfccf8cc7fa90000000000000000000000000000000017b05f549751d090f42ce8a3ac5d959cf988ecdc485f51734d52c40a3e22a097917345978209fa74a0a05be0a66e5c6d000000000000000000000000000000001481fab7750380626b174602d9fcbc97555c516f4410193d2849443cf25ec22840e4fd00b225f98d81b38619e8844ce90000000000000000000000000000000001d56434066551c5bfbaf8c9007874abe57a6f78de9355a297bc117f2bc5e6e3f44b248037f400f7caf83fece0c00ba0ef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e000000000000000000000000000000001667f1400973598ad3f56c2e49dcb5b556cc38ee3e5801ac4943f3c4554205d8fa69831e582a084aae1ef584feb0a1880000000000000000000000000000000003f0bb26ea548e498f05a5bbda8b8e536613f10e7165607ab77565b193f794664c8ab0a5ae2368d7483b77bc1173d14500000000000000000000000000000000176d8d294b4d975629c6a89bd6d45f9c3924a621259ab43d33a3d5aa1f423b68e3cef96dc103494bbb9036436c170f5600000000000000000000000000000000002f8ed87c584e69de59cdde02b6de9816c31a6efbebafb6ad9cecaf266f5bb9c8880f062dbc9235c91c668bae5051f4bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f00000000000000000000000000000000085fee95b859c52e44fcb2900a9aa590b1a5c2f981a388d6ad7b81ffbfe033f648c4a84e2119cb0484e178ebd3e220d100000000000000000000000000000000171e6ca074aa97981d2c2ab000a8bd12cbd5f5d574cb83158a6ed734e8f9b7aa4b74aaa43b7aae31b3f4fd3d82fd30ea00000000000000000000000000000000004fe6099a52fb491a0624a8d787d95617f6c64d16d20d1b3769f60d4721f7af66d7e3e905b3e08b2946ef7bff4806ed0000000000000000000000000000000004d3d1a56af91377ae6b00e192ad64fce6dd43a37592fa8706c9344b3d96b1f930e03be85a5ead3007f9016255d2df7570363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a00000000000000000000000000000000155830eff04ec2f4dfca4f73403e408a68830bc031555433fd38ab3ce1035b5f882bcd6032aba69ecc43625546b4a3a8000000000000000000000000000000000ed5b698b1ae23769cf5b6dc2e39f8500fd8a881eb43452d67c6b84ef9f0b3c7d81db1909b646e92412acc7365923a940000000000000000000000000000000009f28ec2f949cddee9bbe2fac12c2c029f4e472afa1ea56d0edfeacdeb9f43a4a43b79ccdfbe8957b4cc16bbcac1857d000000000000000000000000000000001474b435131301db9e232ddf54569ba99bc476200ceefc15e4aaaf1a574c1de8bd2d63c8280e23127a7a036acae223b1997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e8800000000000000000000000000000000168475854829d47356d9a8dc13a94e8d169771ea0070d9ef45e666d5378dd676d603c2eb57a3cda072c11e0926b02d650000000000000000000000000000000008b493a9f4c19831341782fe6285db2f7e8250d72952351ddcfcae6f22a2ec0935e29d396ba32f72dfa4067d0e7ce7cb000000000000000000000000000000000d9e72e22f2a1522babc5f2e8dc7857ee690f60f7843ffe15a080d56bf63db86f124cac039cbfa16fc8ace4d6268a1180000000000000000000000000000000008f3db1f6c0e5e7b3bb27abd34bd877cc3c373c681a3abc88eaa91636924ee477ba5032801dda091dbc51936a90c84685ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000004e34bff7e9e3ede02df950aa0e8c5f4c5f85cd3be89d211e957a7de95b8e321cc11400c3dd5b2ba0d1a3008462cebe7000000000000000000000000000000000fc1047097f01fd2079e6357ed379ba39107ec41ed6c6dc17fa6248d52be2b1cc2593c9735a6cb48e6d6e0434028f755000000000000000000000000000000001896fc5e990aeb416cf21ccc73f02c41d019d0a2679bd533d0811b7c16ad3ad3a6988170fb2db030b5fa7c3e4df5acf4000000000000000000000000000000000b70e14ce1b54d7913b9f3782b2b8ff249967a6b871dfac7f54f959954febb2783cf20e20d1710e5526ef8aeafecb3d603c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26,0000000000000000000000000000000008056d4dfcb593c10a877cc8a4accbf58f360256b76876ed2b33a07be3110f8e295ef459dd6fb10d12bd02a8276351f50000000000000000000000000000000005686da1a0da89074c6b13fe9913f5cd49e0ecfea46e06493510625f1393ba4cc2e13f023fbc7ec2e130bf9a4f7483ef0000000000000000000000000000000010cd660001f65876db5b2cb1a56d85171d4cbf037f3bfb0e01bf4430c479237cde5b6cce5839a4fb22b406846e757868000000000000000000000000000000000809d7711211d37df76cd1cf71001cbf02c76df67c83e4eccea3e05b11d196b5d52ad7c3d0a00d9f0ef5b018717fc3eb +0000000000000000000000000000000005d6cf50e3add0e5ac3016b394ec363d6145ed66ef56b07bcd33c90600e83b4277558695222062e02d1e2b0693858e73000000000000000000000000000000000de8caaa810d4ac39258e3d1656bf7f2fb7853a5963ecb989346abe90d5d35d3662f6e283cec7bc386a6a8638ac395ed000000000000000000000000000000001849ef86eec16b0612f214c5ed52c0d50a90bd65b623402879f2654fc578ab680d49af9afdeff546702304597a20f1fc00000000000000000000000000000000168707730c4e74eaa4e85e48e7239b9ba3e8cb74c24b7126a685da0fcc963b9f9180e252adf7d8c521deb1a2ce0099582849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd0000000000000000000000000000000007d4fed2fbd9e9dd19e0af5c52637b2cd337e0bfbcef0384f182a56189a7e7304b9d2144266ffa79044be90cb7ede1b6000000000000000000000000000000000baabe8c23a10cfe85494c693d1b09fc8e43ef5f233052d5b6294dae14b4ff9e5ec240a1c00a16a9ddc27cf7b53bcc7c0000000000000000000000000000000001c595f193229da9acff04ef67ca444b0cec75db5b2c1921502e37eebdd2bb43ef47290fc6f1980abc75ef4c50034df00000000000000000000000000000000010fe7f3110ed3a240366ad7ba31d56ab993468dae2dc1b667a46c7759baa37b865d02834e14280a2ccc062af5bb2b7d6e6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba69900000000000000000000000000000000150350d8a771c79268606d6a5e1c147dc9d92e63fdc60b20be688bd52eac697aa5d90fe1b7b91321b2af87c47ac0d5060000000000000000000000000000000000fba8f4da448b8f2bbd99014bee2f9c581f2a974bb0b54f41a84a7fb359e9dbf88ba59a705504140284d486241e94e80000000000000000000000000000000003bb92d6a603bd93f8e987071a7385de68d10cfbde389eaf01ba6480caf1ad8aea03c84d1889b7d5b5c5f72e62a2d75a00000000000000000000000000000000193342a9f15109367030724946342e564507b26971caf75190e0b209e429a948d8b21ca16041a01010b68222db66a16b8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b000000000000000000000000000000000bc4a46eea57231cc64758560e3032a8ad8f1907b3cebd7a8faeb98c4216cb8a0c8fee09929ecefc4bee7955f4e799ba0000000000000000000000000000000009f9486257ae3f94a2ca48eb203e2ef44ecf866ddec7824e1a4bb3b89b320c38b3c46de8202480628c53c415433383a3000000000000000000000000000000000d8e2b5d0825b11344d16dbb2cc614c6b84eb1cb43f70d70e272123867b731775b429aacde611318b2700aa567a84c7a0000000000000000000000000000000007f720929287a70873e9f2f2031b66693eaa6e604668219aa5aff3f50e720b34c5fa3f5c66eced5c3e86e8b34a81b984c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e4000000000000000000000000000000001537ed68e203e56f31498efa314322694ebd74cd1dcc3145d534299fbdadd4256f20b9f74b895931a60753bde6ff9030000000000000000000000000000000000935c6ae847aa7f47bf427988665e5e18a32aa869e196cf9d5bac1349c650219a8d20e01bd8d49bc7e4bb8d464aee84300000000000000000000000000000000013e0661d7254428861cc3ed47c3fc9daae8b86db35d1c64f8ced3bc18a89202825f13163ff94ac0ebf046a0a99727e200000000000000000000000000000000039a6b0b2cb91e460d50eaf9600c29fd4f82a81c283ba4fbd9a7d103efdaeb1e82947f5cc1a7a1112ae6344c51119201650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd500000000000000000000000000000000071a6b0267806f2b9e0ba493960fe0e43f135c739a54c8daf5ef9ee348a281f19876f80c0dcea59dfe9457b49809c12a0000000000000000000000000000000009ac83690c30a4afd78f94b2493674668da4efc84007d2a08fc78bba271ed1f43e2a9e5909149bf0811c44dbe07c52f9000000000000000000000000000000000f5d523612fdb2e7dcf5da56720057dff6b0b80707cf5924d146c0c072edc0635c73fb04256e06c7c9355cfe77a7af0700000000000000000000000000000000168431fc569869ebba9b4a72371e3df232545b5fb95778baf3d9105930d9a89b4cb7eba430e9162a5589c7465e54ca3ac067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ce836522b983fe3ef6a502a0de4c599fad8a36a60d914218d5d2cc4d56d69eed8d27b2d50899639d1a0ea9dc7597f900000000000000000000000000000000014110ac048ac4c20e53f2214df8c06d77f0b3150077d027691cacd3715d4630a387d5819ef58eb1bce2e8669be330a3100000000000000000000000000000000178e5cb42f56df2f1b255a028a00df96c02eab0a79aa0ff3e9772fbe3eb62174728259b3a15e356e6d9666eb65fd6b7e00000000000000000000000000000000045197f136649b61d6e0e7b9a56674e769e2d26716ee7a63fd2b83b767a9ae96694e9cf81375d0377a1b27ea6dffaebbb448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd0000000000000000000000000000000016a6a58301c243b0c59d6934bd926d6440b87b49f004f411ab0fdd924480175052f63f594c18007359055dc776e7f2d300000000000000000000000000000000176db4845cad46a13d9dd0f4077cd22b3458f64084c7325e9885f8ca341ce3ccd4f634f41efd6a70f16e1f0c9ae103a900000000000000000000000000000000068ba68f652c4f072a64d56618f93a1e148274b1b835433be878c06e11f65ff45b7cba0f67fbe80327abace68396da7e00000000000000000000000000000000047a699487964c98453207c98cc91c980c1ed37dc26e17748e6ee88e5f4c0ce424d87c82ca6db2264dc8aa9e437a5f25441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee35000000000000000000000000000000000bf7e4a9751d4e3baa7ce9906f4378764e5384136944f6d3f3074dce66ed017759783c64fc381f0dd7512d6f6e55b4aa00000000000000000000000000000000006ae2a4fda156818cb5ea6120edf7ea39370eeecc3f306890f47a6dcfaffccbb69fd21f33fe491b7065838b277ad2b2000000000000000000000000000000000d3ce00c2f5febfeb232dbbb74fb0405bab86474d1d9c545c93b65c7892bdd58aa56225641074ec9b428efd9063085d00000000000000000000000000000000002552a8c1848fbefd6b039d6c4bd47c34dc34ab307163c4f6d337946f1d1b41aff2f7e37f5fd94012f0ebd21f97d18a83020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c00000000000000000000000000000000105320cccd67b6ea78e96e66425a10a6911d2d348fac3231af583146273609fcd7fd27a19d4614fbdf05bcca0f92b927000000000000000000000000000000001204229ee1f66fb5a5dcc4ee978327e35d703ea310901be9c100af824e39d24a028ef8fce42370e5d734df02a26c145e000000000000000000000000000000000dd21f31f116681c1810bc36141cc18096cc113faee7db2c189abb7a746e398e272fa0cc61286aea0a5ec4008c8d03b60000000000000000000000000000000007911297718e98588844b9022c825bc4b37f2af30e1fc2d9cfb58b4500dffc8e9949afddd051e971fe78d4e1e7ad1b4a82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000760787190048e6ec8bc3bfc368f010e2f8aadd53164693a62b0d7207575bb2597bcec4bb382c57fe9053e90fe2f7159000000000000000000000000000000000ec525abbf13da64a8093c5d3fb800440f4c1fe798bcc71eb97bf2e0aa9e8be4b08afd2313f9143260058132d2607141000000000000000000000000000000000aa12c902084eb843daf7b351989bbab7a86acb62eb54eff0c7599bacaf44653c9fbf53f47f6ca72d22ea1671842eca800000000000000000000000000000000082f330d9a693f2bb9386fe5274aa79ac73a17688821f3c705120fb2aa76903627786a8614053f21a93e0aeb555de64e338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b0000000000000000000000000000000002ce7f08b8d5052d8bd07090744ca067700eaa1db61dad3e5086661850337bcab485c15fdd36c309a9e5169fd2a2b55e00000000000000000000000000000000073fa834cb4dc4ae120e738059749bfbd86b9e64fd71b1d372dcec8474f3341137ce8cb97a38955e9081f9bd5e07ab830000000000000000000000000000000001568df6806d8c3cfc9231802ebe5edc5d505198747a0adc24d0ac59f28d32b7b379d1f2c6b8352389057c7465692ded0000000000000000000000000000000004fb4b08a4fbfe197e924be3f7213a769a2bcd24109ae69a32a197b6212c5f50dbe8f46f5ab6044a4c779cd3e09d13bdd29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee75021000000000000000000000000000000000c07ff656904a47b0c7bf77540abc47cc6eee3e76b6ff0983151de9468ce3a860c427f3d5d489d096264159ab0567cd20000000000000000000000000000000008cce094ae1d9fff246a0e76cd67dbf9808c94554372fc4aed4879487ef240e45047dc201dd8bbccb613feb9c4623a0b0000000000000000000000000000000008a25297940a1bca1267fdce450b0cf43105eb4a21ab14562116039bc8379b1a3f58a7c117e9ba735bdec40f772465300000000000000000000000000000000000ae17a9b1fc3b0b7803ef48cb26643e8e78ef133f94bff5f87739182e662e2641e72383efab1f3ec58fa20fc816d56c675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb250000000000000000000000000000000002357e5a04b0dbd7f9a1709bce9b7afa12b10c7274b440b4dc3bf51a801d483804b1b4b9a096c3205a0e2aa7c0100c6e0000000000000000000000000000000002ff20af67f126c80293e44bb3c9ac74a94586a2de4146588c7ee8503530398eabc30f7e89322727739618087fa55de50000000000000000000000000000000013c6d06ce509fd557946479f2768f62474e6db04b2c92c5cfa86c023f79d05a387bd4c9aa618888476d4ecc93ba0995e00000000000000000000000000000000000fa477870c952f7506b879b17fb0a1c31771ee832ce0ab21a513fdd91b7a2a78a03d297c55558b834e255462b15520544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff700000000000000000000000000000000181336b8fdc03c02e23cd06ac975855caa2bbc1fe78a2fc7a9d0963c90a1f1f9330d50b88bf2526db6132d336ea5b8e6000000000000000000000000000000000f2d94d3fde2c0f67dae5a6ac12f713ccce2621303762e01961843eb9924d1d3c732b4c977d8cd0e5668adcd7dbf7dcc0000000000000000000000000000000005ac9ecab11c3368c75b0d396889dc34bd43ccf550d817c1dcdc7143c15d5c0e241add37328a7bd8556fde87d75d67fb000000000000000000000000000000000184704eeebead43f85b32d7f3efb9b9469f3ae10b73a2f034bd33e6e66da0bc36597d8e29ef5585443a655e24ffb68fbb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a00000000000000000000000000000000046751743f8f747e378738c265c1df3a368cd9570a2bd7636991045974c34039161fb0eddc6b813003e0908915b402170000000000000000000000000000000003341bea6cb81fc5e7baefd386a518d17a6f752c0e1ace5a9580a1b1649f5501c7b4639ba0cdbc33808d78b025a31f190000000000000000000000000000000016e3b9e8e189df73574a00a721440379589a7a6df09eca9a790e04c729400323b2110f63d547d83664c35227bd15b5760000000000000000000000000000000005ebd94e4640344e99e7e0f1619c6288665c985b90d99921ee61bbfce921265c4881a7e1034bcd840a665bae44467f5a94a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc,000000000000000000000000000000000d993522760839abc960e99d62dca1021b52ddc8147929c4a064ec72570ffb3793205598cefab8490446453fb6da231600000000000000000000000000000000105db1e83fdff735d06d34574f962e70d84e2c1ceef4d8a8f14c2673633d7dbc7b97ba6dce9013f06fcfb134ffa2ef98000000000000000000000000000000000363be663cb0d36b8eb076df283b075ab9e568e460be804f197c51cf7ef611d8783ced304407d4c2540f1a4a04c18467000000000000000000000000000000000ab2c00473a2267682ecb356422aeafc893fab96a3bd27ae58d9b0786624c8fde446cf68bf8a003d9449702e345b1ace +0000000000000000000000000000000000a575d896b06c5ebd7459a70b9321cd0de082dce7dc0ce7e39581751d01b7db810bca80f39f521df0bf70ef642bd66a000000000000000000000000000000000ab497a9590deef40f6fdc0d4db2ae7b6ad9ab59f112a5a0671b48581f1f2b6a71602c73784ca6c0effce66a0a9c6500000000000000000000000000000000000af3812439e44981c91633f73d1a92298ca1ed426c98cfbdb50643cee36affd5fd02886349aa608f4b8a27452a51a96500000000000000000000000000000000013126db8b642d33dd988b745b07084ef86a228767f7e8bd45aac830dbce4136ca5febca5fda9644d3292203e27439d9f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c277000000000000000000000000000000000cc12f75fe5e6d6f082f9977dcce64c7858f3b6378112e7e083caf0c4b33b5811d62a1130c595937983905fbde8db1fe000000000000000000000000000000000308b803bcaf4f63affaea0206aa9f4770c21b4d191890602bb4151b80fdb42af0cd9f8dd2b1a3adfe28d0e49712d2290000000000000000000000000000000019f83af5cbee858fcbc9bca0f499222849b9e80dde7ac79b7c46785a484fecf274e0d4326469eca647cb223068a183d8000000000000000000000000000000000d0a8334171571bc63054c032299824523bd2476b1150a67eb17b84bba01d8a65295624202c3874e0302159951734702dcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec088000000000000000000000000000000000141cc01094391887f46391bd49fdedbaaf524cfc94d741cc7c8cf081dd7c425d81ea3e407be48127550012e39d2b0580000000000000000000000000000000014db31972eb242d6c2912b418ddf416fd7911f13aede9194559b05d1c9e12056deaa1e56c155cdbc231b39f4f9aa91ea0000000000000000000000000000000007b361beb6c156b5c8b92b489e6d6c05e32a4376d20ac3e1a54c94e678c88480779bb789c3e1ff7a021aa6d872c98551000000000000000000000000000000000215d270f2d3c5c5b9fa99a873fdc337f4edad6889f7a55556d8ccb5ee86b592453b74a720ef6a907bc342710cfd9cf91d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c670000000000000000000000000000000017f5d31987655f8eaf046d6ea4025444924befa51c319b2bcb02dcdfde4d80a1c48049514e0b580e4bb59dd2fe40bb22000000000000000000000000000000000141ab771c08ad7c592725630aca0b2564de1ed8759eb3afb10a4bf451eb21d25e8d917f49bd5f7a06894baafdebbe790000000000000000000000000000000012dd82703c939cc5e7dd5bc3b924d744f0ef1a95fd0b9e57617e822e3fdda05b2e5a9959ec48cba0da40079da2253cc7000000000000000000000000000000000c53ff34d875fec4c7095af324d15921cd775873a3ba67740b2c123d6d482263b1cf93585dc810d19c68965cdbd9e102310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a16000000000000000000000000000000000a6514331035d42f58abf98b805f159921d8c4c935f88bb5493c580a6ce14a65e243424b41b3a9188e26a7f0c912a378000000000000000000000000000000001351e48b2d3f619887f4e83823dcd9dc15afb2800169ab78a2cd5ebdf25dcb6310f1051894bd2b549e509c55f5286f600000000000000000000000000000000007900972b84b6a76b2e686fa5757e98b8395bfc99da86eca122ce209afb39e8f3b07603cad92623774ed54d637e350d30000000000000000000000000000000002c68c42b3924b89a67764990478e48fc17aad4b5543bd38bcfee34fa1cae7535671f3b885852aecac53a30f28b0d4aa2fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000152d2c0e798d85e4dbf35dab808dd29d724e9b6c7ca7f53ffddfe1aef5976f2d3079eb1d3099e91b37d9fad7f1af5750000000000000000000000000000000015059423ee4e7201aa65e39116a2a49ba715b15e4b9547d18a0efd355de6f5a0159bc9047508bd3649407758d62887f0000000000000000000000000000000000e5a823fdc69f3928b22c542388f982f8131a978b08dde80d44e51d9eaed2ac4a1d5fa7392be6c7edfa33e833da4832c00000000000000000000000000000000044285f4e4ce526f96f9f512c5be754e0b0953744dcc04807ec6f041ba5c6fb9d5d395e93317064d50e61aae26810df0aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef0000000000000000000000000000000019c457e369dbfaa130ee79bd33ca70d00a3797b6cf62126baec0c5d7c3fdcf5ba7f41195276dc412b6862b71560aeb77000000000000000000000000000000001206f67dee6521ede85573bbd5784d675fea42da16010544857d4e2d81b720b6f85f646fa23540880b44a6cde9a39f5d00000000000000000000000000000000142018ecd7c7acd4f4ae288e1c6a66594f1c7f31bdb9bade2b4dc4c6455cdc685b716382c54d67373831a19100185e850000000000000000000000000000000013b0b57463a3e4cbe063c0d4f4e998cbeb132a41c2877106ee60e83d4ef7d339a5432d30a3c149a42dfb1da9d61f34030227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa80000000000000000000000000000000008a463003900194e45fc2610fb461fde538b17c4fd516919000d423f5a1b582342ab9ec20d8eb6fda8fffc6a898e46420000000000000000000000000000000010eef0f7bf73e35dd75fb924bd9759c09aded9cce46b05e5d3c5eb3e93e5d5032ecc459e2220aa529d2f773c4b8b8c180000000000000000000000000000000002a0247f82a25468ee74da555218cdbb6405871f7097c24e89db3f3eab59b91ce48ac06e8eab2c049346436c846226a3000000000000000000000000000000001895b58a50c025e46a2cd0c59d5437f6eee75fac949adb7ee12d455c96206a33ec9ac17d5088fb773618fec131981ab6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc000000000000000000000000000000000cb0f13b0680c2f7de522a59f4e46fe1d4af3a64cd3ab97a2523ad3c3dc42f5e6760e06cf48e4db22ee64c5ed8273dd90000000000000000000000000000000016517038ecd2799d787c5b6ee93079c93f78de4a96449bc82699ddd6eebcedaa1d02981ab47c529652cc21663f1a665100000000000000000000000000000000067ae1dc093d4aa2ddd8b7127dc60745ce9c462a066106b099a7a07525597c72e4920bf64c2ea8a3fef3de51c703de8b0000000000000000000000000000000016374f51023e2448eee7c64115d85794996fadf4f76fd4266c45093c266f35be09e861d07ff194f3d15e310385705f0e1a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec0000000000000000000000000000000017479396aeac06bd624a47e75b066d6daf5a37dbe515650cdf3e16be21e7d3a1f52a695c1c06382589eb7fc869c7d9250000000000000000000000000000000015c31ff36ed4eaec4d3927e62c111d062236e19fe6514236e6e3f7ff05ee96e3e4c084fcafcd21049a81faa1f84b7e7c000000000000000000000000000000000341b440e6c6273515fa7940d2f77018169bf6362b70a7b0cd6d66cd332ccc30e3ac48f7581edf47ebd137253a9c1369000000000000000000000000000000000cf424de046252efea9320b32b79bdab58e0e04f2916b4e8ef475da7b8ab85d8d5fc793a45ec6e6c035b6331a895d3efb206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000007c9111a85a6acb851e9cbdadf182096b720913ba3fb357dc2cbf2b8e796e9a8044b6df3ccadb740c73a16c3780c640b00000000000000000000000000000000059543a955c84a197d23cac22e15d82363c881026e41c57ee924da2a8c044f3021b29918d1db7926ddc2fc7a662ee7ab000000000000000000000000000000001355d8bcbea65a50c9b6ab59881e48e8e5f5592cee6aa69d5d01b033a84057cb6e74d911769bd2ab5f9722328aa204640000000000000000000000000000000011232571c95d0cbadf8e70454c851974efa4b326370249238db159a1224cc6d34eaad690e1840ad887a875b667ac1f193a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e00000000000000000000000000000000088b7cbecf91721e01e5e4a08ea3b261febb58cdae3056d9316c3840b3e5720a289739568bec7b899f4b1f4f5372013b00000000000000000000000000000000001f8835d4b0e3b957e46b718b6bcd81acdb50ab85f10bb70c6343a23970efbe72bef89dbcb24d66e6a6be3eb55665a200000000000000000000000000000000046500afd292a31bb5a4a9bd7b5bd0fe608bb1265351edea69162e61f1623cf58e34e8e1a8ec58ca166e8203c86f84c00000000000000000000000000000000005d6cc367ff9c88fc8b6c35383f147b4f9e3eb21268a5a7405794441d449b3e1b44c8f66e30783e5f6c3567adf0d80171231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa0000000000000000000000000000000015bab57412cc5c7ee0147b0d2511b7836a14a82df06b4eb2b1baab102840ed04cad81da6e920ee000751e0727091c1460000000000000000000000000000000002f725e61e82980e6164cae7a2e30a36dd7245402f4933697607640d53fab2d5db57698be33a0c9b5dda14aa846db7c90000000000000000000000000000000007fdc589448887f6986efd817c63954d350511401333cb0df89214317dec0a82b06259ae9263f260fc7f21f98ad2630f000000000000000000000000000000001324e3bb46a1c69fc550fa8f2ae2d0ea74bc2d7159bed03c13a9d232233449e271ad1c3922dac5d84aae52606f77dcc0393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000010adc26d73007e3b1cc58684fbdd7d197550658b4c66c702e9cd0f4e481f23a26c94c6798cdd9763110eefdca3d802050000000000000000000000000000000009138258ad1bdf6f9cdfb943fa32b42c4f1d834be536ed365d00126227c78b0df2776610fe5cf66a937cca3e0b088861000000000000000000000000000000001991db3a35bd2cd72377cd459502a84315422bed92890af906fefcc0acc4515fe7cacee1e4f360ba24efb23292482b8f000000000000000000000000000000000d10dfb682ae7a78b23b37b081efba32ff2011fcdae7b0f8a794a6ec33d71f5d6055f93e3b68a37086ab190d7d9bd7aed412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000182795b905320ee69281de833f37e040a3295e23be05ea7ae4563bd49d8b1fb02e95782c5c19645244633951cc29c5c900000000000000000000000000000000053368ee1412723b5c6465ee5ebddcfc00812e0e12e940f8485f44bce475c8897b324eaf7e66c0351ce9a6c92758c337000000000000000000000000000000000279f26c1e76e5f5d0fe1240c0956cd6025f6520ec303feb383b69525ebb6b2f199808a578a91368c3881a4044f37be50000000000000000000000000000000000ba4012c24dfe1038ec4b4565e1b321bbfc174cb197f0b0914bf1c126bdac9f423845f6742129670b7f3dfeaaa62df45b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f10000000000000000000000000000000016c27a3a950fc4857fc775441947f7ac02af9b3df6422874507b11f7b005c61d7d6a4a115d3759fcbd64633a8ad95611000000000000000000000000000000000e92954034df4f15450c32be31d4e146c4b0014a2b81e2afe755df79aa962afb05ca4d03577f15980fc6d8a34f2cc50200000000000000000000000000000000032db3e3c3617c16ceb1c8fae83e806744ca40cffb56bf9b79997cf48c55e5fea89db43b368cd922cd7ce30dd3984d82000000000000000000000000000000000d153fadc3854be49b2376ffcf4e5a46b9dfb4f54e580986767db13127e2d4d10e465f1ca932d79ca90f1971ddc0993dab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5,000000000000000000000000000000000e1968e131e25d3f911284c369eb63aaf264ee82f4d9bd978b7d02470feab68ae82aed8509ffba875798a292f012c9180000000000000000000000000000000011488365570d9bff018ce6aa15e3d7e078368f09873ed5e0b827d1c25ef369d6571899c8df38a3df3135d0db814c87a700000000000000000000000000000000161973f4949bd72b9f010f017398778e0d7f0c8f77e12a87db1851af516b4540e3f4df314381f295c4d167fd9ac091a6000000000000000000000000000000000ae16f0a4a597159195aa47862585839485615095e538b745c1081ca73f202115a438d279dfa45bd3aef8d4043ec67c6 +00000000000000000000000000000000046eea8e5af344dc8600ba7e506e923f6c356f7ecb3b78bb3805c4561e808c1f570e122c4fc5a1fbe433b48ce0c15d510000000000000000000000000000000006f1ab405a46c825e104bc963d2b2f573f0d345bd2b08a952d8793c0297dce267a754b802ded4db478399cfa88e7e255000000000000000000000000000000000a5fc4a09019ac9649c07b623d2cbcd9f0cbb89d28c01b170b62544d8da8ba3f236ca3172ac754175a3db85d9b846cfd000000000000000000000000000000000f7580110db2549742f69bbc2850e4ab35a6e415bcd1b06220b9b009c1f4c99152289eedbcba2aa653f38f6b8460386b3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e00000000000000000000000000000000026907ccf4d501265cfe67bc1c0b06840e9dd94a614c873d676b5416457d98a1dd744322887f1f1f86176b11a27d2830000000000000000000000000000000000cb08e541a5b32fdf51acb28ec64d3ea542c7bd75179fa3f74e9588156815bda9d027dcf5597d714aa001b2dd8a9553c00000000000000000000000000000000103ac1c03c16706d5936f216a6445577c96acd3a00a3d8a9c2c66e6ce568dd84a4c4db187a5fbde24e6ce60e037f53a90000000000000000000000000000000001da5cedccc02d0f8d1dd7e4d81c3ec47d432e81e941ea1452b112eaf40748a6634957c90f32fb0385dc5d642bf65acdb3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000a0b8dd68918b58ca6b113e938f8a00b2595351777aaf32dfbf703ef3884f02c798f1b5bb78cfac32f196c1fe88aecaa00000000000000000000000000000000121a4104e374566f8d582f75a3c9b70f09628f116b7ab22679ee13a1691b0b0bdb0d737833fb606c746fafee5859f1ee0000000000000000000000000000000000b8bc89d718572ebdd6e3100769f2571cabdd79ef5ca9a4b9bbaa432b1a4dd752f9af9d2a9b1f1f32d76d4ec2d1636500000000000000000000000000000000129f1d760a12eb1a75fec1d2ca438189c933e87095b9fbf9a0371d64eb205d8f0932fde9ee2ab9f36f8b6e5d4b5dd31021953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b825000000000000000000000000000000001499e5481ceeefcd2ff672df24e8987fb60872ed106c496178d71c68e9078409a80016e1f9727ed0d5922c93e821dcc80000000000000000000000000000000007bfb606c005c7da6b4ce2d974f9fdb2e3710c8f51f18257ced7663cc341ff81fe2e46308a2b62b13408965949a6f08800000000000000000000000000000000003fbd951e860e3a4724b667427fd9916ca4ba511a0dcac7b1125b14d8a4f4da82ddc0b0edab8ea50e911b0fcb5c200a000000000000000000000000000000000b43195a5f0263307e85408ae4eb046e06ddb1295a490ac4e0e654324de53d0dd023b8cc159d86b861dfcfdf7ebeee4a505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000000743bed2c17bea1ebddf750da504fe120f457cd3b1754c9413757cc48f7aef07eb4fa0572cb853cb72d68427e875456000000000000000000000000000000000102ddfe3dee27186a9484f74b3cb3aa366a79f0d2e36063af6e484f6a459e9168d7a4a6969bb720ec694a52db7ab34b40000000000000000000000000000000009bdf5b86aba4845adf9187ccf9c74b1fcabaa05764e41fcce4b38356b4a0ace8e7b16abfc7f7b96b785ad47fbf8e90f000000000000000000000000000000001934fa903b71d234c4341b2f49f8177334142e7c401553dad38e66a2c157fcdf7637165058955b7798a59051846dfb8cc4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f000000000000000000000000000000000ddde953f59b8591a83b0cdfce780ec23d052037c26d60cef36522d0f984f907315d7b41c8be9a9632f2b88e0ce950ce000000000000000000000000000000000b8d7bdd94a994901a434e6ea5d03ea45dcdb859e560833d8ea0bd9d20c7db9c16b2427eac27d8f1eb640b7d28a530fe0000000000000000000000000000000017b5b3a3097a74d9c1f1b23783723235b6148023b6b060234dd9e2f6fd05e38668167136c999d91249963e224f9bbcbd00000000000000000000000000000000133da0c217c31ca052800315aa8a3b934fc1f179e6247801904bcea1e28dec0b65632ab2690bcca3606bb1461aeb147b99762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb0000000000000000000000000000000012778a6fe79b1f2b768432df036543cade95504bb7735ff547969faaa8db84e3588046a074838c9a551a4fb48f4a66140000000000000000000000000000000013288a3413d7e7edebd118463d5eea9f9ae2e10f51965480f9b5c244b05775d04079a1dc75ba0885aaa9e2e4bae1ac750000000000000000000000000000000005b766ad112b8d69f1a28079688942ea146f8f31616611909f539a57c58ec5e857da9fce415d683c1c6dcb5e74da9d17000000000000000000000000000000000907e5c3c83d3f12a68d6bf812e310f5a04f1417094301fab7d4f41007b9d01fc1bfbf739dceddef756417367ed5b1d0298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000090be6ce5ed09e45a6fd9ea3a9223fe43a835141c1c29d6b386e085846869f9c5798b80c3bddec8bc15171906dd417dc0000000000000000000000000000000019bdf67eb16f2708ca55fd20af8deca66e2ae270b2f2f9736fcf49dbdf7cee034cc956f6fb799f0e87c12f283a11448e00000000000000000000000000000000124a69c723cbd366d52919a72dfceb7e4cd9ca5b5cef1784bfad3f125b11d810328ea1c849602536af500261aa684f5b000000000000000000000000000000000bbf05318ffd81495efa4f4c271c8b1c669041a6446501788f49b8739a934f09de9d976fe7300b0ae861be567d35c992177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e5730000000000000000000000000000000010833a3e7329ad40c1a8cef296b015f6ac6542c612038ce00f13a99f673783cb7eeb14796485c168d21cc169065d051c000000000000000000000000000000000d3b1416b23453b893c92a6c7850cdc0e4a395459140391b1dce11055da10fb68f318c5561e1c12d991a28f3f544a5230000000000000000000000000000000014721dc58eada80f2d0574fb4e2c1c94c45fbd90c2d2fd666fd618a96f4736a5ecf34cab34fcbdcb19b6cf7b44098922000000000000000000000000000000001905d34029bf84617a956d1edae090853dc1b622f560c5289251447ab6bcea5700bdd80d6ffb2dc12fdf3b0267e74543cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a99000000000000000000000000000000000b4d380f4f4eb976e6121b933be8418c536f85994491b0b93695d50473615e41547ead326bab795d4d59524a61d607cb00000000000000000000000000000000104b7f4058c9b355d38908d715c311a53169b42d2434de0876f1c4ffce1c39603c4876b33fe3076528be15fe42849d3e0000000000000000000000000000000017e2fd647e7739366ebb606e8a326daa5c03cd2b726cc4cec7747cb3468419f1907126d7cba98bbbc659478ce3afee7700000000000000000000000000000000183be0a976dbb3b5385b544c194e111729c7a8d5aa98eba3fe1c0a5b69b5fe6e5d0164e96398cbc61eba5b86d91b3c94e03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f50300000000000000000000000000000000023bc7eab817fcb9982cdac242cb6cc0ee1779bcefaecf144dbe57d5ae2b2ebfe9088f39f416a56de4b4dc04d4bbce7a000000000000000000000000000000001318e728c271746905788dd8f5ab22a3a10edce3fa063438e54ebadba22c29e461b2ed78a95a8f26a65b47022291b8df0000000000000000000000000000000010aab000b9c5de56623f18861b343ffa80da5ed4ae0d7767b7ed791bf3dd507fe7286447b6a07ea0fa12c19f2e4d8e8d000000000000000000000000000000000770e2909b5795a08d98dc66389655b1718e70b93c5bc6d805c3945cb5fc0092a5b390e6497b550988c28c58b6e016a3804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f1000000000000000000000000000000001259a4e36f5bce7d5f97184948d57fccd458cf7f2ae0c9e174f537bece01d744fef544447959cb73a678fe2c378ce3c900000000000000000000000000000000131aa575b2b94232e06879fa1f6f145a0bf5dd12456b698f731a72bc587e6def5054b3b2afb6dbbfc34fa5249dc673860000000000000000000000000000000011d64b923596c316b097a0752043efad8b61fbe068c58bec7a6766d9bc90ed965b3419dde3b96679426f72184adb8931000000000000000000000000000000001653af784cbad5a804e3f72716bb51e0c733014d587952c47395f953828566cbd7da811a3da1d48681998d569db00a7bed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db00000000000000000000000000000000077a9ab830f7683b7fb46676df09f72d773b65286c5f5ea86623306e5de51e63851c18d192c4c3b20af582bb7f017ff70000000000000000000000000000000016dc185f4158e249939541d35ae8230fd749988b9174c40c40b8c932aec625a7e94beaef9a07f492445d4675a01b7453000000000000000000000000000000000c107a895bfb45d33136db6251c76dc0461a235fa5d1ba7a5d216bfebe15691261b46c9816315c146becc328acb6b8c7000000000000000000000000000000001151cba240678efe61e3a36e169e314b3610e9d4df6650507f53ccf635d8f1277a80d86baa85a2d4c7e2af73934a7299e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd506400000000000000000000000000000000090a1ee6c611980e0421b72a122cb39257dc38d1e74ee41b809ad76e440fe307cf45e79afddd8d40b94382d48cdd4c450000000000000000000000000000000010f2e6e610eec7b7c2b95c1510af1af342ac19fd3b01dddf81b8961ead2cc57a8eca36c2f5747238eded5914e484c52e000000000000000000000000000000000acce0789cfff975b09d687ef79535c536f3b799157d3ff731915ea5b323ddd9f6f4750dc8e00a879d4e516bce8cb3e40000000000000000000000000000000008d8203dd13aee7363f6b10a9e1ae9b713bbc8b8fb2c56f05fa71e8d69ea571384d150e8fd01e855b1b0054fe7967a052f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000b332430c518d7dcd120b346440e5b6b48900b5c3656d84840823a96e5bf002816d583a989898cad9e09ba978ebc58a40000000000000000000000000000000004197b43877b833de7f69cc1a43ad8d6d3544cd10d42336d4b19a187f31337a37b10cbf48e72b77e4d8e1a1da68e5e4c0000000000000000000000000000000008887d5dd08f45034584f40a2a68254baf2104f9d6a4c2637ef79c5ff2503c246f7adc36559758a0c07533b66c3637d40000000000000000000000000000000009343819dec1d4569683de4596621c19785d5ed14ba13e57d94b1b1a108aa62cc8c55c58dfa18c06883ce50cc1364b95e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d7305370000000000000000000000000000000003fc7f9a0804e7f1664f8cd3ca67b70ba128529a611c24214fd09674072a6b8d652ccd37bf5d4611424688213a41cb3100000000000000000000000000000000137a869cd7bde696035bd9353662e0d37d2aa0731ae55357df3bc43536b9210f360324cbb3670362cf9ef607b1919bca00000000000000000000000000000000045d9d39c04e257fcd912c54e57c86d2d4304e6a7cb95a83d2bff07964d0a5dd8b4e42bdb91a8b245e512395e6749f1f00000000000000000000000000000000120e5e4b04b8a744757812fc331e7c98b35624faa1cbabfc1470e4c0804248bfb0c53a484107a677a7d3f0d2b533e7530cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5,0000000000000000000000000000000002bed414afe9c7a630441e7b163280be10e502cf877e94b6521d14baca0087c5dcdfa39ff4a51c8376d99855e1e6f36a000000000000000000000000000000000dcd54727a7729408e682c6e213005687ed51fa7935c522312793fc58cdb273eec9c61cd8b056a26619fc8dc006b066800000000000000000000000000000000137286f4086763e6ccd5ee82d3bda712b26814a17c6a71006a3e6dbdd919e469bd0e744bcdb2074679e78a1e7d56ee7d0000000000000000000000000000000012d75de1310199c0e556d61d6c0395b406afba0f13bfb37486c05d66b446809e8b1a024e8fd2c55f1b82cf2aed99a5e1 +0000000000000000000000000000000008b83142b22f6d6496cad0dea23c71355e7c5d98659580b5ee6e97eaccb9fbe523f7e3b925abbca3a38f67426f3fb35f00000000000000000000000000000000035f655a1b2d22ea21cf0081e78d7140bad08c4e66dd45230a113ff3b7a77e39f0f1a72991f85e2b00ff58b27d5cb54900000000000000000000000000000000105d04e38243ef1ad2f734a3c97e91506c5a7c5d95e9b8771b7fded8908f1be933a81a5769044b633d501c0df7b5d7fd000000000000000000000000000000000e670ae4af94d0df34a7f2d7cfbfcefa6eebcf2a6b2dc5b82068b023fe02ce8a279e1bb96d905ad4f2ffbd8214e47d702063b046a71c2674e35466657a85d8e02253b42517b033619e31a536659172120000000000000000000000000000000009051f1e636309016c5433cc7eb019c7dbb75b3a4a5b27f6927de08fdd9577e8eb9e12919157ed35bfd6607be7fc4de5000000000000000000000000000000001953b7a33695ede6d0792eba85567aa5052b8a58c1bdc94ee82b5001893c6b996d3e8f7af8b8effd6cf50656d8b85554000000000000000000000000000000000a2f769f00679b610bbe212c2f8045e7579a96dc6bff80899eb7715aabb1afe79421ad5000f2c7b85d4e0904e335ddfa000000000000000000000000000000000ec962a3d00fac14d05774adc49bbabaf46ae78325083c0020587fb85eb234387aaf6506f503fa988df8e9ecafb4a59992fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000006823056a4da801cae430fb9e3a8663fc8f46bb6c180b743b7f9c7c7e3287f3feb1aad4be0e98409c74ff58004f8732e0000000000000000000000000000000015f7a3f692d55252fa5af5ec952f581b796d54089f13971fce2ef9062173664816dd9f37174294ed78681d8c8c5a9cd800000000000000000000000000000000154743c76f7de590a31cb96d46a0ec0fa88008b7d6684bd8f6fdaec70722afff7b6e88c1f0fb048714fb1072d30780e60000000000000000000000000000000006f3191946d0e7c1307a1a0d1ea9a26db195ec98ad88f9b8f08a03a3d48bbff1fa53ffc920f7db5ebd4c65911392bb834484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df8000000000000000000000000000000000355018079cd02dfcca15fbd2934a8e47c5ee89e679663488499ddd4abdaba7679fb1c9d2102317cf2798c47aff1ceec000000000000000000000000000000000c417d489a224fbba9999300eb65a23749194bf5302fdfaa33ff7daeb8d896e387e56600233038d5c5eb59f644a99b6a000000000000000000000000000000000f5a62e9d711293d4373bec1bc2637802938eb789c828939e6c42f10062ec171ac6110261165bd179206d649713f6fe3000000000000000000000000000000000b11f9fd0ef8dcac2e21ef09846ffe9f5a624ec246e31393b39082a47354fc9523dbd247f0059b6cc740d7a387b137f0fae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000fbb5521cdb9c3a69d58e5c9cd7e4a50bf5469bda2603f5119f3209669eb3e374d700f851b0c7ac5ee3cc9de79e6a7ec00000000000000000000000000000000131ccc37581e64f6f9fdf675b9b63ceb67d9d5844bf512166f39b5bb09d8e031437c06b0ca01caae7ad6d8c9bbb9fd67000000000000000000000000000000000531cb0557fa18ef054dbff2e7e994f1af08aaea7557602a26fd6ff539ab3c0a73f1fe841177012dabed4a1223ffb5a7000000000000000000000000000000000a180e7a345d2b635be92888934608e8b6c17384c48c560f4cb9809ff995f8e70d83cd4cf0e96c458fc414e1275d2a993168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a200000000000000000000000000000000125c83184f63dee35ffd2c0c7dad9010cd6a9735675099f24b465554ab3db727ee76b5b7ea603ead78795d33e37689a400000000000000000000000000000000141bdf7e270dcd356993327cdb5dabe38a5c5a9b53470d9a4aafc041c46fe8bc841089e337469bddab5d4f7fd3d6ccbd000000000000000000000000000000000f9613f6d05f38e3073f14d0c2557101a4864a7d6d0b5a2b931d0613f020adb99a1ab2037a39fea6e99fcfb47929827a00000000000000000000000000000000192d812e05a17d22c60b78c53fabcc55a0eef3656f8e84132faf16686ee18ab4d35767db9a384d42f392c40c7b0fe1c0f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda000000000000000000000000000000000b6e1960e82586de19ffcf29a8c5f16cf2fcf5286bf42febef832767919abddc655a0d1bfa240cac8fdfaed5a1e8f389000000000000000000000000000000000fc1598454caf04414f1930f711d762f0d72f5cdc7a4053c92b916c742b00dd0f107aee111976c1b1218c4577deeb006000000000000000000000000000000000455d6e9e9bb848e0868c9d725edca1f50b279d0acef8c597927eda72763e3702f46b216919ac36b080b4865249fd961000000000000000000000000000000000174463cc7804796b4a6d8ff28d2e8cfd8361b2e38f368de30166cf3c20c474ea0a1e8d94749fc3e6468924a7d1369e62d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d00000000000000000000000000000000144e4b615ddb871bae85484c308423adceb5de387d0c7ffffdd2211b4ea28788eba9bfae96ffc46781e6d6343e2f501b000000000000000000000000000000000046e39cf43fd707ddc4b7ce9a8a22a2aa1e55aa63cae1eb23082f7b4b5dce49f32d2ff887b5108b40f98062c02d5613000000000000000000000000000000000b75b5460db2baca86528569b47209b5ac24930e2545cc6aa08c401a87ef2c4e233de537e5a857e533d0ba0981b24d7c000000000000000000000000000000000018f53b83072fe7daab226c831a89da63a0930ea86e301c97e639d0ee1609e298e2789d1a347bdb4afcd355fffd16d053bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000185b296e9c7209a9abcc3194b46be9a545666527ec9b0634a3e3be579447cb52330174c19e40e1667124552392a7a0c00000000000000000000000000000000158a053c788e5b914fcdcf1aebb4e21cc8bbfbcc20c4d692256b2ae48149f6644e1578f98d58b3e73d9768d0e7df643b000000000000000000000000000000001318ff4150bebd8fa612f4e84f89151d5c56c272969bc1f31a3c1fcbd8ded0e298914e98e1ca48248e9023cd12db0fd300000000000000000000000000000000076555254f382707fdb7419772a4978808a7409f59d1dbb8c9e648372e19c44573f5ce1888a2b570a83afc20e698ee44f944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e431000000000000000000000000000000000f4395e3f2e301ee3e18df3c23cdd142716c7fcfc23caed924f0561795948b0bfbed948a6f7c415ca615ee0ba4d5145c00000000000000000000000000000000176ad308c7fe8c3a1aa350fa82b8f8ec638f77bc703afe1042a6da22e5385cd8473ad789247f205214c9980532b12c7100000000000000000000000000000000092b0ec86c511992c66f320ad46c9d6d7c82df118a9ab2ce1f2c5611ff4e5cdc9193a39c3fc95f18ddf96e139688b00f000000000000000000000000000000000b4f671e334b7f22bd8d89d8c4eb8a52b04bbd4dd1259cc9caa1872093736680618930f3a469b3af4a00cb6e44b573f27de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e100000000000000000000000000000000123b64561ebfe085238220eb1428b3a203acb01846d1e4428f3759db6cff4ed3c1b9d436706f28b77e3b92e2e39ecb41000000000000000000000000000000000ccdf1973693e4b43b6133563986f6c96e2b924895c813f8acdd0f39585e4ee95ef26c0d9d51d6ef88bb62305e51594d000000000000000000000000000000000f51693bd44b12188131ca84801bfee0ca853640c0a8d5b20123c97b369c98299ac04beeb27d75946cc6f45f8a07b5fd000000000000000000000000000000000804c6597810d2c75de94484873a67eae258fcc9577bafa778e13d4814ce099a5684b1cc94e0df5a59acc7b19328fb8bb0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000009b02eea05c78a24adfb0187defb6810116e21894d8782605c1d590f8bdc10723bf71a1e5e5004b181504ac2deb142cb0000000000000000000000000000000015882389195128e20e50ec4f8d278e8b8791e362341be93c475064d640e1f8bb1c92a6c777d666f8644d471409bb9aa90000000000000000000000000000000000d89295f845f989e0fbc6e86e97400b08e39b2968fe6c9a141d1e92ec9c838a3d8e1ada5e44bb08189a5d514ebfc2f5000000000000000000000000000000000dea05d8e6ab50b8f8dd9632337948a60568724d5a03c7914e4a03e2af572dd8153effef1a7d5c2cb27765ef2c17bc5b4aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f8899000000000000000000000000000000000bacd14447ede6af0e92e19b54c4f5b6ebfb94207efec3e9f385a4c84a7d670514ecbc28ab686b383e239ae7f9bd673d000000000000000000000000000000001698bc92d146049174b843dac8c5dadcee12d1d503b2d0e46ee68139dd43d3aa797fd5bd06e2b214cc9ae3647c98394a0000000000000000000000000000000018d20cf6c84446cadfa1a26192a04e16d2b2a053705a89abc51bfbfa35c2b03cd58021ad95a35364ae1e2da5d233208300000000000000000000000000000000113268e360006294fa0203ce58cbfd05d05fb625e1f9474c96c89c0ec1ea80fe834030592c2f1c182ef8a3d5c32caf71adde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca0000000000000000000000000000000001727b6bfa9c601fe84a65c54f556887c4538cb5383a288156fec87420ae7f15da395886e1ac0e10b8fbbae8bf040f4ba0000000000000000000000000000000012127cdf02ada71f28ed036a417971b87fe443b8c65b7739795dc7067082cbc9f06f7bf10c709969281cd072490c06fb00000000000000000000000000000000134f1fa1d277d01e2811c118cf10e2de6324e2ba14efcf717a03c1a10dca0862ebde0f6328839da63d7d85f573e8501f000000000000000000000000000000000d20a036b715d18ac9e2dbe009dd0063a4b13b3ec6fd060a64c4ad2b98e05e069060179530410d154caa575d504c63b7b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000000bd5375e7f98d3972b93420a39fd6c31da86d0d9349ac3774bbef15c2240437cc0761b2f1245e805d2538cbca6f778600000000000000000000000000000000100232139641c8cd5bdaa75b77e1e1c8e33b3f9554e2ae00ec6315b82cc00a6a70d576d744e68938a299ee2b451558250000000000000000000000000000000004224691faacb007bde3e37db6c7486aa5d3b4259a24c8b7653238e7522604ef4ffc1eb3cecf719a1b7f52ff00c34399000000000000000000000000000000001156ceaccfe0396374c6dec5adb39f14b6f08a32b88ef7499756f5cc324a9f1553bf5dc106a97469f2c49be5d563e1100cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e0000000000000000000000000000000000010a549108e77f0ddeacdc795517ccdcb357f909264457cab22fac2b982d10064756d66d0e48af02a59f58eeb1e8ba14b000000000000000000000000000000000c68703ef1c1e93c78faebc5f7ccc69e39046fe8af92e12469e9fd6baee62a2e8cc06fbbb3def81ae5cc57f488fd9c9100000000000000000000000000000000064ffb6aeeed432629242c3843f8cbea5bf7fe78585763926c5c45dc3cb4d1c79b3715506d7cda18c531ef890b22a1f7000000000000000000000000000000000e0eeb69f28a552cc6563f5fdc9919423c4358a2b70ccd56b048c22111454f67107513cda2a5aa0efd2af25dc74a1c47e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7,000000000000000000000000000000000b1913c672760f98fc6d4e96ad1ef200f99add6f233b17291036e187ac6692ab0a29a4083dcf86a532dd06efb3d9b8c6000000000000000000000000000000000323b703abed59a9824f34d97851546a5e78441accea4e3a933b9270f92a9dd1aa056858ebd1739659018a0ca13b96e0000000000000000000000000000000001603cb3ed75c09ae5da6b94eea6017dac0c40b17d9aa8b65b78f2ba17de051bf3f21109d9afb214d260a09391f5526c10000000000000000000000000000000019f3bcdb8f16d9a2bd11e3f9491266780aa9110f447e19f12f7a2d62dc4f2c7b5fa18e13792007f5f361e20656c8ffdb +000000000000000000000000000000000b7d06c16c77a57b5ed74d829ad6acd665e73d20f1d9285ebba67b60c0d1571e1b02cabe5dea44499ce6d40a83288aac0000000000000000000000000000000007e6ae768ee3d149c7130022f9883ed51e4fcf68c91327ac1fe85aec8083aa61a37e9afc25d3352e144aaf888f264ab20000000000000000000000000000000016f2423478e0388e8495a898c23d63a0929a2ee7cf848034e4c1adad3460c7070caf47631eb930678d3c85aaba044dae000000000000000000000000000000001587e63cdf50d6e0b6b3d7652ad0a0a2497e70259d73432831781641d3a76db4ac7cff1bef165fd8ba29200d7320e43475888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001181bebe3256dd6ed754b8a1d77ac07e9a341789b3f4a4988599c7c60a36f1e95d3e3cec52c54c0f0abe312ac358c28700000000000000000000000000000000189d224b2904bd45cd1e8fa72570a1e35c94680d03d30292892462664f9d7aca3cc45ecc0773e66a10248df28ba9a9a1000000000000000000000000000000000f654f4c8b02a891e14fccbd5a96228afaaf79ed8306c7c1267715bc934e5f2568ea06de2bcdc2a55ef708689d90108c000000000000000000000000000000000c0a413f16e1aab8b91a87e7027f067ffe7de65097da37d67f604a184c7e7a7af6fe59ced8c03fa32ab036862868b35018ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c495043000000000000000000000000000000000dc972d55b7e68f97191d988ae7be5f5301bce5c654b323d4c17bf6e070f7227c0789ee38af3ccc07b04f0793090c6130000000000000000000000000000000016288c405bb42b4e71d12fd0a798cfccc7d33aba0500f939f5fedbd0e071166169d3072befcc5549cc6963b6dacbef4100000000000000000000000000000000171ea4f6607d6efc875cd9cff203bc62eb83bdc05c07f702143c23ab2770f50f42738f748e6bb3bb5d6f51f40fea1d910000000000000000000000000000000000fb729cc9716bf2e9e30a598ee7c4281163b287422ab66b414da85b0b960102991c24cd023791e4241bda5b0f6ddd3424a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005720bcbc598c4eda697406dbb390c2aaf4bc22c794b4b664e9b85b7c2079b90f7678e69496a4a5cd3b46580b90a7a30000000000000000000000000000000001159788c3edf619cc5e6f77c4aeb4860764d46afac4cdce54cade63155040c631eed65c2fa11b9cdff14847950cddc2e000000000000000000000000000000000d61bf02587e2c61544ae8a98b4c742c26a3d6ca49c6ae1b19a9d69c7f8eca43cefd555c973145566f8332902217cec3000000000000000000000000000000000cc0da96623432a2c170f07a3aad2844c1c2aab9d1bb5d2183928c818e681c66cb3767be372be4ae65fa40bf5483258ce4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a1350000000000000000000000000000000016efffb5d4ecbd01567c1e6099c0f06644d4312c599579b4cb780fccc8a425f3d1263a5f0c957dda4508202a354162f600000000000000000000000000000000115686a37624ffa8272ec7dedb7a632ac568245918ed91be6c9116e0fde290c26b5291e5f57ba6a779750685b0f126ba000000000000000000000000000000001852662b92fb49b2f0359255db8a7a2d20bd37705b7994cef1eb8e318aed70fc37bb7af9fc0c32ab3efa8c0afad640570000000000000000000000000000000017a691c08724ccf0e668f2f4eeda692e9ac21385fea243dc62c37ca73421eaf51c3a60771da3fb3e3cb578de37d2d45d38573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000006fe4276e8f2e23127853eb929ee4e0c6ec4a190d46ac222dceb995c2e1d8fc9f822806a877e6cf87cf579cb2862c25c00000000000000000000000000000000044dc671bcd516cf03ad98ccc55266688856a4e4e5a59d6a6bb93e9ca77c596b5ecd2db6f3cc6377a0086c53ceed5487000000000000000000000000000000000c3ca83688d20519423b2b5547afcccbfaaa088a90523272c7cdc9a9b0397340350f2a5ced2a8153d69c81cd79732bce00000000000000000000000000000000069916c468f22bad174522d7fb94b4b7d2a664743b4689daa5423f470014152387a29425642b50f9e50fb679ddafdafa02257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc1630000000000000000000000000000000001fd452b8685b0806545e09783947551bc5f6446c9d15d079a8968a448a6fd6f8b7e91974b71a4b2c50954be299c4885000000000000000000000000000000000f28bdab0b0fd3e05d08ee2c51f1bc0d30380e3a7aa18d6e04b47018d6a8d2b35a8f06df3866ccb95ffbd9c5333ca94c00000000000000000000000000000000035f3aa1cff72df0bb10f1a6f8414aa2ad0289cd15f56d84061a7cc70562f1f12304c402c388e48dd3f34082aaf79eef00000000000000000000000000000000034730e3ad7a3373b97279a00dc7a053aadd088557e0da61b9aa132c5b402fd9aef73cc45dc1cb7f2076cb2ff27ae2fc76b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c80000000000000000000000000000000009c91d800cb1d52501520b3625dd4c20173684bad8742c7ac4b35c0ce028556b6529de5cb7541e3c146b13f58ccae57800000000000000000000000000000000124259d345bf2f8c16215be4b6b7922f4e2d6b32f91c4b1c4f1d4974918fa9e6fcf10e46f0c0b55e2a7210d1a5336eed00000000000000000000000000000000072e6231244ed14aa0f5de06e2f953371995a567684b00e459113380c1434a8faaab8b28a0280336ae35bf1f90f1d4d10000000000000000000000000000000010289a63e0e5f1f35b7af22137e117a85df27874ba15df39b7c26801c169667a3afe9a14663d7ac0c2956f4eb70cf11fc9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000d0f22a9bcda47ffcd034618c15daebad8160f9ab6b3148f1cacb32e713df2ef19f706f14131f8ab1181b7ef7598e3e4000000000000000000000000000000001680314cd79fec583c8bc0842e1750b1318f94aa7700c6662aabd4c592ca61ad51a6876b686ac3fe3f508cb40192c31c000000000000000000000000000000000a172bd8e49637fd9eb611b590c68bda707931e403db35cde1c10bb74c389ed725aab54dcd7048285352c56c8bc5fd920000000000000000000000000000000012589683ff3f85ecb006c5c435ca7bfd9d5a6fd06eb625bcbcb18577cdef610d912e783f3986c965710269b1ff79ba972467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000009720c2b3a0658a4aba8e76e196a558bd155ff550b3e41bb5b43e7c5946bad803b1de64e342956a11627e7f24f69fef7000000000000000000000000000000000decf2262e8369d6a2b1ce07fdd257abe1c7610084ae2f347640c0cdb98c7cfa732dc609c18b7b6a51b47ebe4b07a586000000000000000000000000000000000e8a0158702ff6d6c3a7ed9fbc774bc329681130840d86ca3f26cf6642cb49e5f14ad95fff1c94151457b1d5a142bb5900000000000000000000000000000000035ae66137629e95539e09ee99b001d5b9a6ede79727d7deedcbeb5acf081cd05ad469ab06c265a5224fd5236db160b62f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae000000000000000000000000000000000413d82d0b02ca706f0266051445c04f3ac594ad82e2f1fb4e8e0cf23a6c1087c29383238ad3677f170e99259e2fe93e00000000000000000000000000000000070af21f84895c0193f0b8174cb20b11f45c845a8d782b1f58182b149362e1368ba076ba702185fc54b5da94c3172f5500000000000000000000000000000000182e124ca29d66f9f6c370f6065f60928b6a8f445a74800d59209219add6cab0d1b79702c31d60e61cf56874a4eb6717000000000000000000000000000000000b94b733f76067a102cce9659292f31f3df2cf2770e3a83c1524536e29d0a84ea5c4883cb4e849830384dc7e157d8715474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000004b2feedd5badbbdff6fd0f33a4bee17b38cc8967fc72206246c349e1017ed0407fe08e0cd9208fa9e4e21eca4cfbc2a000000000000000000000000000000000df0d74d5cc17ea94457c0ee26ef24071700a0fd6bfc762e3ec69b8f1c096887f679e312f07cce8340686eb2716c9a96000000000000000000000000000000001878edbfff2efc5af64aa9a23589a52d63749b7ab2970f256874fe0cc15091c4511050b0a243d421dc6536f19b5977cb0000000000000000000000000000000015951da3b20494a266e4d014d0ec70fef4586c8656baf536a0ea9a48dfa041624e8154989a2fb106189217ca979ddbe8eaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec000000000000000000000000000000000657fdf40c829719db134acd6c2a9ff904681b1869f28512cbe2a64d93e5b62114a73bdc5260ad9a1f24a3ff191b7a3e0000000000000000000000000000000004e77bf63eb9c4741028dffd0591b4f525d533b455d35e51cd86c7884d63419a162b145752bde188d2a622251c087f870000000000000000000000000000000016cf02af01fa6750b4d862f0cdd5a87a79da7c3fbedb0fa356ef2e7419e25b3a2bc8cbfa97463d463d0ab349efaa3f2b000000000000000000000000000000000ea4468fe6a85d36ae990d0ba959ae050756805c4c769c829de475a2990ef1c46de20d5b466543978faae0f6045023e85158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000018d42a2df8ca475be6bdc468a82c313599239b974ec3d27e8b8c534aa4d6b85d4ee9aceb15c38b3bade2bb1706a2c2cc000000000000000000000000000000000124d5dc60527faf48f5e9574308f8a328b410de1cb49f2cc6f76b8a1f2707f2d1a94bcbca0a97bc38f24545a8013b250000000000000000000000000000000018b690b3d1e3b22946a91ace004e1d8f92eb5beb284eb05b52ac5ba003d7bc387540d33d088a02711522e3aef7f74f4300000000000000000000000000000000103080d8bb379d961da06bc4c148cb5b056ae115b3a0e33f0a8c99a7fb7b7ceda35d3902e0733156d354dd0240e4bcabd66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000f4d068354cb5b51e5a86163978386533f8f9b6e388c5e75f7d9ff5e1ab6d1637717d251f2b723b7d683e26a274d610c00000000000000000000000000000000001ec5a0d408c55f247d62ffef172ef26e45c41029f1d04e36f0dbb4fe8af414b0f7fe7ec0cfda66a2855b58592486fc0000000000000000000000000000000000cb1b68045076f457746621cd415d743701bf3ecae8d52dd5582c3e0bfb38e6cf2651a5ebdf521afb1ec5b8066444210000000000000000000000000000000010f5672f813470378fa806abdff90edeb0239b00d85ff23a3fc6798779f46d6b43071d66f7742897a4e53ebf6c7dae719acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df000000000000000000000000000000001422fbaf1bc2908be5900968af61ffa7b3af46e7250e4663ff321f42e2db057bcfb2106c433a9eef8fe20f7138b71d280000000000000000000000000000000002176e68cdb0ada2d7baea437bec8754ea293d14afb85a811f7a5d740d645a53e511b5605445b110174ceb5e6720e736000000000000000000000000000000000a69e992b6f4f7eaad2682cf9ac2e58faee9b3341e852543c2aafbff390ae067a641b2b5693319618fde413fdc64d6c10000000000000000000000000000000009440317af8f5c753b5de4648b06212256a39b7fb03678f1913b0a3d402a50e74e2da5d29c211cdf0b292c132759c36d0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8,000000000000000000000000000000000fa31d16d9625200c13a415fd61b7552646c62fb8db307e92c1ac3d2acc92336765a1db42407ab0f774ccf01291b9ee800000000000000000000000000000000156a77678873dcbe4832b9fc7f516eabc1a10f4a6576cfb15765cdf999a771a6a6d41021897dd783e9beb2db722a6fa2000000000000000000000000000000000ee4599a6ca9642cb4cf38f5f2af23271cc8c5bc6e2cf6bad572b618bff9f8677837104b93ca8942843fd5db5c30dcdf00000000000000000000000000000000138986714a4053618e66a85835d105f4aa2ef38ad18e34b2ee7ae30a4282f7e543c80c94bd12c244506e7fcba25f4c1b +00000000000000000000000000000000083c515ef8509b12ab85ad7d0a816d986bcdefc14778efcb3bf7c2ab61991849f279ae6a9f5342880837c0d0f4a4eba700000000000000000000000000000000020cf5196b5d567fc429cb9ced7b55e4925e18c914caae216a736886a8d886c4bdf6d704bbd0ceebdc1975ef530c665a000000000000000000000000000000000f3d0a217c224434604d63cef559eed3864d2da62ac00d49fab8c2c6e22c688496adc30c8d591e21bc0be404b62083c20000000000000000000000000000000003d0bf7f25bab0bf2c768b44e10a6022650f7d5b7d568d502b9d0b28209ee69b1d952ed848572d3e966e8771c20becc4b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000cc4c4b7eb7e358d4133b65e635fc13b8a92229706a6dc5867171a60a99a8e343045a794c368f1133ae6cd2788c3a7db0000000000000000000000000000000019508aa39fda9c3efced287d2571db97045f8b7b0c7a9c9d51796aa8017fc0e5abb8fc994700dd5c9f755edb518e096600000000000000000000000000000000049f68b0ac142715cfb385161ee70e453f0e24e2e93f3f96c3d69447f3a28b180fe76989427b2e392c7ff939011e04ab0000000000000000000000000000000004903c0f8e0757dfd3f5edb4f54a0e292df15ff70757df7b0b04c99f590a3dd13c6ce7bbabf3e14daf9f3ec60e2379aafee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b23500000000000000000000000000000000041128064ac768664f076116247e0f8a00adaaa824cd6fff33bf524d0c76e61203408ac13b294aa41f5c462cd42d3cec0000000000000000000000000000000005e150c27979ff1cbe307511816be900648957624caed1f08d88347061cd783179c615258fcf3619bc4bfa53d2513c610000000000000000000000000000000009d2b3d97d29386b93d7af014ea8f1cfe2c1db5a9aa0c17e8430b0fcde974a4e7b8b42ef041e9a7b1a8aecb97cefb52e000000000000000000000000000000000d86096ebd88b2cdaf5cda1e9ca6b7f12ed5def629354b0570eb084bc7139cf20bb8ebe4438f87937b8b554e2201344c28728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb960000000000000000000000000000000018d677cd67e96b10b671d2ed9234d7708042ddfe6fb804d2e9371a80ad167004f9d6b92d26b3d3af34ab7caa0e03964e000000000000000000000000000000000e34a6c85187d328eb33c2d5b2ca96b5210d47a779ab810dcc380dcb7e6b3c334ac8fccd7354aa9108136e4f6dd4ea0a0000000000000000000000000000000000ab8f7274ee3fce1511c58661625c766ffb0ac68bdb835a948b09b7510bb573d49000000e3d3cea772bd71d79681e1800000000000000000000000000000000135ca42f2103905748a1c416d82170f7d24b49ff3f859d6cb7493cf89bbae0217529a9edc835be1f9890ce105877af630fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000007884edaacca499491580c8c7194c0d60ac6eba95f7a81f63742451c8ed21a223ca545d5cc1e648b9d2dd05016b4fea20000000000000000000000000000000014c78d5d1a93760096bf6da73bb41631e94d6a1b251ed0be7bda93e4c50568420bd4d49e4a46e5be4bb204cdb6b0ad5000000000000000000000000000000000128a860c23a183c5bdd18b4a1853cb53475f1a893420bdf3271cc4a65a827eba6b92e1f9e8ac0d10c73edec5160c640b000000000000000000000000000000000ac14b2170042ee6561c34f77fca40e1bd2d40d01798417dd954905135ed9b7772e5689e6d4e543d44a4563da8c3ca40c14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d0500000000000000000000000000000000144df2e76821c19167f60630f50c939b66867a82c2a5f807e943676c876aeaa2aef2126bef7fc431f0c7b39e648542fe0000000000000000000000000000000005e463627bb2d22c25520c27c05cdc75e1f2ee3b91e8088399ee42ad13ca217284596e5404b4370995f71fdbf1c1c7860000000000000000000000000000000012323010d6aba1bc6b1d6e7f7e8c7bbc0838564b279d5ae6279f7f7d3cb5d96273e27e7096e9a8540463ad16deb3780e0000000000000000000000000000000019102ac6bb33bd1c5a158a584ce32308b6ee5679dd6d2acdcfa4b9c54674fecad7489d1e39c05b1ded88e4ea93620724d81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c6000000000000000000000000000000000faf210330693663c8a1d1fef78e211ed2542f7ffeddca3e19be3ba77ef211da1b8bb5abcfc96b692d74f8c7df40b0ce00000000000000000000000000000000134153a252fd8ec5d9aec08ba09a94c4416f95ff6f4ccce59bd400474c836af5bfd941f03384ca4bd5c56fbe81d96ea2000000000000000000000000000000000b4532ff1ceab2a3a177cb83a75c16a833a2ff28df447def351134ec4fcd608b2b75b1f8035ba7d40a737087f3e8c1c100000000000000000000000000000000127e3ed13384b69819b34ef8705fe9a66dd01b275f1f74c2c724420546b39c70cb7a8295a6c1ec4075ead4e3312b8b603a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c000000000000000000000000000000001351d0d5d531a63a5f56aaf1d7906b7ad2bfb4e9d823e2659bed4e05e7edc9179a7bbf13405ab5cf410b25c7d476c342000000000000000000000000000000000f0ec96128e058e8bfb6e0df1331887245dee87c4f9721fc7f1d20c20a2feea7a7078a4946803ac093477707598d59b70000000000000000000000000000000009399034e4aed13cbf197d8c4753285effa72fc53493ca316db11b39d5527b009aec6350d579f9dee22cd6d4cabd88ad000000000000000000000000000000000002f41ed0dcfa2437cad7b12a94501266d670ed6956196c438241aeb90474d17214eec5d5217090d28892d95f4e40055af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000ae208978a751f8921c6067ebab4190ac8d3608dbdf50222eec59460095b8ab2abadd97616c240edd0a9c53dd006e38c000000000000000000000000000000000905224b317a1e64d8af075b6db9de46ca4481458ad6bceaf726ba0f63e81e2a0322e79e70a5a82034abf00d47fccc300000000000000000000000000000000007173c3359f0c2e315d11d646a76e6f500c0922401e4bf9f4ccf2f0801a567fa653f287fdbfb878ba0d9ee12e25396ef000000000000000000000000000000000161d4cc71621e5df13d121c77105af195c2adff5fc6b656b0fc1dd6eb2518f474444d8bc526ae16387f23a4ab3f342f6541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c60000000000000000000000000000000000266af9991c393d3b55f9e0f22b0967d47dbc5b0c97947125e220c4bf9f4bc58d32ebc7bfb02b2e329c933ce41d0d8c00000000000000000000000000000000004cf5748aae8dbc1e4778dc85da575de2b6d9d346f5dc5ccbfd82513166384111f5e5f2f1c2f7ae367a22146d1fac027000000000000000000000000000000000095dbe68521b2cf51283a8cfea1f20eb7ae37e6e945c5f879ba4834d20918b74981f9e0eff4543a79ff4eb36d84a9c60000000000000000000000000000000007953cad14379ffd4309cef1ed6a2dbb73a93db0bd3a256753402e525bb62b10aaf22b662bb2c704865690af995e7d284b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000010e99f318111baeb1b4611847fdaea7cbd5e3ae532af667ad2498fb2e97b1eee0297e2811c7ae854b882f616da7733fd000000000000000000000000000000000e56cea75b4c4e4c669a492a6723fd60e351a66dc5c34c46469dc36cb04d2c23cfd4aeaa23d0e9e83d5b78a1b77696ed0000000000000000000000000000000018f838d6a582a52a508cbd6bbbb9cf515e091deb7a640e141dea4018af6593c001dc43a8fe4819a7877d9ecf53d5752000000000000000000000000000000000119aaa2ebcdb6379f7ae972cb709990a3e8254f1025cef308281bf7057295e3099d1f3127f76bd2f9ce0a03ae0de8e8d7e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000008f837c478e874b857f1c939a26a02e13061d50728c10939ffcf5e862cb177993e204590699a28cabc7593056617d433000000000000000000000000000000000432d9e66dc78bb58ab98771e7e8b5fe51835f286b488e2df6c1991fd36c3c537f2ce30abf24f9d4fb13941189972e39000000000000000000000000000000000b202de3708984f44f7d05ccd9e574a2a93a285d5ca262017346580be273c58f13165437dc90d1d4103d3b9eaac536ce000000000000000000000000000000001873e1251d9ae9448de8e7ccb7ca59a21bcc0d07a2819d140c06ec33cbba559ba90647494a7ecdec8b609b58cf7995cbfde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000000084e07b6576c73aaf43c0ef9c5666dc988ed93d1a106b71e4882fc0cfb5e710b91e5d5eff57327f5678f662f4a451d50000000000000000000000000000000008a29751f1653236a48adb5fbc59059c7137d36139574c6af97314bfbcc22f77a4c5162092762a26b5da7887b94f2da6000000000000000000000000000000000a4fd84c4d58cb9e18aeee180fb05f07c3e1d7ed8d09940182e9b4738744fa6faf600b6f720441e0ad6391a4d502ac040000000000000000000000000000000018b356be2aebca82c54988ab2a2ec58751ce7a815f3dd58a2218a638753d4734d38b74ca0e00bbc8681768f5d1a02b646f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000de0f22cf05620a5d4bdcf50ae179f23a9c089fd6eaeb14eca937d9e2480f1782a1c67df76e06191a9b87514daa8bbce000000000000000000000000000000001981cd1f260e7d96e55533b8e29867f37af507b4a58abd69e0ad6af2a55228ab1c82fc2de52deb7b7b7deae2fe621e10000000000000000000000000000000000d22a7a567ec8826391ee711768e612c403e3c16e20947ca5861185c24728b6c7e7756debb333e7acb53d86032d5748900000000000000000000000000000000016fad52e1e86b9e092955cefdf93a10f30db896fb519fd2ca12571d8dc8aa352cf4f8092e0e973d0b0c66df78433251e2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000000484bb2452158bca93dfeeedb40745bc5d9a9ad49afa20e6c29fc9ed1a8fde33ce508cc252ddd05fc486f8ef78738ac0000000000000000000000000000000003c2d6ff6f292b0f0e505fdfdd2940e72bf8c2837da4ec9c74fb593fe3318a9b9a8592524bb5d40f6c38ad871ab7b6150000000000000000000000000000000015f888ae2722713e1b5b02803a5b48d53116c1a4bb1191c9da77ded8c6ab49f1620b0f7c7867957d84503cfd3dca1be7000000000000000000000000000000000fd96baa382cceadc252eaf000d47d8c1e2085e9f274dd9dbb571bf85bba612836e1da2453fd914135842e2750796b54762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001820de289f62058920ac3d4bc60da023ac29c431ee429a10066f305d2b1a333ffaa906404af977cfd3212b53e66726b500000000000000000000000000000000094e448db84421e25cd03be3867125cedc7f77f286f404524757f3c1a9cfa28ab6771293da490a4d75852f515dfe1a6700000000000000000000000000000000097dec124970bc63d8f62f9133157d412f5ad3fd5eebb444568cf0fe2825d6ef6577ad302842f35570c9977638c6a827000000000000000000000000000000000490bdaabf4db27dce906cfacf3160c0fe25959df4af89301cbe6eeb29f72e4c55bb467841ba7d0750a59a32fc8b03d0ffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843,00000000000000000000000000000000193358b283147ed5848559d4d1533734822b0248dd17b2effa80920a853b70e7fb683b08aad6ad4dbb91f964ad1b3bb6000000000000000000000000000000000649be60ba72734db4cc307a2fd8be57857f60660d0c496c0dad73794296552e17cb2eabb3537ce677edaac1c6997341000000000000000000000000000000000f91ce27345e86003c99d133eca50710c0722cb35af2ce442ebd74b46d659e0118be9bebf32111c258e4cb4ab795a2cf000000000000000000000000000000000d76ad65233522b1e079fcfef4dfa80f163682d7984d5062680a5dd4cbccd5044de4705013c6bce2140f7950032f90ec +0000000000000000000000000000000013fe4afb94d08ae311b7442de7291a11e733d8e555f2da6f72bf99da780a8f8d357cbf3d8959f6aeaca7bf3f5b5bd10500000000000000000000000000000000025af713b18cbdb5a960371c2dd0317f4bfd0182f4bfd6b88d588b56fadc1a0398412e7e0a786c326aca8779ae384243000000000000000000000000000000000581c277053c15df8eec05c34267f62e63faeefa2d124c2b4b84d2a739ce5484641ce955fbecb901d1e8ca816690189b0000000000000000000000000000000005355dd304b9b60498a3fb1f08e1ba0c98db327365ca9a0365a7f1e5cb56aec43b7fd2b4aa104eac7b1c30b6f53cd422be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000016b5b3a6fdeffe5b9a0244a333ada4444a2e03771f94433832a4617be696e467b4e88ed80b174809dde4242bbb51248b0000000000000000000000000000000003dee846c5b84f89734016e547c63c02e4be07dbbecc86f811e2d8d3245f91205bfc055882565371db532240da1a845900000000000000000000000000000000194d53bbfa962def4da2a9bc7129fb6242a3922fe26cc4e603528ff31393a31d03dfc3463704250ea2ffa973ad175153000000000000000000000000000000000333768faee332d7468119b9e0469bbc7bc98a482562ff2fd9aeb6d9c67daac9c3da1db41c9e12224a2eff2feee51778bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c7260000000000000000000000000000000001910ded86d79f9b043bb79cc4049e0652c13d0fb8db2f070d695124d7a42cc3a2238282fc8a424fcd8d9ecdab4bb6fad000000000000000000000000000000000dc8d6caf97416928d2d58466219f054c6f28f49b2bc04d8a80cd46a308bc95aaca3a8df1914ab0c7da341862fdf47400000000000000000000000000000000004380ca7b1f7ef96295589f78a1683a51bce4b2afe50bd6076ccf5d07d35e6cb2ec7f74fa35097b2c0b9fff3f4797c1100000000000000000000000000000000054f492d7442b1c0d1293277d95efe822faa7d8881b9afde20db58d6267e049b90d0c8828a6c12540f4ba1e7c9ace6d84f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d7284000000000000000000000000000000000bba4761eda87a304a80180c2447a1d5a52f743015ea7c728e70d6a5defe3139c80696f842da3f06586be8d506ca4bc90000000000000000000000000000000019ea930d5733f4a1ace9fa0139d412d65b2886b659770e388894592de0694d38876fcd86d14580f9b92518d5496fd44c0000000000000000000000000000000002bf5d9a36d641d1259c1b30397aeb071b88844c4cf17e3de0984129d7b4d67865157ee2f682e7cf9d968fc07ce43618000000000000000000000000000000000f9a4f29868654abafc7ba935aa22d3d010023ef5112683a037a6c69b9e89374b256b8e1329eb5ad306d9f2063c22c335d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd00000000000000000000000000000000004f194f21373f09f8cb4984169890ad3855e814a4768c84e9fc97dfc181c60114aae534a27d3eb225b2125131c754ee000000000000000000000000000000000e6f88880e9645e35806d193f5d16799d63e2f9edd8ae28df54d19875c61857b0a34819a70ba3e9c31f00b5826b0cdc200000000000000000000000000000000193293c6cfae9ae4b24519fb23469e2f8dc4eda8524ee0b00c7141587b07c8a26a29841d41cafbd24bfbea2034a9c18e0000000000000000000000000000000017433efadfe9873dea9a68177af3d5dec4a13dcf4a710422d52020d4d145e2523ec0b48acc533a1ac7068c08ae6aa28bfeeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000011dc003f7542f6822cb872117fa658638dee2a15429aaa9dd576a7e895bc0a2160bc120558a32aab9e646354233a1afd000000000000000000000000000000000fe9ed8ba572ef7d1176176a31fa92a5ff3dc38b0183ea1e22618e3b3214ee78c53074d4c60b5056901c6f046f8210070000000000000000000000000000000006ef1c20c3bd88bd6787598dcfca52da4e5e0e7c7643af983c709b916e71fd15475da30d763ddba0899b182cbc070ca20000000000000000000000000000000001a38a2e54a44ade572ecde076038f5244f266cd99532024a377829a64c20fb2cfe1633367c74b5990febb08e776bc34edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a60000000000000000000000000000000011ce7b2cba037e5f3ff19b36371d34e287eec807178dad4118c6d43aba68623e182aedbf911a2ae5cf3d0e690ec3ba790000000000000000000000000000000017a617453f391e6e2437d56ee831ba895084f60d1a5f342e19a242b9661c703219d90a157e1b55f005f5059c15c179dd000000000000000000000000000000000746ab134c7f4bc19583a4ea4991c7cec3f651a60582b40c17b2d18cf6e252d93d2f3c2a1a3399be70512ec9eab251de000000000000000000000000000000000698daf214f2de44ebfaa36379862bd9ffb40987dfc8e632f14738c93c8e5c3fc7be9fa9100fb5f7440311cab34fe1897a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000000e4ac65ce62180ac602ad68098ee31cb747886e95a183e4f819d54af99850d70496e6952076084dc7bc2d3f7a273383100000000000000000000000000000000182c718fc9e5cc961426258e82594a5cafc36270af0eb50646d161fcc192c30d40d06647e14a282421638b31f378de940000000000000000000000000000000002bf448ebd27cb6270e1b87087796ca6534ff51ba0962f3290ee1d06dc18ed39fb736ec95632b483f44d3a9d0e45d1d50000000000000000000000000000000018b956acc1300e60b22bb936b2b52e2ae82e256f15f1415263157965179855137715c321d3765c5227dacb63ba2d6225f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000f5372651ffb40bf853f6f8396a7c7483c401b89b67e098ea888fde8d19e7552a006a127af1f3311203434126ffad85800000000000000000000000000000000050d7e89b21c7484cc5831885422fe7aa8e898df85cf7a3a275370623eb9660611610cdb829d3935f0d0955e0ac97506000000000000000000000000000000000f83a3f79f1dd110bdb8521e18a64490d567210801d77fa3c0c6e5cbc7285840da325cab7ab08494c8d516511eb189dd000000000000000000000000000000000f72904131be66380c5a18af4857ada7c15e88572197e100de1cfcc9fdb4306e446f2f330fefcccb41b676f24e3e0bf88031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000009ba6bbf102d390638ceb9259205a1856def2b3a4b5209eb3e4e54074347f71b6c06b70764fe85c8dfc9074067b8d00d000000000000000000000000000000000339c30631229eabc1230240942bdbcfa6e18f23bfbf88b7b8a8fa92f18e35d2f7336f0b819e875ac643b43e6d931e68000000000000000000000000000000000600cfeda6033ff51c3bf9182d22abbfbeb6db46c0fbe15ba82e72fee483744ba5a57ab2eab6f35927b4ba6d2b150063000000000000000000000000000000001530bba4db8a60bb6b7a05f72dbcd23044011d75221d114b839aaa9535400874472f94c849597174322291b5cfec4974cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000c636ac98557e22897fd101dc6c54d87060f460b4cf2c5a88ea14641e2a8a9395492fc5a946eebbba36dbe38f6f5c0c60000000000000000000000000000000007fe3a557aa93f2e9aef4ffc55d39a9172475e6595fd57409df3a7fe3d11558c4d3dea3396ee62f61190add83b85813d0000000000000000000000000000000015b04e0daf4a10541623e7523ac5fbe57dfff9ac17afaf4293c493c1982f3395980ec63046cb1d424c6dec91899202c10000000000000000000000000000000019617b191e9e493751b0a02511a18757330bde56722a72a29a399ace983db7114f84795e2b70bc9d670cc0095220454ed983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000017472b8c1cb3ec528400649fe7c39e3908b16ed69b42d967e4d225b694544e8bc7ce5bec87019db5539f1de39dc6807a0000000000000000000000000000000012b1c4884c37037a94f84c15061df5ca6c05c5a35ad9b37e3ab8e8297c9000e715fd2bdc3f2b485e86c415bf656392a10000000000000000000000000000000002c21af2933029f04b344be76e18ce499def4a0671a97dd9b6a108d0fb23852fcdc56f882be0319978952ef04a207a6a0000000000000000000000000000000015eb31e80fb162d5fa392fada8d43648ef54d4f9ebcb0e9652dd501f55a8875a16a148d42e283ea8bb2c5a38bfcc8843a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a70000000000000000000000000000000015e68e011ed063a9fd9cc8a806d8e3561e4f449526ccb6e5ce983ebc4fc49d61d26dad7db64f56ad5ab0b54fbdb76e61000000000000000000000000000000001617d7387fedcdd772a34b267a44315212d21b798c0fe1e7a9ed3caafb678910d9c9c3bd1fff4a3c8e339d0c90a865b8000000000000000000000000000000000e2b3c9b9cc10f41c4c0129d34c62d526aea47c77ded91a5ca3afa0da1801bba81def3ca66a978ebb2d1f3227ea82a9700000000000000000000000000000000096b6caf7b6f29e91bea370f91c2576c188b08b95f9df6c7df995fc9879c11cdbe2af86809468d472fcac8a89716d1d87ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b80000000000000000000000000000000016daa86ec04f57c72395d96b6ea5d6ba7cf2d9d4a50eb90f7121545f17c1ee16216f4086481d91e59fc5ed8542baeb7e0000000000000000000000000000000017a783d60be67206241e0bcad20e371d86d47d88ba1293b73f32999b0a1646967e5d031a5b28517f035168d7c7d7927800000000000000000000000000000000058f24fbe4e9befd8abe364c961f0ca4d9083260234a939bf6103a3e8f10a8381a9e3d74af7c13f159e5c7dcf456df00000000000000000000000000000000000485c9448fe3a069eb024ec43aaf563a98da09c02c294da2a94a98a95430e25b062e8ff886fb5fca240fba1abf7cee60297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000001230577527a0fde2e8e66b8c4d17594bdab8be1339866819c8890c600b35889d1e3a749fe15fd8182001e30e6420ca6d000000000000000000000000000000000ce03cccfa87229fa8d560884d8c7963276d79ae9873a23d550b4555cc4bda35a242dd2e70cc730b70cdf898609b3d8400000000000000000000000000000000174aab1f142fbb7a45bcdffd64c2d38b99c8919baf9651aa430bcd39613d7565196c18f0f4ee6fe05f5c40ddbcd4a67a0000000000000000000000000000000011dd23f59ca2a033ee5dfa50afb0c7ddeaec6d4f50e1866cca3f061fa03594216f005bc65b2c97ed1109c305e16222671b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b640000000000000000000000000000000014e1cdf4f10b11f47c15d0b6b7dfccb6081d05d116c8149989cce4f1c53dfcd2d0b7443677b03d037710eba813f6f597000000000000000000000000000000000c8415c7d5508010e0db1878ca663d359525b290b2f02c61436e945145a7a4e1b3ff4e27ea1b2c8d3adbe737d8291b14000000000000000000000000000000000e424ece68003cbfaf65a54dba51e7b0942cc53b2fa9794b4deb6aef1dc1ba1719cba285f9a1a59e71a881eebffe2eb9000000000000000000000000000000001404f9a3146b7201b09c5fd678fdbf2111c48130e82cc95012e5aec1df7e64a3b3c727afee4f603e620925686e126c0f5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6,000000000000000000000000000000000e9f6bedba1f6e2a3ff33e0e4b18fbf8e77558bf42e89023df6338b03a648c591486c63c2ecc8ecbbce23b3ff9a7ae6e0000000000000000000000000000000013d2526d83b4495b5af645d5a1af7bd40bd0ebff125e0fa14f10d1c08511dc29643dcfbd25ca0bee5705a56b26c558730000000000000000000000000000000003fa442ab532094d47f1a9111c87deacb15d80ca6e76bfb5f9b9a209bfe196643351d778b0c6d6b274b4799f733abacf000000000000000000000000000000001278d51523d5d9aefc0d3783e745da54f74a88620f2161090a398defdebf82d13d5b5a21a5cd466352ab8685b034fa89 +000000000000000000000000000000000a497e74635fde8caaa5c9dfe666b1b40732e58b93a72d39c8a60c1f4b262e1f18f62229a30fb8257bf895352ac4d249000000000000000000000000000000000c1b2fcbd7f78d85c73ae55f67110b575750bec353e55761de0ff09a9f8a2d916c336655d8f6a78dfbae13fded5a9c36000000000000000000000000000000000173893333d998dd32cc3e82fd7ad8ce77003192ad2bfa1b1d2b43f9466898313276b922f9fbd8e83e86b67acfd9ad780000000000000000000000000000000004ed01b702bbafc73dc1e6846bc944be297ff08d1dfef397603294c7fe11668cd0670d386a8fa0f0f02c52d47f54a11b34aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a6000000000000000000000000000000000b5ec74a2150dcf5ebe09f39234c4dfec623318889d92b0bc1f197a69650bc48d28a1112306be763176b691c6915dc7c00000000000000000000000000000000028db19af73ffdd0111dabf9c7d6879cc7389320a249f108b41be8b1d4c259d5889dbcbb48b30a288e26cd9926682d1900000000000000000000000000000000172fe526c62f9cae49e6d3284170e6339d5af256441590cae9507c61f987eb495d340500cb761896163cb8ec631434690000000000000000000000000000000013bbfcf9cd3167b47b48af5f5ed7e6d45a5fa38192756c9e140eb89a85c75602814f767c57108cfa2f726e71f31548f808ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de0000000000000000000000000000000000052d7a0f93142b36489cfa21d76c0eb96904a3ddd946a53b8a6730036d88d30336fd8aae3ab29ebf62a48c6e849ca66200000000000000000000000000000000198350abe8cc91bd675f26516d771422c128d5dc0af844c6c1af07bf04a1d3ad9654cbddf2de5b7828d1446c45e7828b00000000000000000000000000000000198f35692d5face8dda4b464ff48d650145242852fe189748783b1a2e48806294368ae0a99481bfe739fb4962f3b86a4000000000000000000000000000000000e3cf2e018a7e0acfee25bc3a82cb282cb377bbd72ce3044dd20e109d948f68720c27aea3d4663ee45b2de6f178a00ac58c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb41500000000000000000000000000000000150e6b03a3052d043da6514bf4ee09baf1a35b2a909473db33ea0bd4c6af7d7aee9a8366c1d08d2adc5998635eb0dfb0000000000000000000000000000000001370c2976b0d36fcb955e797087e6ccffc851d2450cd63833d6cbf52e1fccbbbbf9dc695ee45c7df01c2828051bcd79700000000000000000000000000000000048b5fad2fe0af7ccdf675328d8ff5e63b564d8436d04c55b23b6ab7d2aedbd25d614d1780963fcd03d569bed2085bae00000000000000000000000000000000141f94b4e7ba542707d0c3cb69f8dd79e499602952be2374cead840dc669c5ac57089c5fd60c44291703b872098fa2daae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c000000000000000000000000000000000eec51e0ddb8cf9914304e7766a7418e2854ca71367c1d2b3875c12b7dc5c7cc2fbc136037bb7ff72458027104ed3f270000000000000000000000000000000009fe5e8d1918f9b5865a8b97c2c2cfc8bd750a0ccbe2942070827a09d8e41ca795a86b2262b10462795f833c73e788ec000000000000000000000000000000000b95c9146f3f560ad880ca905b5f297e48905680b4613e91f393f72ddb042f6a6201628fb5f75fc23f2298cde66a6df5000000000000000000000000000000000a29a8fba7644ac96d77ee73a93dae23b03d81a57f6cd8cb4594b23571cc1f658f163081ae50d72e09c6513d1cd2c8bf72022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c012871000000000000000000000000000000001807dd8d2bb40a642fef693739b1df12fc787db0f031306f31970d0f59f0c97c0894afc34b9a9913726a20dcb7d5191200000000000000000000000000000000096fe8bb5e911c1ed9985ac08d864c7020367f4259a0d074973a26cc421a44e8034a7007f6d1639285cf8acb8b2d64a60000000000000000000000000000000014026d43eceb26b9ab5bdd4139d4f94349b273e43f27737f9ad26d23454cdb1d35ea793d21f057359d28328a82d5290b0000000000000000000000000000000003dda2a84bd1f92524a8ede9f5e81f0f64b41b24510f4e0b8146496a776d5b509968f188c12c2d66cf755e5000cddb3b800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc00000000000000000000000000000000011246ad07713d1916c662679ab757c053e33def437d7a976533f0ce80ff6ffc259489c26524ea96898c3747c4127539000000000000000000000000000000000acf66265811a57e47a4c98b40b12a37c6f439550b18215fcf856c167b7218397d7d559f852fb45077945a5074f460be0000000000000000000000000000000009badf2799f1c43a2e3859123aca91e894f86d6298a06a9127249100ba270f2bdc79cf511691bf2d7faa45ffa17490eb00000000000000000000000000000000069438b1d53efcc4277ea7b41cbd28a19f80b5380136f62121e766bd2845e13d5cb40b2f15d508414876ddde491a3830a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c01300000000000000000000000000000000112c4cc34da9e83207b5ea8a9251ac5f004546596f2294b3fd51b77ad8d8e98239d53ec4f527c7280801233175500b1b0000000000000000000000000000000011dd8627748c9a2b08524f88e560cd3944bfd1fa17e1d6e2e9cd025b04f2e3ed35125197136afa2848d24fb5fd19508900000000000000000000000000000000093219f9ffbfdaa60c5965b45a5d5bd923eb5d3971542ac147de3f591a5fbe31b30704a0061a524e2ddd05a45dfcb6a10000000000000000000000000000000006407dffb5580790e250a72dfe68a488431f61f45ec9df279217b8800f0ac1ab585d84e486487d5688735fe5aae75bacebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec73040500000000000000000000000000000000107ede23f8e4f273ac2647fc251008905966dde32339c023f1da3c4d35d483a55b54f4157a303e68e1dd7fa3f3b14c8d000000000000000000000000000000001739327f282812fbcbeccb12e40df049284562d8986b8d4559787e1d5247eb6c83d6b838d099f36d8d0e32da2a7999a10000000000000000000000000000000005e5b6b2baede3ceae776da5adf075c1d774e83d6129ccfe7e835862686bb4064b187cc0be0cbfed37e5cc039f3a3fb6000000000000000000000000000000000249554dcfa53f73ef8f08daabf20c55301f75c8ce095cd794061c55e195221602a54ba54260980bcdb35685e41d0f4ffa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a700000000000000000000000000000000048b7fc5a71787231f1c7ed2134be528fc8d8f77102bda806ccbadf4f9bed79ee94b43c0fd3e5b1d776fe73d786872d1000000000000000000000000000000000152a1f005a64e16949d7249c3b391d5c1e0ded4893d0ce926cc666f0f88b64e8dd6ec4f92ddda18127ec24cad7e40b40000000000000000000000000000000013a2e1e7958a53307adf3beb32a88b7c493df0e37e074c9105da3c09bbaa01fed092fce2b1800790c6e8af3d30ec5a81000000000000000000000000000000000e2d405806764c75122c1b5e410673b28759f26af7489cfa6f35c6c0dd16c508af045009853f3329cda4a67948232bcef0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf2110000000000000000000000000000000000a71d61dbb3ae37230a2dceb54061d5f8c1ce645e20ec39785c229cf79aefe238959b2745e3b50e4b3c20c7a8e2ae27f0000000000000000000000000000000010e82b8dd5faed6bbd5755c4e5a88edbb3511d3f4442d1e44b82cf72a6414bf6558d29e8907b07f71c00f537637605bb000000000000000000000000000000000d8c93f1984b742b5a02777b706970215c7d8eeeb7377cc26c3af9005648c2eaea7f7a3177b6e049b132ef6bb4b188da0000000000000000000000000000000000ff082a252082499d70eaeba6d5514fc8d641404b48b2ecb256eeb40d9c6b68ad5af58556c9dcfc5667621c549b8ee760d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f9000000000000000000000000000000000f4cf354b8de6dd2231448bb235af3c84daac2db49abed345da6ded50eae93982a4f2c27b07ce725a062b07fdd9058fe00000000000000000000000000000000076cf19408f0f0379c7e65a6675b9856782990986f5c6d7002e9c9c74b95ab875924bd7ad5e4812844f6d1f530e58deb0000000000000000000000000000000007acffe32f96f5e56557965e3db8dce87eb7140d93608cc003bf4a43fb261bb7360c576da0b7c4dccdbdd9cc53b5c5f8000000000000000000000000000000000eba1c668fd9323d42d6a82d9f075cec2d278cc57122e25ccd72cf8b5a569552cc6b0e9f88d23b9b7af18f3bfa0cc820006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d60000000000000000000000000000000006157cb6e2dfa2733d4c489ec0334f0303ff1ad410f329cb59f99a5fa3ed2cf84eb7d2f231078ba5db0954badb58425f0000000000000000000000000000000003dfee394f4c140e2cad61e8675b26f91244880d9a0b6798d6111090dc9d080563db5c89b7293dcaadc74ea5849a08aa0000000000000000000000000000000001aa1e0683014d5b6f99f469a0b7beefaf05a7ac0298bd1a3e2da409f6cf856f70bc067610fd705a851cd70054df9562000000000000000000000000000000001571b129f69f3a6717272ff75351fa053f46294f68ba3f859208d6c91ba5eb9a0f2133a5e139d04e38c7f7aa303451768f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be4000000000000000000000000000000000c4e84b7c8e46daea67c8090b27dc28b7867b89b92f56232bfd8ecd9968b865a057957292e79c6dc08162f9e91e6a4b2000000000000000000000000000000000b8d1eadcf3f1de6ee608a4a0ebb7defeeaf4e251bf07717a6a8e50c07223ca32a2ef290f26d0de14b1942e02acba39a000000000000000000000000000000000e901b546a4d3c68e4432f376c97f42ecf0724777956c4ffb1e6ca4fda562e57be788ecfa45ba3afadb439c2ea546ff30000000000000000000000000000000007ffe01da4fbda9fe5d47c3bedb4b92fdd71ad73fa272b071a7a7d1cdce7743a535da7dfe05a43d03368eb97fff54b2d861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000008da0fe413e31ca68f84032f23bdd5399e01eb3b5ae47033c6834a39645d7b5cc2ec937067b91ac6d83035a86fa841f9000000000000000000000000000000000b950b982323f747782d9065dddca5332940058a604829e31560a6bf9b03ec72b09cfb87a1cd244ec694c7cf192c37ac000000000000000000000000000000000f4afddd25eac15d2248c71d76c9aa27323f75141820efeef1ab4f5003141053f138d9a7d1a901961d0f2c210ade27ed000000000000000000000000000000000217b1800c53d53459b00b8e463df1882b2cbafe85043f08093a5414e58ea7fd4dd933c601acfd7c154d0e4ce187468a2dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be55000000000000000000000000000000000046aed1acd19201553bb6a88fd6a6c0525ed44822d2a4ed3bca48a0a2b75e76cfcdced8f342b81ce03ffa72e667b3bf0000000000000000000000000000000009a5adbac43cca3402db016a2138342fae89285ab1fa16d7acaa9c3ee2b4e3df2641f7392355996bef7b1578ce1ef119000000000000000000000000000000000c8ebbcbdf2ac3fbb553a2e589f4b7c259a1621b83b14fd1927f92d9f6cb27e82507d7943ff5930f0c14b9fc38c9857900000000000000000000000000000000105b729f678db31d04ceae0aa37f9cb0b0319c4da9a1a4702a11bfe3a5f2f1f2af09b9cbd5ded5a930e2e65f4279a31699893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23,000000000000000000000000000000000708e9b926f2536731b02b6b75305c549da58e312d9c53701a993624697af2f3469af34dd4634467f8c98a0f721cd9c00000000000000000000000000000000019185b84fc0511a048e3c39bc10334c91dc1052d323a31c8bf325479a2fa3e4228f8260c0e725c2b89d5a0319e6fbed70000000000000000000000000000000013c7c441d5cca81b48d43e908d6a3bf8b5057cf19e4884227cefa9b235103b46edbe01bada06bb9b620ebbd016d537630000000000000000000000000000000000431182c8a1eed66073956fe5798a894be396403c072e766cdc262b719d1779f960f4aebf61c1bcd4d005d3c7413e52 +00000000000000000000000000000000199f555aa5c651183f52470e36cde438422f41c9b2d1947510665254b74ba0bb9cdc6e6a1283b0c8f58d8f009eec46900000000000000000000000000000000018f1d8f22f43b4649300aa23ac92a2e8f17e7e3853b912bbc8e90588125c371084cb224c2d54dcecb4946ff6db53cd02000000000000000000000000000000000efed0bcc83a52f0faf9e260815da8d4e5286396081268485aab052a96af8eea0112be6cce1486b10b60551ad6c810780000000000000000000000000000000013a3b1ca3b9b7d50083c10d36997f5f521d4426af8d2905aa5d074ff37e218a0c96c74387485c2dae24c0842b7a74cf0d8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000efc5a5c506e94ad2754e235e2da866d9c46342f14d518f12510c93f13a619f6bfefec50c146d6d6170f190497eff229000000000000000000000000000000000fb91f34356005f38c9804250549554cfe67ce195d5e218e4e1b1a4fb904257bdb68d6dfb013e8e85fb5a4cbdbf0f21a000000000000000000000000000000000f09903db4c41fe3f11c6f0cdb7c31a131033e30f52cb66ba10c2e7da1ed8a225ef280d313630121701f9a490e8a0f5c0000000000000000000000000000000003484f7e8f7d67ce40b4cccef110bc255d91f61a4e1968a9ad37e25058eeaf39e9f1ff89c9b2e515388a7c1b49a84a2c33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f000000000000000000000000000000000444215c3d4a7d62201ea1b69890e2ab90b5f5c6ff56fdc9908634c7489e785521b8dcd7ed409cf09c585cae8414a3250000000000000000000000000000000002d70674251a0c9ba76b8bf3b70547da77cde5592da9204954abd6d8aec82799cc0fa4fcd42139357043fc867b3d0e0d0000000000000000000000000000000018c57fafbad2351a3da695f8b523443e8c763dd7ab875caaa6a494a498cc40b1c0d44488e2dc80d1f0bce00a2c90c67000000000000000000000000000000000125d5a87ee3f558b5e1e7664b0cb95c195bcebd5e43b930fb47d15eee4fd50b3fdd0a401c9bb011c326acc77645440137894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde480000000000000000000000000000000018790123ce8b3b72d626493a16936c47770a9b06ca45b17c6fa5c7759f088cf98de8ce7b3b5d6082e9e42b39acf76f79000000000000000000000000000000000fea86cad8b40f315d8378550f6d3d831149339a8e8dafa77295859ddd2417e8f5c0ae2baad25fcfe00de14f45a537170000000000000000000000000000000014ad78bb2bce966d52b1fe1a273bc07f2f24b354465edef6dbb1e0123c7c3d7550983b3793ff1c7db846e88eddbf33c4000000000000000000000000000000000c0daa6fba40ec59f6b34d413130df5d9137297d1b7b71b83114a6570fef8e7f83d6f5689527164782f92da4b1ea12e8fb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000001186b7c78952e5c32a9393eab07ad4532471595bc2c5d8137c61dd7fe6b6ca3aaba82dc205a559bdc15421a001b7270d0000000000000000000000000000000012d56b6fcec3d6511d2d723601cb8c9faabdcdd12efdd0e2bfd7c9292f2c3bd7f39c6e9aa53e6955727f88ad69c5b4f10000000000000000000000000000000006a5e56e4a42b04c03619c78232104f1f1f39e755058a19354eb230f2f09bf486b2586817aa6b88f27b884957ea0226600000000000000000000000000000000118c8521dd4866df907ecb252d9ce7a489f17d0f240d054a5dbff6c35895ef20b205236aa6e5be6f0825f9df87878ab783c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000092809d18926c20456857826491f55cec17803e9e7d43f22faf4da18ede3bda15e3319539017ab20ed1de2bff490a33f0000000000000000000000000000000018d736b967eca64234f4e0018e5d6c902608e265037d9b8ba42dcc923b84ac62599e153e1c7d00e552ecc5aac57d1a5d000000000000000000000000000000001804aee99219354d4a5c46328f0658a417c85c6bc89af6db29a4911c4b0cad5638fac5ca61cc997fef3450cfb4a6c666000000000000000000000000000000000bf99dc4a400adda5bc89762e9011dae8ada23b284e52e2d49f75f1c75247f6282c95a36f7a72f896ea308131215404bd2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b1000000000000000000000000000000000604f8bde85c0b26894e0de155cf896c911bca47533362a0b59ccdad0dd64108d33af8262d3ca2ca399306723f2482a8000000000000000000000000000000000ec10d3777aa54cd0cfd84b4062092ca3ac840a24e8e8aaad5f4c275e4d45091f838ae522efb1b2a0fa42229157297d300000000000000000000000000000000132cc70638d02186116773b31ec0e571a55c1cd78ec055fc647ab09cf4d3c543e0552d559b3daa4e99cef031e583e61500000000000000000000000000000000194a6a32a269692906b64feef9e4e8cd204e560b98db8c66380758d2123babae871273b4c571a1570a317c13a51d0fe9120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f0000000000000000000000000000000016d05912dfff44912bf34f242ac85eb55bbb8a21625d45496c76d057f518352528c6632d6e8adbbccdd5983d13c26953000000000000000000000000000000000b10aa1402c15fd601ce605ade8f25531ea8f95cf592bf4ed86c4a3aa847dc8aa2369655ce5348da30a897fa8d71ffd800000000000000000000000000000000183f5a2f40da0a0f4598c6b9ea7b99f8cda1d85cec0e6da5365d7eaad1e9a3167bd647e5e654985f395ea72257f61e5d0000000000000000000000000000000014e615e2d5072c1b536ffa607f3a826ce297800b0da329fff397b6327800ecdc879e91f1e3ebc26c18e188e1ca66bfd66af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a000000000000000000000000000000000f3cd275d72a637bcce855e2e20727c6e5a1f15bc8d799231d3a7f61311d4cd2f58cf38448675aee9910c1a3d0b576210000000000000000000000000000000019efca445312f568727948c803d06b8d4e2c5289015740f2626fedbc0047d344aead06ef521ff7e139312fa41d1c107200000000000000000000000000000000141384e1c9f79e38bbb0bc1025c079741b93f56e150df58cf9a61ec27c2877c4188866fa197242965e3feb47a78c68380000000000000000000000000000000010638286faa6c45cf028e8e3d200edcb348560e2e35902927391401b3155240b62a40784db88e02b874e128e3a2132b5f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b000000000000000000000000000000000c7fc4216767ed298206bc142862c138d78726e2d39afa18fe5732616c73a965d95cd2032d4b2f5a4d562be48ba6885a000000000000000000000000000000000928bbbd76b87f58ecc850e1aa4a2be11b15a81786aa7ca8cf0f6cc342db87b66c435f009f88ad97b747400fbcc651e10000000000000000000000000000000019f5ae9f06f2bc27a39bafacc7f3745fcdf8c78c9ae8a3c066ffd704aa4117eba773691ae43387b93e86d2e2de3688700000000000000000000000000000000014360a7ed73c05ef5fe651321f7e839c920bbc1896636143b88357cbf76e15da839bc7e1f1e629768d447c9d313cec8e23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f50000000000000000000000000000000000b9d955f9d28f9485d0bc4a961f0acbf09ee5fef38ccd81a2c73cf87a461ff1bf28d4dd1e0db3ea522299af67bff93b000000000000000000000000000000000889061e71866001b0760f68e20c7c0c033d782e6e6752f11502a0e8b6b70277a985dd13dd83424d1e5cdb9eb96a01c0000000000000000000000000000000000e05a26686667f44de2bef53c36c82f1fdda13dd3f7f8fe1fb026273dc4dfad18241d732ccb757e2b46ed8317dc69fad00000000000000000000000000000000038b55685b02231905dd9a62a709c0f015cf5650b3fa469462b3e9d06e3af8092d998c8e08ee61db1fd5583b0809a38996c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f0000000000000000000000000000000006649a8eabb25fb7793344a0b29325a88294343f6c69612ee9d9002154a49791f6cd7b37b2bec69fa8ce11722e9f8a03000000000000000000000000000000000e10f2f3de16fce9b9817085f0130e1839d9aae949170ec16834732a9b12f589a2b00f17d2fd3416ddd020b7421ca20500000000000000000000000000000000016b51112b3c7c42a8c2a0fa7f286ec05cd07b6cea5675bf1132de99cf42b450b3c2a8f02ec821529a14a2a0fac3a751000000000000000000000000000000000f471ec8b65bde22e003500d1d422dd0d163abb424dd261fac588333755cc5124acde328085d8df852c61e024155564781c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c000000000000000000000000000000000ec162d22b6516c309efb6a4577c5631a5807bebddc5fd1be5446e4a64785d49eed80eba2e89cfefe484ecb8d50440a600000000000000000000000000000000070c252caf6c56018af6b281b829a4fb8dbab850ba0446d233dcd4d87bebac00e3e5070bd41898dd561526498b153199000000000000000000000000000000000a0d76d1205c1f520d82c85bac4473ea7cf5f68022d95b1f04d06062197973001234d86921e70a94e478eea85264f14a0000000000000000000000000000000014c6a07f0d568f2103ccf8f61278e916458820bcb61fd91479b0dee874fe36c063a34bcb14ee434b68681d297637b5bfe44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d3000000000000000000000000000000000aabac129385d145243c3a1f357ccc963ff14867ad039827488128ac639dc62fba82ace66f889b47d8eac39802bc1af900000000000000000000000000000000062bbbe8c72cd6f8626484bac159b7e28c6c8c3261edc6a05a30c308cc9e56db17eb58f62ab755f04a5c87e58c04c7550000000000000000000000000000000011a4a439d18501142350229778f67bbe0c9b948229dcecf70a8b09d1df6c54801a111c603301da2377d4198d09dd51e70000000000000000000000000000000017de3d9bc6fc5f415d04ecec013a635fa200699c496f4d0bdb5cea7d446274dddd0a7f6b06058fde43fc4f1457361558ae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d98000000000000000000000000000000000c86590a02fb5c9568af4e69611f09980cb5a7e040c94ecdbe64e40005783fd3305a5657a5c6bebca7d20ee123a872b4000000000000000000000000000000000bc873a9bc694171d2606f4efa409897e03198a61b1bb16ae90f0d12345d2650d93c46e0c22b717e2f0504b8983515990000000000000000000000000000000001df9160ac3bc54c0121a9c69e9065f4266202f755c961bcb8641d13720b82ebd73eb3804ba44769fb2d75144442f1c400000000000000000000000000000000045e9c8ed2fe1e5c9a2a5bda75dd60f6bb5dcd0a805f68c1f662a5960b025ff29c8e21857d2a61bcd65c747d2a2da8ef6a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e000000000000000000000000000000000f94df8d267339bb4f51b21014ca6d685f7657d0f0bca189e53cf19e0e5e05bfad773c0553daafd80c86f302b1907ba5000000000000000000000000000000000d92905addc028a1dfdad50e909c77662e10e4689e7c8a4a0174a3e1c746b361665b65e17fce02b6c067a5b8d7a6a6f500000000000000000000000000000000183444f0665790c48bd3c07545115a11f82463a092774234e7b33aac1094761f213235895e5e61ac1b0a15603bffe2140000000000000000000000000000000003cc2cbbf181fb023a5f6088d8a9793b17984b3dddc8c3ef1a9f82f8f436002610df60b2d35be212da9945bc8108c0bced3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5,0000000000000000000000000000000011f85691799cb76213068ef4f997af66c349bf707295b969d85fe637d4eabf54f3f29e739152aba5027c1b55317a27210000000000000000000000000000000019627f9570f07f44f326b5b3ee19bc477e92d813be2865e00da93135645e02e6fe5507ac4d50085b02149667794609fd0000000000000000000000000000000018fdc97bf0f88b2348b436d70ac4e28b5ee5ba21e21e94808b8b9e401c0c7d688974fe203ebda0b23abe38018876f4930000000000000000000000000000000019e28c9c936ea5a0b3b41871c3afaaabd53a93902e44a96dcb7651bce7e6143d81cb695fea8b94aa32c09ec030dd9ac4 +000000000000000000000000000000000703481cf48efe78fe8dad34184edd1765a1d01846de74a45b43d4721bf1af116c229f969868b0e6e851f22bdfb0451300000000000000000000000000000000063d316d495b1e82380c5b73bd61ce7f2159e7714c50e374e8a91dd56731dbe03a3378bf8afeccaba5fda73b4c2dd166000000000000000000000000000000001012cb2f6578065c93aeb673f447ce95fb42927ef9d12e07968ec04b6a604d785944620043dee5de4de33d59e67d64f20000000000000000000000000000000018cc7cfc360801ecc420d77ee171fb3eac3be0cf26b3f36a6cfb7c6adae7bb74c18071daed8fc56b8fa639ea138267928c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd10000000000000000000000000000000011e0dfc437a65c6fe37bb9e554b5138f68a3c52816807bdf7d98f13cfaf86b37e9669f4e0db1b7865d910a309f16cc200000000000000000000000000000000006f2323e01591a7db1d3c7fa1a2ce4540cbe0396cc55baa3a3e13650a6f6b926a7cde0eebb45d359edd52137152fe360000000000000000000000000000000000066bfec8df4ab5f5f5eb369b34e8e22fe32abfc00ac58b68f2d3841248fe5843d6d29ad012249fb9ee851e40b940dc2000000000000000000000000000000000f4ea977d9249bc05dafb682a863ed17f7fba0a06c4a13cdf5a836748664183272eed96bc4109bc5beff61c5469e221f8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000fa3e3e77112774fd6d6b560ff88cc92ef8d009675d0ed65705398ce727cfe786684da50bcdfaffae97d19bdaddd81c00000000000000000000000000000000019e98284b8b9f53faf3b73902cc322dd80fc330dcaff2a7fceb55db6a4b0f7f667297f5e4650c797ee337985dc6b54310000000000000000000000000000000004e30acf2ba66d842575c8679caec607fd090f0aa2350464f3b6eef22e2b9a1d9d5fabb0f3909f1c19f6b8f27c53b040000000000000000000000000000000000ad76b86e32f84ad74bac68909da0c271571606e071b13bd92e387a8a16a1c4002c5a5e94ecaa1e8d2d6e051e19a45c763016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc100000000000000000000000000000000019bd07479b234bba974ca2f39b317d5f4be33afef66c1d69e53c44cb5e44c679775ba141f82486424110d186561777f00000000000000000000000000000000130002de0d453abe9052a5f70a9d55de74939d1c8e6ad5871a669a867861b1359322eb98539f4a21597d806aeca62d18000000000000000000000000000000000b2f0c649fdb37216c10762f510c3bb4c789dbd29c4f9a8ff39f74ed1a96609c60473a50f5ce3f6535e4af0f2f0a150c000000000000000000000000000000000893b9af710787361a32fbd19c380161c9a214a1bcf3761563424b8546f6068ba650d9caff3e42be63ebf4b6afa2de516c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000019805c0de5e232632228e2772dc79712e3d863bd6fe56932b29ee99870d2ce5eaf90c73632d1dcddc093e9b6b5b0f1d000000000000000000000000000000000405d77f4b3c44f99a956ef375879e62df033aa408127e0fee013b74675a8c7d999c6abd30f459693086bfdb326d67af00000000000000000000000000000000110f2c231998aca3d76e40055a05feb37eba76cdd10106719f2300f57906424d7eb6d9f85115b78b7371ee60e26d02b5000000000000000000000000000000000593a4721a67caa7cbbe1566611a1d48532c68adcdbb67f362c9ec21e08aaddf6b5e09a9a96df9a89bc25f11665f3a36aaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d7100000000000000000000000000000000151758f1921743d116f1c4adfc09cb68b3ff911329e2f6d6bcd04beb9c109568c796f328e1f04381a995fe89aebbc49c000000000000000000000000000000001388c73b1db46bdbe70540c99db46b730e157a23afea97648d73f9d5f7e8b073ed665eed9e9e2500152c87715f1c4d4c000000000000000000000000000000000284ad228867ed14ade5a327ed951ca50c87f0a669e59b7a75d17feb54bc5d685245448a912590179db1e84f1eed1e5b0000000000000000000000000000000017d3da7c167733dd88f1c39315e47cc80c3310cc431989d4cc50ddb22e9fa481c5dc02d94dbf806c4c8da16ba5b24905f6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd490000000000000000000000000000000016064871cb68f748939a839800afbb018fd5836914a2b76c51818e764628a76817c7ea329e6b2f9de653c8162a2a2e0c00000000000000000000000000000000082fa03cda4c617a780caaecd7c859c5251b56b61f70fb3ea8c05b4c11c030adb8a96d715c1325ef3dce9b20e8065b6700000000000000000000000000000000174a245baedb7e1bf1368212620b850151be41ebb00c977d85da499223c207ab6f1a1d94a51aa9e90d07764ec3615b3a000000000000000000000000000000000df5b81cf4b008480775ff3d7644f546a60382e92a98b03deaa4a20f831e69e14a893ffa731c4ae9ee237d747149a9080cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c64700000000000000000000000000000000019d36b8dae5e1083e687743f7494b7f9dd0923024df81e2f83c78743e227ffce588a16630201b9909daa6c9207b5f430000000000000000000000000000000015659059cfee7850e1cf0e49abeef2fe5837cd128742e62de20dc734f1bba343aee1c9f1a59d920a0519995561891fdc00000000000000000000000000000000102b7221257c40d9adabd0db3ec9f6348487187ea1110773fcb2ac5ce210dfed167a4d15e605e9d9e666fd092147a1c7000000000000000000000000000000001402ff9770d27d2d82efa6abe4a181e3c1d944e97a06f670d9e46b24f9900fb4a838b32e17482f25be9b6f3240870c02ea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000001745ea52686f87a39fa42ddb5b0f69368db3757394fa7a1a93eb20c398c26415c8a7edeec7334df5b15345d6174126b0000000000000000000000000000000012b580e6fd228f087c7584cd95826e56d1c074cf16c35286c45d2067a362529d241c1e24fd22cc9727d423551de1a1f700000000000000000000000000000000104b46c42a706c61610f8c0434894c7cb9ef878cd0234f8aec0825cbb8297bed3de349e7f6037dd19a159103ca7753390000000000000000000000000000000010b781b3cbe6f415af15e37be7c60dc6703e6e79618cb3d8d9a5ea3b17c00822aef1eddacad66a646c009dac887bb070caf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea30000000000000000000000000000000004b172c360fca555e65860c7a294960f506b562e012ddebad5803bc3f4b93159c16cedb73f339def9cd1beaa0912c93c000000000000000000000000000000000242e37775a042ccf59e99da667c67fc49e80e54a1b438a74fe306d668059ab4dc7d9e457adb45e1f91b3e6bef0a130f00000000000000000000000000000000186eb83ce3abe66b8760dcc0d375eb783d175b0b2f36cc08793d8a86cf76b7618b826f50c6b02ed586394abe4efec2f1000000000000000000000000000000000bf780324df1cc5de325a796f1fde367eb52dac76c0632915dfcaf01f5acd6ae890dbfc2e505bafeba7fed8fd63018c2ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc000000000000000000000000000000001231b52c8a081add6e5c250caeb9467335933c2ed66826e4ab44561eda9259acf926f22ad0df8e8756aa51279d12bc9600000000000000000000000000000000051c46bb04d3e035d324de681c772e4561cecc6a5bc4ef0a0cea56618e09b3f39f5085e208229e50164bcdcd4abdefd2000000000000000000000000000000000ad7ee610398935a02c3a7139185409d7fd4681ebb74a239e15d1c092ea913016d3f585d8224cb1d109ac111660a94aa000000000000000000000000000000000903bb16efb052b99e9c46f3478b4acf800a173b35b0079d7728fc25c9415c8b05ad520f31e6a3c867245f64355cbc080d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001788efb21597aaac29b7bcb9ad6cecb89267c757cfcd8893c32fb13c0f3e1af7fcccb9573dcffe8d9220292b7861cac90000000000000000000000000000000015f85d3686148ad62d7fecb71920981117cb8759ab249d0ceb45f9e4687914536a1eb16ccd0e185d1352a8d2b4a8ee7a0000000000000000000000000000000015d8ed94c0415ee0f7c9854841bac5821253bb2ed4d86a61f494cbfbd61614983e4279fb17802ca68aba4a0302ec1d8a000000000000000000000000000000000f950a4c8aa18f4605e1252c367dba1e170ad00376a8560c2fccfa7d5487b0d1d5885cec16a0a17d81b5a584d473853f7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b82000000000000000000000000000000000da25ed9154121205ab6843f603a38a6892887d2725f16ff87a5218586c6139188f46da5a42b5e05982468e8115713ce0000000000000000000000000000000013c13ffbed4a60bcb8659013b022012ef3a4400f506d65aff7ffb1bd5a9a5e030a298e417cc1ec8ee7ebc06455dbe61b00000000000000000000000000000000132d83bd141c434326d4772de7f8772c30a6456de7adee7de66a04bece4c0d20bae5526c8eca5af5ef2eebd72c90d54d00000000000000000000000000000000131355c5e359081dc86e0b15c8aedb4f2016b41e8428051f5132258eaf4392fdb63a91452dc56aca20b7ad3263ebc8c92a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab7190000000000000000000000000000000012dbe1327162e4176b4988cec23df0c1b0075d0dc51ea8afbbf98f00891511d9023cf7538c5705d59b6d6ddcc90b101d00000000000000000000000000000000036c12c7f7627b6d6fcba9a303248c38d784a3d1d0ff02e550565efbab68c5116e9a88faaaf09bc72bcc3358e9dad0ee000000000000000000000000000000001578ffb68cf12dc9a5ae6fb5d822324cec9e3f576ce08d45e24fec9203d36a6461c5b8ea6ac50233e8893b07ea6e71e00000000000000000000000000000000015cdb43c82b20b8ab270b942b9e625ada9283962a7ce95eae156aa4355e1123ff87ddb1cc85b2a94bf36102ccbec33fb2bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da9600000000000000000000000000000000017041e16975850e6445c7b4896955eb5eab383ad3c3031aef04e8fdfb65a6d52c9e647330bfbb0f0eab630c9f9ef7a12000000000000000000000000000000000b62757ccfb913ac4264692053f766e142697f598a3fe26e998119b63a3abc7fee03db32a8af36aa21181fe9ea89d12c0000000000000000000000000000000006bbb842a889d7ff3c1eb5e0b16e3a921a11d28a251c488a8a17a29edd93672fd15974a7e972a34c47283c583cf2d29b000000000000000000000000000000000e94e685fb1751f8720b8af79aec7b245ae8daa195f11f485f2c0c5dd68cf39eef848a402ce2342a6b3398cc7879c6010f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed00000000000000000000000000000000100d3fee47ae6c8c7981c8cc615870924fbcb34c2ed817d6862e2e6d0b4612222a4c8332c7d51b58ec59df6832139e1d0000000000000000000000000000000017270fa71c34ec84043ef64c5dfd61614b5b3bd99204f9f70994d71498219818a5f16843c67c668b06aa5ad3a6ba8a0a00000000000000000000000000000000057948c0ebd14664bf33fb282e200fa0e641764a353e8347586465dab0c79ca2caffbdc2c6d60b2d7c8cb6b088bd16fc0000000000000000000000000000000012747eb070f2de18f517648395109bc08b4af3f04d98e23eb6b516199b4eefc5df7d57baec736987139c7b03b573941f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e,00000000000000000000000000000000128c6c0283ea35c10330502d6aa849a909df6b8dd927a24f08072135b8e21e40c495c42e742160363772569189d73ef40000000000000000000000000000000016d78dba1e0feeab46f8cd38681a9c2f6490ecc3a6e79b31caead256611d133090a4eaed9691a87b66dd1c2ee50d5f470000000000000000000000000000000016de93e176c950975abcbc692384996315a98065db6d6a6214472e5a338e291b36abbcdea1b8be6162fe578acd669abf000000000000000000000000000000000d7155e239e9b15ab64a538b0a0bd53936df4ebdc3ec9b0b1d494e4df780bd014425759e9743c9b262cf48cda01e945a +000000000000000000000000000000000a653e0c24eee1cdf8e3652809de0cd159f2c541981a4f43936e7d41c0f97ffe2f1e1e0d1032f0970023f1d27241a16a0000000000000000000000000000000012d1d8d2f96db0e5f97be096c961e3b90ef3d88492fb756894979d2e8104791a5b9a43888043ce9e543691f15d2fdb650000000000000000000000000000000006ffb94dc3c2d07830498260ebe4641b2cb64df61cebfffaf2d4ab5b6ba92cd75de209e8d7915ee744c4db5352ff239d0000000000000000000000000000000011f25722cf9db77ef8adb9caa250175e12412e6350b494395a86c31e1f5dee6c89cc6603f1dfd08a70344cdc44aa0c2df3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d80000000000000000000000000000000006177a74e3551770e7d906222590108bae7b97a5dd3bdd2344fc12e7005f2c1a188ab9dffe68f5ffb0cc36294106f15800000000000000000000000000000000041b140c46868767119a6ebb58562570732198854c92bcc070f2a8d9be91282a70c5ab99e75cc9e5064ed628aa5c59de000000000000000000000000000000000f318ee33fccf455e46add44922bb6e99afd4354bbc79d7550f8d12d3de4f75e5ddf4e62624b116f91aaa80a148adaf9000000000000000000000000000000000fe012bf88e152eb62c0c906dccba469abe591687573a59d3debe747b7d895e4b0755f16e67fa9193a2fd338c04d243a4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000b26c6e0106d4efbacf2dd0d15df17209b1306f388f493c096429c031bc4a6a535b64cb02b400433f948fd6004df2fa200000000000000000000000000000000061853cf1a32fdf4c370cd413754ea584d3722a08d58575075a7371e57a7bdef95386ed72f91c4893377f6b551dd6b1d000000000000000000000000000000000ebf17e60718c8563a1029ba035dbbba75e7191b4339d5d33f64bb35f34866081f26f4815e01b02e8330e7b7e9c428cd0000000000000000000000000000000008ce40f92efb5c5be48c814018fbbe45f1be45f5b607a6600cecd50d8f791de7d91939ab61204c2a1337c3f21b2c9d26518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af0700000000000000000000000000000000123ef52cc44f36326b33234ab3348893bc722bac3674e43385b201f372fe4ea3569d69d4d561e26f8ea903e017d7376a0000000000000000000000000000000005b1707ef61ff9acb9e8b4dd6922daaaa2d8a7558cb55b1b9b96eb6d57c23f50a7955763c9b5ef04f52b09be8d55f4b50000000000000000000000000000000015b6e35d14da61e7a7fcbcb0dddaf0071d8d2d89f7179f44851947a2b9b0535d6fa86b5cae9713a73bbed909a4c6deaa0000000000000000000000000000000013463e135b1fd460cf042dcd0226e229d60cc2beccd8a1832df241e65a644159722a14297c0033eb499e5890f0caff1e5561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c386551000000000000000000000000000000000fbdf4a533d355e232723fbc97352fc5d7d3d199934883a61a9ea116830bdf9e40d423256225d9a3458134332ef6e817000000000000000000000000000000001195f0ad227941c5e383c48f546be34762d158e6cee585650b6ee987f7b98e802f678abac6646832b30b6e12e90948cb000000000000000000000000000000001820d5fbb5a62140c6e8cd105a70fc2f1ed84e254c839deadae5eadbb75e1c33a07ad12ee92900f55478e91958a3147a0000000000000000000000000000000013849bdcae33fad27f16e91c6d46b9678a00491e3d70a8db905db4b1d2c6f02a29392b5b77c1472052d6f4d49f14a16737c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e2600000000000000000000000000000000188fe1e394b567d71099fa13b5c8a5891636d83b6b8a08f410b080658a0663deaae4dca1afe8b9023b5e8e573c752c92000000000000000000000000000000000f66c65dab8e1b2912fd5285a4c87821888532f5107075cdfedacc4d7f75c6a74b4828d0b4c3a2c0ed94576654a7047d0000000000000000000000000000000016af44a6df79c8c9b6f1d8aeca24e024c454d7b94c9ed386858dd35c4158cddcad1207f9fc3ac9e3b748c2314f875dac000000000000000000000000000000000315e5e4f78e9fcb93aac78025e95b8bf82ce4c840cf565e0a868b0aac22950d62f7becbf8039a16ca3ea66a7498327d981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de4000000000000000000000000000000000f9a61dd1b3034b8cd7408b0a44c8d02f4fe0e87778d5d34f5e884ccc9e2d51eca6b6060b46b66843e8247b3c794e19d0000000000000000000000000000000005c47fa7799a0fffcafbbe4694dfe8d0f47b60f712d6319e9a56ac459a636460e700e2af80f9c688208978aec7c413af000000000000000000000000000000000ab1c55fe2207865ecf12e372a341c776d24c08dba10702fce1cd2c01eda314852d81d0ccf1c3423c2a12e8960677f060000000000000000000000000000000014f8a1964aa3240d788ea40bb51abc50fae2736a34120ca9585fb2d5bba4e5cfa201c83be1e00ecd1c46fcb2ebb4eb809913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d30000000000000000000000000000000006441fcaf5e68b10e7e511a95e56b9613453ec6468bb126c5eb12f204c9681c69b5c296320f92a6fbb0b848f8ab5fcd1000000000000000000000000000000000141de16aeca0a2f991e9fca4b6ce8fbab3d66ee3ee4dffb0124384a7d4ba51864a53e005fd34516c92ecab33165944a0000000000000000000000000000000008543656b5495bdb726109cd98fa18e405648fa88cbe2e5fea5380b7d0ecb207f0343dc7888b9945e55156977336226b000000000000000000000000000000000b53d4e392f304225b1ef363a3528daca1d3a6ad64ee99d58491863ea432a29cde5edd4f390de45a567cf32112ca5929188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe0000000000000000000000000000000002e0c55a43078df575efb2c99b27c5632dd1c08bf28b6c0558081a78de58e4258d1b57d94ec6fa157add04aee06e7b6e0000000000000000000000000000000006d3f4f0791431a56fb386f4bb8e6744cd19b10bd0f2e65e927371ab488d3735e3b83400ddb25ef9d740a8620821b0ab0000000000000000000000000000000011e9cdfec8a8f8eba0de6809485911711149ca0ebd0cecc033e2e5ddfc195fa7de671a686edd2f56e5f7da7328dfbec000000000000000000000000000000000171f188afd5d9568cc5648aefb65cd715c0293344b9aceac1031f10b4a1e4b9fa2ab11114bd58f28aaa58c10ee0eeac65525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a000000000000000000000000000000001651d9bddf61e5e54f86609c2479513ae84b000ad7defd840d9619a8361922dde81c999d0e95d8a3044c46fe0360c2030000000000000000000000000000000014a68c248808e826a3bb50f3c1c1438483cbb9da8dd67a0c9633a47f733e6aa7deb4a13aaebcd50de6e8e8f00000424a0000000000000000000000000000000010c8a94b9e0ec9965f6c8bd0c4279102ab682a14fc3c22e9640d68f240ccecfead9a2c6e69f7c8ed369cce7e2da50d5000000000000000000000000000000000181493e8137fcfae203e1b45189fb828dc9eb56887c89aaf9aad0380fffada423f0ab48ed068ba4e67a2b01a16abbfe55ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc57000000000000000000000000000000000e3e33fa4d85a35e8707419ca6d4fb6a61ee6b07ce152adfbaf6b5f1d7ccc253b59f91e4545848b3570bfaa804ad9767000000000000000000000000000000000c923a4de074dce3ccc94698bf6445af5847c0e6f22f225c589f744ec83ed0810913af2a6d04bd55200ffc738b31b01200000000000000000000000000000000186961ed1c6039476eb6f13bf1b5f6627b3b017ece57a4a5f33db8ef12347fd507398a421932d3d2a1d009f65d06e42c0000000000000000000000000000000011e10ae0139f95a2f1144810894fb98f6e5e86ce67877b949a2a7134c446dfe53c23dfbfd12919b24975f26eafa249216ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb942800000000000000000000000000000000029265ecf3c81aab289c98d9cdb917749ceef56e2e4d59de2d6c83907f394ddd1cce9d093a20206c2c1c215493c41c49000000000000000000000000000000000986ad139381e4dbabd6beba179600e1c782f436f84a7bd58cdd96a22269f1d937f88f25059214fe2a781ac519aa621d0000000000000000000000000000000019e296d5b17f78b3ffbdaa2ef5228fa9dd65abdf6b2c5b0f99a708c4721797b3b156b8df98a5a879f17f095548555da7000000000000000000000000000000000349677d4719445d5525cd65e2338463d232eb75721ca51c48fe52d0fbd299ddbd6cbc12546f056bf212d5700c3c4100854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000016f5d5eb3fc3ff178843a7d21d3dd628bda120321ae44206d88f07ac001651428e0da95d3f0676e1bbb969a300406ce000000000000000000000000000000000029121c539ef1d7b9888497a362fda2f8402adf10a1bee11b53cf3dfcc6f99d5026bc386f86a2eecd0c276494878104f000000000000000000000000000000001320a402922f2a0bb287464854be6782046dd9dae4c0cd94efcb8ad8e0f37b7889bc97a3c8b4d3b3670a6924c8ee23ec00000000000000000000000000000000101fa8bb2c90b755bfba9cd7a98790b7bea2ede4c806fbd9f2006d10cf87c44172d4ba46ea40fb75afbbaa2abc3b6e9d7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd0000000000000000000000000000000003285cb099b04b6acd333c7ac76c839b6c09388792d5fa1f2af0821e49dfbf40a06803c4cca92512bb76d073129a48a00000000000000000000000000000000005b2fdbb25381b3b67814bf6cc0a4cc17271416d16ee369b723b1711d968c355b755183f0bce519709723250515ba32a0000000000000000000000000000000002c7062ba4f642b95e028a364b0698b801f48af3c336fa09d13d83ec6cff10d210b55b23cad1d999889c83df7d1ab7e10000000000000000000000000000000012cdfdc10bf46097083294259754453e084010f7ee928cf540d44c80aa4f601247223a318700bc24114e7603922d15ae923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f54760000000000000000000000000000000000669d760352e34a407aef8e141fcaa9468257b12ec08ec218f49f0769f3acd5068c6dc9d251a1b2af02a2d091f8ad0000000000000000000000000000000000064a7b4026ee3115cb730e56c4b9bf3e1527dd0f0ac6015f43d30a2f3d8d8c2659cf50247e70ca3c93d7e0a404d9faaa000000000000000000000000000000000979ca2e81663ed61486c1f841c19d83549388d798da72feda82283406d4964bc9991f876a6032382c35b605441ee7da0000000000000000000000000000000008d92cf77b44c516c243f3e6a8a8d3f9d3d7405820ab972338f700de1dd9a66d33b4a70540a30f630aa81fe1cb5bf057e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf900000000000000000000000000000000198831a40fec54a210a63f5e00b132bb1eca6408335b85a75e28be6a111beea3b99d9f2fe5091ab0eba0f082c201c14d000000000000000000000000000000000fe457f8d215f390000efbb7fe7193ba02a2ef78e9bff6539995f01604fdca9fa3c010276afb90215890f5a5df3ae21500000000000000000000000000000000076771823180422495d89c301443a9d1fa141716e5e27205b8cb6b461a3ded7e6f196c3976cd6ad56b2e6ebb6b3a70860000000000000000000000000000000007f666efc677f6f767828e1291bde0ba0ca445ddb2d69d5d2fa090ca49e697ce4e00f55d2b706454be6d68f012d76efbb6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa,000000000000000000000000000000000e1268a5e2f654c4038647a910e6cb4bab1d6ca3562ad4a9ac18444c8b8a3fdfbd35acf37f9203041fd587a5175ce86d0000000000000000000000000000000005e701a8ddd15ecb0231b7625f7868c81467c140b2617e60440a9b348a192e5248b1b3c087545cfb6d173fafe48d32f600000000000000000000000000000000071327f52b1325bb664d32c513fb12afb96dd8da23dd91bc4c3e8ae5f97d6bf673a5d03bb8bdeb6da3144dedac200dbd000000000000000000000000000000001852b86d3ef45aaeb691f454a345ed61104cecf0f444e4d841f2bc0402ea1291ef056eddb3fc3929ef4623f31016c3b5 +000000000000000000000000000000000fcd3f253d018ef09a7c6e8f36662ab4190867a197e0c42a0b425dfb5fe61d57596ada28dde0b093676ce15d03406d20000000000000000000000000000000000df00598337060d603607f3b8dd16f277ce1882a2e9ced48e1944662323efc29b33c807653f31583a5d2198426019ba70000000000000000000000000000000009876c81a76986435d34c6d44d51cf1016c19ceed2432ef1e68decd64da2e31e42372c1a41a514b0eac0ac103ab6f43800000000000000000000000000000000121cf298ff8f610c64ca4a887c52cbe940333506ef2afecffe266b5b585ff737395e2c64adc23b5bd232250e67c7a62613ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff566000000000000000000000000000000001885d5cdc3e0e0c8cffa7519e6914e5d85962d07633970c4174ae4587853f13970a1f5d7ccba97458b9b5046847ad29800000000000000000000000000000000105b7c0ba96d5ce32d7447351ded3e3f491a0e741e921447b91f22a23b64c2d749055a0593e5b47f0ff7815e1a4c9943000000000000000000000000000000000cb88fc10c94642ae7e1d7275bbfd51a2d40e9b29f3d51a1ceda577beeb131eae4b17418f9f358d47b4b9c9ca4960a3b00000000000000000000000000000000131a3e080b1d4e936d97d255b07b09a6210b5fe6900da87b5cc595a72de2b6ddb01809e2dc63ad460a2926dd8d3b3b2ebcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a00000000000000000000000000000000066b31c0bc4b3b9fe420dc095d551903a2859556d86e210c96480f1d31d449d85ea292e2432babdb71c151c7b215cd6b0000000000000000000000000000000019d79a60793957745077f9233aee7a4f096515eefa7c49473f09bbc73fa0ee13a2a30a08bd7f3bc1d5c412d671fc37ff00000000000000000000000000000000006882160e4fa8ae2c2d48ae389d8f023e2775adb7a815edeba13728b8f6b343c45788c8e9116445e9989e01eb43e1500000000000000000000000000000000000ce53ab2d81ebaf4a85b3e12a6175ad7fb6cfbae207a69a0fe2195ab916fcb582b097f09d9fc565b837925f68855c4b59f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000a30d335c035afe459dc262fb1bd24dc0bafbc08fae0bed47e4e204280eb96595fada9c4332df1218748921bfb1274c7000000000000000000000000000000000e37eb189560211d6fe56faa3b6e710878a21907fdc1a9f8becabca290c24b8831e28ebb48d06bd822300fd09b4d103100000000000000000000000000000000104842b88b9df6a7b8243494eb11eb62c89d1ccbde9f55fe221c2366d6bc9149178f177628c6fe7c7661318640295e570000000000000000000000000000000011df8599d72b85ade11261076e02c036be5dfa3b6fab4ff72ed7413a879c0a0742be6c36a32d0829a4e3171b0341c6a3995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c0000000000000000000000000000000019f6634435be45b099cc739fe5c2dfa01f61fd2d466d5ea464053e2d5acf2e0e9448b1bb7770b5ad426f8a872c5764400000000000000000000000000000000002bbd52efecb10b3bb6f8bd04a5751042d8598cc34e2837184cea2b5953ec125dee871d1f2f57ebc84849e3a7ee5abe2000000000000000000000000000000001962b716342df9c13c21d89ab5b8c4c0ca191440fa709627e0f240a7ba518f4c95adfc5973b6ed0af591bb54bd00937f00000000000000000000000000000000089eec676276c52bfbb2593ef0362c12a5f3c1a0566d5aa862f5f5ba1580f4dadb36c15fdcf0c3910ee14487ff146c8997b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a5000000000000000000000000000000001556d081a489eba4fbb0c20e22b8cab432a9f6ff459ab9b0e7ceacbbd46c8e24a2ee70151b019a1b4bfe47d934afede30000000000000000000000000000000008fdd7391113e8d9865ef48b60acf921b17c50744e6ad62fa24abaae54836b3d59a7441371bdfdcdb251d252a43aed7b000000000000000000000000000000000cc66cdb1fe32beb91b05922f3920060e7a95467381d62f2f036e6268af4128c9516780ea53e873993744ce932b901f100000000000000000000000000000000151f94dec958859ecaeb810c4b1cc7a707d0e1671cd4a1e3c811910bc8b95c6c944167dd280c7fed22f92ce7650beef998115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd178100000000000000000000000000000000004f88568c7ede48d7476175f1d2e7ded4312c24934f0d47794705621f8aa8a5072b86cc41e187f4aeeb49bff17a4c9d000000000000000000000000000000000ca6c579e86a68b4041150fbbc36da744d359028993681c34e66c537eb8a0a0d55aeb9b8da7fecb844104dabeb507805000000000000000000000000000000000fec63c57d3d3ca98cd1735b2f59217e163ca53b07b4fabc4415b98377d87e75f0fcc9b51c99a57ff61ca8d0016a206d000000000000000000000000000000000940e9f93f3ccbe74c7be93236a2c440b213a014ed51cb57fa053495c3d6f6c8edc08ba8e10be26e5faa898162d67fe327370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf500000000000000000000000000000000055e4dd9da22201b5eb64e3b9eff2eab614c48450424491a85c18e05f50659b88e862490edd11ff980b06696b60c35b00000000000000000000000000000000018fab38f58d3d541666bc29b9e94cb3940f1794b2aa851d079b9aaa1cf742b07cd6dc7c985c7e4d7d3fe683bb15d618e000000000000000000000000000000000534de5e1c1181e951b437fd17993e995fd4aa2f6b28fc3612cd4db615de742e12d66c03b9ced538c1c7cde27752c190000000000000000000000000000000000aa8580f1da71f2ae9ec26f3b6466813a40ba5bd3f89ed0d42695d420032540194617fcc2f13e36219fc0cc3886a69c36bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f710000000000000000000000000000000001804ed7677fa3842bdc3eba708bf4fb7f7d4eaf2f1a46193c861595f64196398622df4358b9526f33663138b24fef1310000000000000000000000000000000011fdd7e1d0c5adfbbbaa69ce63c7c54525091289e4dfdfb3de772a8d5a958581cc23933deadcb8856540e2d0dc564dbc0000000000000000000000000000000013fcf17235506fb194e3adaab881c7aba4b87e5aef739e0547b858410e3cdbff0dab1980b1b30a7d03d617179ae545c900000000000000000000000000000000004eed0ca479cc458231ff969ebdd4e33732953e9f5610d78d4753b99c5f8cf73c742387b8e71b9be074fcc67acd71cf6b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d0000000000000000000000000000000001082b7796d35e387df689bcdda6e0316d343dc907822d1a873adea050374962b164ed27cea0e1b834997f8274e4c5438000000000000000000000000000000000b1905979a90c7a61f4ee2cf3a9f4d6ed4c724c9e216981b8ec34fb9b528018d237771ad620020efc2c3cb104df667cd000000000000000000000000000000000752663e72390108288ef4de3c3ea409c74e7051505b12083c41a2e8937eaadbd8cd61f96f7991722226fdd02dd8d252000000000000000000000000000000000f8e4eb7a3c78b8040a115c42b5d2fc69405f8334e948b8553f444dfef29bf3920892da431cd8394cf61f24e356e95694f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce11070000000000000000000000000000000010884516bb9916084709351ed8768c6105fa451e08d5acb233511254ddbf4e72baf9c43b56b4d7dd129a38f5b34ee5f0000000000000000000000000000000000228fc5fffef746419cc69abb17cdc63ded44892b8c5d02f0c72bc8506a61d15a74ec4ea0e1d78f555ddec07f418539500000000000000000000000000000000048a4192c204b7441e871076d91d4f610c347c2d71cf495ffcb2e2ab808a8c1a549eae96e657d756d9a3b94db2892a2f0000000000000000000000000000000017a94d2472df89104ed96e24d166f922bb852b5ad80f80188fce65b08d39cc3ecf94991c6bec5dc12f9337e7c087db2f8b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa3000000000000000000000000000000000f6293fb0e19ec85f43a1a02df9f59ad4fb0e49b16a216ce097b8ec59e781fdf176360d8492e8b77674ae2c0ddb1da70000000000000000000000000000000000e354d09aad68fce6cde40c787ba1e4488999d5b9f3fec25c9994b56bcccaaa746c958bd16ba271485f461b0d4e983200000000000000000000000000000000014fca0851b0bfdf2c69fb346f23b46135d2b7914bb49e297a0c1304d8c2851ff6bd0a0bb364938dd44680fe86cfe12e300000000000000000000000000000000164e23a53103dfa332e5ae09c7c898b95773c20f019d8b794a6b49594040e2e090db6a8047c943885dca95188e89a63b30e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b200000000000000000000000000000000146d37241ce4f71017e4423dd0bf907a12c1364ae9fc6dfe535c25e5e99e03ce157cbba2675829b396a69f92668107280000000000000000000000000000000000d5a992f5357615f436d95fa516212812f6811dd1f1921ba4129e84e3d487b6c97520995d8a65f6771dbba9d150c7ab0000000000000000000000000000000007b01f86574a9cb7eb3b9a19b6040055a5c11b13e7071078d16b9ad71f714ed28ad25db9511964b156ee34db22385cdf00000000000000000000000000000000154c29c6e2b21a75b14159b183e625c98a04be1850b22d314225e94b313619f641ead73130c1d6feb85abd8c9e172f6323a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe00000000000000000000000000000000075be2703b8416fa07a7cb6ae8841dcab1e36b0ea24231dba617a2fed3bebf8d952d31f68c149dd17eed136fe37b01880000000000000000000000000000000001156563f1401b731cc23c4be59e69b0e6a0827df4889cd9ef9e11310f679c1603a0d9c9679c29b8dab75ae51f49bfe3000000000000000000000000000000000663faacfaa92fbc095a5dd6b1f2dd141e248f84eff1716ee71bdffd4d28ef1f4c88828e3457e8ebf0daba1416d2d6070000000000000000000000000000000018f2871f5897aad9ff6ac45a9c0e78be8f312f07af5f1dab2bc4705558070abf367f1782af896288a7754da82bf1a5141ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000001618a284286899f501f46c4761c93b68bc8ab3157144e4013e242e1678cba20a2d978ab53b4b43145dd6062748df541000000000000000000000000000000000c25da737368775e41ddcd9c64cf99a824afacb1d404f1ef46ec7fe4ffd89673648c5207551914e6e0d12c57e7d7682c00000000000000000000000000000000097ff49c4872e2da1f6c24fd6dd4667f0bef4eb30fc197d13e8b66adc425e39841dea011d79e4d775106a19ea1978f4c00000000000000000000000000000000147426b7d9b0bdc2be051d8f6cc4249014e1bbc2369bc32eca94684483f50ced2c07be6a320effddcc1ed5cae455fc92212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000000cf68f7ab056c4689af95b361ee3e3b1c1c48f18b5aa655cce1a2be217010814b3f07dedf6f9a7b835cb13e2afd7136000000000000000000000000000000000dd6d0fb94048dab34410dba4e682f020ed54a655099fbb6f6e94a31511960f0447d7e94143eea88195291b225d11246000000000000000000000000000000001864c6ad3f2f794239a179647d68734e23b3520b79952bda20acf2f5afe1b76bc18e35b852d35a5cf3b02a3ce86f640700000000000000000000000000000000015ea24562d7bc59d813b77b2a4943f9e98842b5a41c0c7026077a02ddfd3d5fecf352d4399f507fb12ada4ac495ddece9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37,00000000000000000000000000000000080f0e50f90e001a442965ba7900997fcc89246742590b99add6d90428d3b61516664654bc8fb423f701e85a342a668100000000000000000000000000000000003fa9e84ddd754047649b7cfcf5bd78852abb298b3bbe6575c4c7dbc2e7595499a9f42f379a2463aa74f29e5c73a9040000000000000000000000000000000009e72d3c418726f6400b8cd8b9b649005f3b25ade40cd6f77a0c3cbdbef461e917d4453c9e07ded45301d21df4ec44db0000000000000000000000000000000015a06cac223217602ccfba4f4586cb184994bf08b324bf977dbb3884c394aed0622da7dcf5712970554d73b18e2733c5 +0000000000000000000000000000000007340f432a5cd5aff1a1d98c6ea1c94be24de2d15a4e112925586c30979e23a5db93643308d3299e370b1f26bdd09eac00000000000000000000000000000000155027caae88381a60af71b2fa770e58efccfbb7642f5ef6b1591bf77e415eb117ab564aff8d9ebcd576f813b793ad2c000000000000000000000000000000000f604238d1b28f010ce8e45f2fe61d3ea20b902a4debbabcd54ce0ecd44a9540fe2bfe847178656fef0a5fd7e6d012b3000000000000000000000000000000000d7f503ede395dfa5682aadedc98bfe28d3fbfb52f42ecabc9eebc0e0a6616d3671604709f28255f50b62bee641d2711f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000176e1f9eac4dab0d253c0ff41b7600437b53a5ac5278d544a9620648e0bc4dc56aff0bda973fd1338f77fa174d8b13b90000000000000000000000000000000012919a18343cc166e2dfb92ff07bbf838779ef0479985bb85b3b82f9d0632b3f7a19d387f725a21729a77c58dd4d1d1d0000000000000000000000000000000017eb269ed75fe0403021ce70505bb60a711c91c551931605bb2a0773fafa07aeb47cdda382c0aa64f40f5e6e0e6bc77d000000000000000000000000000000000bed8ca999a4691646124a140fcc17dec02b74bd28b599c807abcaaff13bff65aff3892897652cd33b4ba5e4cc0198a9c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f000000000000000000000000000000001344d6902f5fdbb59a4c975847db0191beac284eb17cd92360e59f42cd7796cf2aa282bbd4cb074c4ee10b489ee3f2f60000000000000000000000000000000002158eb3429d0532792532fcceecc404e95a879be68b3685ae94016ca3762438b3320553ab6d5fbda3a0b615a04d996f00000000000000000000000000000000118f6fd8f60edf7088a0b4b49338bfcfc9c38be230460d7516f317b27c07600f504c8cc87acb0c95515c3acdc1b125ce0000000000000000000000000000000014eb422d44ec6931ac9860a6a017a907e8ed76de91bb7557e818dbacb19fb51457a1f45cca91f1d1d75a3567a3375b5cf2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a95853000000000000000000000000000000000f2c72c53fdb1b0cd13a1f20407c64c46e4a0e461778b0e2d48c4f20be7c655c639b38f758fa9199b8395f706df10e7a0000000000000000000000000000000016e6c75cdfbc20c5dbc2dbd1caa66be92911264d407ce3c689ef3ae1dca44dffacb4c0d8a78ac959e47ac5c454f607bc0000000000000000000000000000000011c5d80d52e864b0a46fb48488f497fb85f51ac040c77b1d01336860b972858c0a6e59914112f6cd6c1612c604d26f56000000000000000000000000000000001136aa7eb63d6f85d665d0539975a9a51a9a3f5bd8731910c32130b1ec8b07c39eb42e4f61e7d22bed933d9fce1810581022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b000000000000000000000000000000001306612f5119d33f177b8804443d14d04c8e059e28f63aa10ac6a1b25975327f378d5d24f0236e05849f07e99af93ae20000000000000000000000000000000017340f8887292264d498f84fce4af83573aa6cf1d57d99d364f2b84e1734fa4f9a1e07ddc81a2135ad5f5e0ed2989585000000000000000000000000000000000f65073250019ea69339379aacbeae7520c1ae10c8912ff827b702bdab2e15404cfc939389587364d811054b7d9f2b350000000000000000000000000000000019742f83ba0c9d36aa1d595fcedc3cdfa6c6f08579e66b8956fb32ac03530114ed4266738c57175e7a10313c8dd42deab80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000b2513f906db531d052e8e6f1cb8d7d3c41c7ec3158b370268d1de204ed8fe7618b64ae35029d1718153b5bdb8439dd90000000000000000000000000000000001664c367a2d4170f463c90351cb321608e2a49fca6f3258bf10d32c39747084cf9d2c38d5241888aaad97985cb09a450000000000000000000000000000000014de15b86461cda9f1be69f43a9ceadfe7b7d1548a206f3237d93c7c01ee554c4245fb73827ed0ab72b99a62215faeae000000000000000000000000000000000b25e458522be9fbdde4554b1a0d9af157aeb7d3ec1f89185b193c0429125dafa554d7a531ef9502d443a26112b940b8f397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000006862c0b0e3d7bc4507bea1df82080745aff21b7549b372085776be2f88aedd4cff00ab8258aa21e63340963bd0d937b0000000000000000000000000000000017199c5ec3a2dbc1f1e8d74648cf8da247e35cb07df22629b3845274d29e473819a31bc344f2a2bd6c790530cfcc0126000000000000000000000000000000000e7fd1ff41d86a02014229c5085c886988dfaddcb60f5c7c81063e8289aba846337d61bdde57e276fe6c65bdfb48751f0000000000000000000000000000000010efa6aaf7650edb0c74d30125e36cb67cffd1c7f57932d92ab4aaf36f8d9245d7c75dc2b3bc8f3f328589b16e26230e28e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c5000000000000000000000000000000000935de4b16f5f9c0accee77b5820cf36c24aad9953d40a2409b7e6040f09f85da7d2252843f9f8005316146caae539800000000000000000000000000000000008a8c542111951b32bb0b50f7631f8938d22e298193edffefa3e0f5c861ac8205ea9b865f9420ad74cd22b37c5cb56200000000000000000000000000000000012ddd660879a1f52ae6284e14f2ae6ea381ff3f321458cb76bfa566b04ae19f3793468d0aab652a82671be74332a3b7a0000000000000000000000000000000005eb148c35732f7ababc73861b71fe4ea5e25bcdd675e975fadd0a9e0fc54e175b2e39dcf0323f4a9802a68baecd25df3cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000000bc351eebfd3f3c332268055af1655c8729cea44eaae803607198cf747280adc0d3dedba137828834af3e7179ccff4c3000000000000000000000000000000000d8a6cca17e1c6ceace7c0ab1333ba76ed6c3b114bf99ff80127c6a17eb0585bf6fcce871deb7385e9a8896a21c065ba0000000000000000000000000000000013222db97e31e28946adecda10c9ccc9aa9fce33e0aca51d6483d2f0c5bc3f33994ad516215f8333e22167164ef5459500000000000000000000000000000000144d3707b1898d35c65ae2c89b1570971a9494e8bd23df835f565059554eb7b5cb66a6eec890058316aef43d6c6ff55c8e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c5000000000000000000000000000000000138da93a9a4948d41a6fc6d057a217faf5efad863b45ae8eab311360c033362213edb0ff90bad6c95f60b8e1131336e6000000000000000000000000000000000f41766d9b57b3210d315a2b8f90aabe591c1de6037ec79c0d72a283f0ac3094436bb97b82b7ad12ff4f471a41227bb50000000000000000000000000000000009aa4f5b674782b7adce6bf75ad676480f96a58d68dd7ef8d1fa488cfab794f06e7754e9315430189eed265913db8b300000000000000000000000000000000004e2a4a48f02079c0ed50c1daa91b1216af481a982c7aa64d8ba90449ed886cdeddd0cc08f1f8764f7f8c5988fe677f5674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000019c927bbffd96aeb9342666e1974d30f9dc215e8eca41c24244c63c106331ddad20d64c79faf8c5baa45cd30b561e167000000000000000000000000000000000523f063de96c9b77bfe5c5045a007e155b45dbe68c5f1162884f1d942bb385bd34c2a37e5e67e6dae4a23d600d75d1f000000000000000000000000000000000c221006f5bfc8baf43826258d0588d7c0fc345d68de1add1693bb897959c2cfdbb9c165e82c0c787529cd7be85afbc50000000000000000000000000000000004218e3d52b42a4504611929f94024326f38e78bba2aba105db3ffb4a51f8906b060ce2302e22ded60714d652a234c1f288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b3400000000000000000000000000000000189e5063a36b0edd736bcd9f997f4b08c62d33b27560e2e2b7b40039e7c63b75757f23746e70a330110d975ca683941300000000000000000000000000000000013393485ae494b1f1467cac9a8840c695d619aa1a78c40674038c053f264c1e20481f2005abc7f0545346f5a982d05e0000000000000000000000000000000003f2be501504f4d37e12acdc54b3280671ca0762a063fd3bc04473ed5a051cae3767044c002b7ed1abe88b2143af08750000000000000000000000000000000009d5952af88514996336e1ff19409e3e4eb3079f6dea22f9738f4a331ce842b151e0b842b68cddc10a711afa6d3242b256e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000a79444c673e630f46bbc5a9e06e8c023978a78e3c58d72910a04c3733ad873c0d0de61448076b2fd3764cc17d86d94f00000000000000000000000000000000110cfd215d67d4a091578203855fa0e85feb4dfd0076fbfad20bd092fb91b528a4117850955f5fb6568fc5844e17bbfc0000000000000000000000000000000012ece0577512182c50dbb4a485256e705410108d9ba9c8d57780d49e2e25a0f89ed1fe917797b902aafcb8f7d98fe931000000000000000000000000000000000217cf1dffac7ae162181d43ef12e3e88da4840f1573d7ffa271f64d8d54861099be37b644e96e650dc613975d8a00a4ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd0000000000000000000000000000000010bec428b2865aa7c077c168dc28dc549481c6f8367a5b84cbbad661b0225cf0fda3e840d96c4e4efc36c20d48f23d5d000000000000000000000000000000000ded3a1e9e2eded0a11211a217f9355070361f0a5887a7e19c74edc8768000311cb9dd8513977ecfb45416cda0908cca000000000000000000000000000000000b99ffddc79e825f0b73f2d0229d66e51624d854d00bdee5aa7a884dcafa1888963e2a2149db0f6e40ce3c67941a391000000000000000000000000000000000147618970c71965684bdf0d6cbe1de189bd23bddb2b861c9636efdcb7a96dff27bb1ac70485b562e78485a1e8e56531cb96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000001a402aba8fb28dd37f1be11fca037baa99a6b57188ccab66208a50bb6967dcacd1943cca73e34f6b2e2f72407103a73000000000000000000000000000000000c0bd64d043fa4e3ea566cb84f9139091891231ff500b67e5fd451805f79003f6303352a4f0c236063d60d9088fae88c0000000000000000000000000000000002861fa7d0222711ffcadac86e7b9e7b494f5561c22544bd0876fb6e1b2e680d0f7074c2800312cb233de2412ccbbc8600000000000000000000000000000000015945f0c83e738a17cb1283d08d63ecf12a7272bc62812006ed78254bfc45ca7c42306cb79bb16ed17bea600a4d62b5d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb0000000000000000000000000000000002268793f6872f7715d802c0d96f3b3d850249d8e70aaa97f19793d2c92e7cef384aaac603eb51525c7ceccdd0211fc40000000000000000000000000000000002507d680a2db16746810e966d1ba5547ac98d08c8402aed0859203e6dae0cbd87a9ddcc05119c1ca08fca2fd733882200000000000000000000000000000000192426b6438b2abc7386599afbe09081ed4908fbeb807a65bcb7c6676aa76e5e0c2c87612cd109cb124c73b9c8e0591a0000000000000000000000000000000017f125a2ef5246e7a19e1b2741b31b9224511ffefe63ccfffaef1b7949e88af573e267d6c7617ea97bbaee6d50eef67e1ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5,0000000000000000000000000000000018c2f533f464f9768308a56209711cf9b6653e8d38591d782ae2374905f99f75c0d42c63af4b534056c28599a9da874400000000000000000000000000000000071d4d708f00875545f381e164f77183e14faab599e472b2857c15091254ddaf5c2e9df809875336f92ebcf5b7628da500000000000000000000000000000000099b207cf6ed022289c27393c32d0b83aed6c7b57323e746374c1a8e2ade071a5293168e72f7aab82f6c2e39b97b03830000000000000000000000000000000005dada01b4dfb6a52d998210a67ccedc11d6aca2405e0836280e2f7c8fd7c8dd271c815a2e9ea1dba6f1ab0d6e89d756 +00000000000000000000000000000000195d3f440857011bf9b764ff270b8ba1d9d13daf48933e49c12ea20d335b58bcbef1353d9698a7e795b4370ee385f12b000000000000000000000000000000000716c151efc6e611b5b15c749eaf02816a86e267428750741b167404a21116f2025d0d07c447b9c7bee8edcc2c7b76d30000000000000000000000000000000012ba0bf62b35327111d09b402db2b75b2e835cbe581638af2fdd6d06034774533e6501be3de84e7075e4184e11fd81a8000000000000000000000000000000000329b14859d004c146047b03870371f53936e078ddc69294ff1fd6f42cf2a354a921e5f2e5c125c454e20af97dcf769e7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd00000000000000000000000000000000042286dd205ac86fdec3fee779059e2ad59adb62505f7b78606c128244b031c53dc40ebc2f5afdba348892d5ef4c10e7000000000000000000000000000000000f960010d4818846b3a0291c6fe1aa53bf0eafbc0e0968e3ee82324452a7c1a8041c06b4db9cd36a07c119c9fd2f9038000000000000000000000000000000001876da0dca72869708b8ff9ea0b74ad6be25ba82ccc76660246413a04344f2b72e5a7f6fddb58e9dc0bfaa6b33a5fadf000000000000000000000000000000001538ad1673f117493d998941d9356fb9907f70c279bde8ae8813b9c7b371344456f8e67cf02bf3401ee06d55604cadf9c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb1000000000000000000000000000000000d91319b4a5e047ffc8a68e10c34b2b90e7f3f08f9e3ec53ed12bba5f66c168c20c6583ba2016f0137caed834845f7470000000000000000000000000000000018d5542919674d2fc32430175405d806ae4abe3e1236df2188bf4c9ddf66c0974036e28414890212ff8dad244d11e3c700000000000000000000000000000000160b128f1ffeb97edf0e62dff85e3f90fa48567ab777a7937a2c0e4659df180fae4565107c2236a5f2808e42a03a4ab40000000000000000000000000000000003ee74d214ec491331fb9db8243e75570daba9feb587671496cea4b480d80ee162c6294c082203534bee450c384f645e69700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000001951afa33800a366944c43bb42b8c5c8beb9ea2e1cead8b84e0f94af51e4a156d9454c0f08d1b13c692c41cc480fdefb00000000000000000000000000000000077f4543fadad6f2f8ae8d5d98f64965bf9626971e7efef5221cb4d95d51b8764324cf4a11d0ff5330d58df70cb79d92000000000000000000000000000000000417251cd0c1b32505377e51bb30ac8a8a3c059644b9ddb5a058b3c6e1110e1c71ee19f549b15090144dcf4668d0d50100000000000000000000000000000000052133be345adc562238c4ecbaf76ca4159fc11ff563ab393317b03065ab668e7df401831baf7027f0577f5791b1ca3019e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b00000000000000000000000000000000067bd52b7a3193d31a4f1ffb76432c8d4108442616f17056d310fbfee2ffaade9437e2bdb8425cf83233f0c632efc1170000000000000000000000000000000011b045d6eebe1bc8218b696b5e81e78db78eadf1b5d987060c1bdd73aa65666f77e1d6bb6f3d939d64cb3e6bda08994c0000000000000000000000000000000016eb5ea5067413b72632f5300efbe0d01a284b2a59b68d0333c269da9302bf0f0cdc923acb27e51bbbbc1d4086e6b06a000000000000000000000000000000000ff37b8812963d9efaa1e6deb5cfd34eec70620fdb65808739295a819e03ebcc8f501b8194d0b3c72717fc922b785194199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e00000000000000000000000000000000149b5e0df255281c1b518427094cc0903fe89eac9a6dcdc379b8ca30f3696d89824c201601fc4b0795a3c859a82893170000000000000000000000000000000016ee9e7d957f439d078f3c5da98d114a1b5bc4da9c17e117e1f540dcbf83a349bba94def4b87b63247f190e3b5813cb00000000000000000000000000000000005d4f56bea105be4bf1fcaf4f25df30f85968d59e60b1c438c28ea0f480851f5ab9c05a7ca6677e6f12c7dd3ed67c2e0000000000000000000000000000000000dc0e87ca5a8b339b485ff3da2b9854a07e9663c43344dfb5ecf3ea055eadf67405c43013e15367fbaa55f1bd8e222f98159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b000000000000000000000000000000000b0575fe2adc9ad66209cb2191efc2946672e4e81b96d50493d2125d9c83165f0c4d3f714539eecef9de0706cc20da9b000000000000000000000000000000001511649f0cb6b86111d2830812231ad37df5500d7ce1086241591dc3cf40b30f1c53dda3133b2f7fff253c94d5eb98720000000000000000000000000000000005b15e4e32f4f4e46c1560792a9973f6ad63f5176694734f379375f16a08c162a4a820385d3ea6c191bd87fea4f5c8cb00000000000000000000000000000000089218403fef08dcc6e679b49a74557dafed3278d41ff36a9801db091b91de0d46d779a40574fa4a3f2baaa1a14be098ef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a0000000000000000000000000000000001c35a3fdea92b28c9ab4bd9ea592b998853a73be844b9dcb500ed6704bbf3ca4ed4216dc24b50254b6ca75c4ca3e7fc000000000000000000000000000000001815292d2a365dd7f41ecf3f9a89e040bab717241cefb3155a097eb9885d64fa55f5de7023f2ecfd33f483ff304666520000000000000000000000000000000013df522c72805b890aef97864ec6769f569504fca2d6a6beae97f80dc92643f8014daf3dafc0040dd7b985c0d9b2c462000000000000000000000000000000001155ad4373a8304fa6301cf48b4ace135d6a0c08cb06d624f42f88073e43612ced3cc37235422171b43af2b4ebbd5662d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c5000000000000000000000000000000000bb9afedf7417ca31beb96486b024af13c06007585d785efd1e78444daa9bc3c03e1d64b560e8d6a18ccf77a8c3c8d05000000000000000000000000000000001652d3adcf1612e487a9ca198801afb9ec30267148502684c2b91c05ebf6c48e2ce33f9c0a986daab81d5359ec1b503c000000000000000000000000000000000baf3d34bf4a78e3b9dfa637c6392c7f4d7ad0ec315d10748784b5b60221bd9da0f4b75c57c139ac2db329e270d559de0000000000000000000000000000000000c30e553fa2324d552bdbc7d2dc86531340c4894495ee9a38b64f5bb6f92314021a2a00c4bcd8837e55a0ae2676a9b761d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000159a1e4e87c35aaaeacdf21efbf8ed99fd6a2ddd7e990c12407b1417edaf185b8f1df9bafbddfaf3d581b5d97d7718300000000000000000000000000000000012239ef7b1e1009c81098aa4aaad8ee9e003530db5afd49867aec47f46d5e29d44b5e62d80d9e832937a299633e863c80000000000000000000000000000000016af6f74392461a9294d9f848508651ca5c0cb50494ee7c6a334bd770580b924a17beb7824b489e7e101ccd50aa0d5cf000000000000000000000000000000001912a0f54ba4fbecaa55c150ae93455e1db6b238c032fa7992bc8456f183c09b6005dd6398a77ab91cf547919ce7485b03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd000000000000000000000000000000000cf1cf7a09a12f51d10059425042ef8e140718ff11d2f17897a0156034f73ed29496d93b8695cdf609280d319c9bb742000000000000000000000000000000000b2c4d26fa1eb72eed1a24f27229d2675e0c6f91e3a4eba7d34b0fc1bf5a9b4eb49c3492d9586669abaf25a656e1f95d0000000000000000000000000000000012c5c83a03087b2449b71e9037591fa265d710ff6d869bfa18ac37cbdcc93024f673128db3dbad9e3517501af12f2540000000000000000000000000000000000ffe5824245e43953e3d0adcd5fdc1a97ffc87f8c5473fdb0fed57000fd126a9925ba7415c698248c51c1f3e12b270d5e4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b00000000000000000000000000000000036aa888e40882b2d6ac71d66c88543e32b4a0a7c959eec560e3d26114d8aeca63fd87dcbb3171622c989a6c7a204ac60000000000000000000000000000000006a5e552e6d2dc95ab8636a8be16bc79572b47860bb88934bf04c195ec01fd71eb91e45f24c58bc2812ed5fa10c8dd7d0000000000000000000000000000000015fa3ffcbd4e562a4bc29975cf8c1eedf442e37374fc87128e6f68bcdf6e996f6f054e0b8c608e651753de96655b2c100000000000000000000000000000000019bba7c0b170dfc1f8fdbf7a2e09ca0c4027a6aa6930d15dc2772a0f20e5e56f0d11644094dc866595f801ba5552e6c4c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000341f46ec06a8def4f044328bcdaa308798469c767d10e5db34b0ffb6f550421c67c6fab7b63cbc7504e55847cee419e0000000000000000000000000000000006952e5f791c37dfebcfe69cdef196dff66563b29e94927e3ab34365773b93e72251a63af4ff294af88d45fe0899a2c3000000000000000000000000000000000874dfe75b31450e99dea063c090e32d24fbff9b681b64a9dca5f967f82003005b003d17eb869bd3b37d4a412bcb28fb0000000000000000000000000000000014203b69e8af4e25232777f503d5e82d6121256fafdff1b037f65d5aaad0f09ce882151d6bb4705328400f00089dcc7a7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af4000000000000000000000000000000001403fa3f418107e0bf7f3f4bfcf621812d32b1b744ab5a4c37b5cf946a5e5dabd675c2b70bd355590a9883436c5e32dd00000000000000000000000000000000069e006f168bed4439fb46db9ba4f279f72ed608c12a05eed172608693f42cb1f04aaa54191f4b0b35f967bf03d0e63b0000000000000000000000000000000003f9ce029f6fe605802de64701ccdf52bf4aa299400a6e1c36f5a1f9173bc11a38e7628f123fdcae01d2b260f77c577c0000000000000000000000000000000009c9732809f60635115cb479c80457c6cd8dad092111d663c0cda0da1fa71c9bd6795ad013d2efaa4599c8ac5c88e5f26f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000115e08d8e4dff7adcfe46a416625be0ac26ea2d7900f5fed497809a6d46e7faa5b47c52ab3bbeb9fb16d82b549707ed6000000000000000000000000000000000dd1b31446e44f64ea5046dca5174ae854f6bb5d95886fb95aa136d432f1a8c03ef1a5f9320f89c82f764049a7f678a40000000000000000000000000000000014879783c07e6986cd393fa1e0ca8a7e23b2c9efa595229fc0b6a11b9c232ba33e92962a1087fe2ba0532d7b541827900000000000000000000000000000000013dc6e2bdb2801333e7f914b99f30b40125fa1ebd49b141d88a8c090b15ec3250a13812a19c3c0751a4e5ed100a6f0ba12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd6000000000000000000000000000000000a2cceef36ec78dc702b6731dbaf8cea1dc2b41fee1b235673c6941729bc5631e69ff37900479391a4d10b300fbf3eb40000000000000000000000000000000002f4881fd626f4ac434bc1e59716e5e5ee14dcb9adca4d639ebc9d86e323d274ad8ec0a4b1e6ff92e1fe7928d48924b000000000000000000000000000000000174cac80e7bc63989f58759e123513b611e9849b44d43a362f2eb84421ad008f3ae9e9f0f233e49fc8e10c1824ba948200000000000000000000000000000000143641099c8a6c8153dc8ce74debe795dd6c4487e8234f164f9f8dcdea6a53619c04a8fac215421f985557b5b956c20a5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256,0000000000000000000000000000000009807ffe8fa881b235b1181d2d3f147dbe21042524fb0c0b4c90fb122d160c7b895034ab32e20324dfca564ca6e3183c0000000000000000000000000000000010f6da88525da3e86ee56cd5514a436e3ce4128e437a876be130a70c42444a05ac269326c84dca532ca2e546860027c00000000000000000000000000000000011396a7317918841ba171ea46bbddc9bb5a08db7c82b90008c6982b4b79a4dafc151081bbdb7b9fb79784e603e15eb9e00000000000000000000000000000000070b8580f303b83c643a484dd031b780ff4ca2ec805d8c538a0b0c791cc7f8163654f5e5a41776a8681500a6690e24a4 +000000000000000000000000000000000f38906bd058e4d32403fc3d39fa57bf49c0da65ef42fb129332b91c184185de4f9f0bfe8908a44833ff4ac4d65b88180000000000000000000000000000000014ea6fffa6dc462463c15feace841697698bc521f608ed0d16be5097bf42aefcd1f73182f37b6279f989e9668a8076d1000000000000000000000000000000000f56d296323177ce53c6977fb60e445278e59ed1cf92e3f68c570eb7a9e5f8afbec5e2ef64674bbb54d7016c829f72750000000000000000000000000000000001b29012ff3460cbe4a07bdc65885718f217cf177866823a7cbeae18bda67f65913ea20bb69e0ffb31bd82f19862113dacc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd000000000000000000000000000000000a4126bff91ada057ceb9a75d577120c7ac8c9ba62151602414364cf88a3e12dfac90b5590db3e40c16163177ad4e7520000000000000000000000000000000004a3768d326c4ebcd5ffed89341e8d04f89e674f3f2aded3205a7193e11c20115b3c4d595b959d6e39a03d76f6b5925b0000000000000000000000000000000006e0ae4a9c45bb69c3a1c65e26e4869f2eb18fefe584e4598ba99c0044e8d911145a5db3f57194ceb6201e7eab9a81b20000000000000000000000000000000005be2ba6b147f3f2052c4877c90ca364427c6721ab64dd35e89f14f3179564d8812b9013e3e3db22f69afd739229682b98c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf0000000000000000000000000000000004c36cf955fc81bdba4ea8d2ecf934adaa57fa4073199f77bd0428d3ef80a7d7102179d4a44ef0de887bcb3ae915408900000000000000000000000000000000138bd3ec7a1b6fb65d1df6bc1d2ada35aa52b06729c10b5d45b9bb7cbbdf41677942b99eb9c2d32e3e73da7d5f9cfed40000000000000000000000000000000000b0291ca10245e2f7a963fa07ec62b15f6bf9e7a5a7839840ebcbe538dfecaf2114c7864a16564a5b3c85c15d97fb7a000000000000000000000000000000000b436e912b8a71cf8050d10d59017eca6e494e5440f02d2816924ac9cc2034bedb1cce6eff5c42f3dc57a74cf1b51cc0a0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f0000000000000000000000000000000000c105ac7475ed9517a0b07f25a030a5616952d817f3893181e352907c7cf4ec9f5f3006e37b1da97e9cae4a1213584e20000000000000000000000000000000002c112c18268934823d5946d2322d0faec497d8e18736da91d2af744d90f74136c49370a4b43952152c62820d25e52ee000000000000000000000000000000000fe2818a397d70543e752e7022f12bab10f1b1289cee61a0230d545296ec872e34d8df6edf7ce9980f3c153e6e51d96d000000000000000000000000000000000f479e6a52bfaab3a31aa9a461adbec8a390daa8eb6273f9e425eeed764a6dbad44d12778bb888aa5808df272edde401e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe00000000000000000000000000000000016106cb42ffc41d5b23bc5b06001473bdfe556d375fac6a0cb0a12494e9c02ca2dd6133356846e1759a2c485faf5e890000000000000000000000000000000003cec25b0f5d1db0ead5319d6dd15517657d1fec442facda4335ae0bbeff606fa9caa6a4c00445001180aaeef895d7fd0000000000000000000000000000000016ce3573fbe27a8d23b3ebd22aec989d61fbd0e41a519c5e2f1d650f2ad73adcfc8c840fb12bce83b722a0cc69164e21000000000000000000000000000000001434d13d44fd8dcf776c2a045734dff7c09ded31c9e3a4b5e765cf26fbfea4cbb4ac15c06599012a7f2cd572bfafd78ba32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000019923ffba0d08ebf1bd43393142d61022430356081c18e37804172082c7ace987ece2594f4852e84604a77235c7795e000000000000000000000000000000000123acf9e1a86846ae27d5fc0358afa34fe9d6b68232c9ebf2d47cc169779c4bd24f225ad30886fdf68166adfd9898abf000000000000000000000000000000000a6061d4cef29d1e3535d54a2e36373e2c16f91543f53e1aca94c4abdabc663049673f2327ea8bb574244d7f5c99e981000000000000000000000000000000000b1f3e1d43575a74584ec7a3280f8b7196f9b99b5e911ed33ba6bde1188c82d906f0f8e6fc2b285fefa0ce59116e449524301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000000373d36dd0fac76a0fc46ba5da279ca3be5a1f8d799570004e429256787110d4fb746f65a8527d0ba681a81b9980bd5c00000000000000000000000000000000057933c2b3e482ae026159211c4742264f7e890efbaeb6e14f3bf66c80923289af095dc97b751a117e181ef917d049b000000000000000000000000000000000068816ad2369bb57b3430c657284858d3736c327284e7410b61ed444786bcb34a66db9c16aca583aa9722aa8d7975b440000000000000000000000000000000007fcd7dbc062d28f6ef906f6a455337e517e1d6e6c02c7c0b2b2685b79f56ca3436c1bfa0ab96e4a5eb0c2e2c321c0dc17a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff825695706740000000000000000000000000000000007bb0ab060cc12002e043724c0fd0c8bad30e08b65ba9f2fe5d09d18cac4bb2d50e29ee14590ca7bfc505f3ee3d4f93d000000000000000000000000000000000e680653d29eb5d90f21802f543eac3102a1de6d2a5bc943a53dd9b80bdcaa6951ced2eae5e2a25448b40468f1923ebc000000000000000000000000000000000b7494b494019e3ef36d5c620ac56483fc6b1c8fe5c6f67537b19f56ef01db327812095fdf805d3dfe678a3ed8bb6226000000000000000000000000000000000291e5b98ecaf7aef0374647d28fb9f8785a64d9165de407d062403047da14d4ecd19fad8575070b278608e16b71d387d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a31800000000000000000000000000000000186536e3ae3edd9cc6bc24fda6589ed26e72e06121e97e1ead65b200fa0578c6e53d1154dc7b14e7eccc3a53237685060000000000000000000000000000000012fefaf6c76ae7197b99571e41a19b14846fc4499e8e964ff750e7c3ffef6ab3dc19eeb42c5f6ba44a573bca7a15166b000000000000000000000000000000000a135db813a44a21174cea3a0b34fb49f273877203ccb66bce44b2b58794818d8bc1df27544ecbf780823467e2e4ee6b0000000000000000000000000000000009b08f70cdf4e349e1a73935de9fb2ad9f4feb8cf5f835be78383fda2af94d81af253ebce08cef825764151d5713ae60cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a0000000000000000000000000000000007976df2d47c14374e554401c4d3330bbf6f1e6b8fafcea1e1974af61e8ebf493dc0473d34b30b0b1cbee082550d85c200000000000000000000000000000000177cd64db8334dccb17fb207e467e5b09e891b05df7658d9b439e3cb72bf3e0a70e84f96fb5e448f33c003c279cb38d800000000000000000000000000000000094d739a02b8ea6ff8113019597f41df4728b270770edc5e68b1f5c32775f0c706e3f31c0a82059c1ee150b89097376a0000000000000000000000000000000006ed888aa4bdbee94ec67500e30d654071774fe22464dd5b900fdc17b445754293504b10d044aac8fa0c289f0b2d9dce6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000014a16b856b04ac4b687c79f2b4e1dd6d45db25b382e0ba6687afac648c9b6384cdcfa89812f1a726bb4d1c22ebaa6668000000000000000000000000000000000764088e337df6db30ce8aa23aefd91d9e35be911c9e89ac62a1e06c3d06e28efac256490400fac4490f595cd03c127e000000000000000000000000000000000894856fa1c8488fce182a9c7749f7953e6a73879b6e743fdb8c780275447122f512806fa83d5ad528f8f61598ed01d20000000000000000000000000000000002b33bfd09e0ff452c3336bde08df0102162488bc83c27052447a1e5d16c9c68bc529f96ee3787a26d2009f22a1246342e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b00000000000000000000000000000000133e2d092352d3ecef5b67a09c2be268fcd4fe1f7360a8ce3ef5f33bf689242961a140d9c8afcc1e2fab3ad4e3dba49d00000000000000000000000000000000101eb285f0c462a22406846d82ca6a278520b65132d2008b124f6647a642c221b0c3bbd4a0abe8af7417e7aefb81b5b20000000000000000000000000000000010958cbc317f1186aab69ac24be87647b8013b678b0eabc6270167bdc9c0cefbaf4d9a34dc41524b709f1b881e6bfa34000000000000000000000000000000000d92c47257fd0c4d6baa4c81efe65852840479b9bfda5cc06b253f167069ca7367924c0c67d6497a1e9abcce7d0ce9502eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f1840000000000000000000000000000000014ad0f935ba129b47ecaad63b9dda44e7ef7933f182a0f5226141c8f0ede026ca2f11db7f4924b5c582461688dad6359000000000000000000000000000000001453716381f13bf6ebf8fff2ed7bcb90f7beb44269008af5880a355dd03de5c84c14f5aaf69fda043b422aab0c694784000000000000000000000000000000000e983c9e9b799eccfdb56444d31948067d46adf275d7f39a70aaa8bfd0fe1b83632c23d87f4e993c8191901e9a607217000000000000000000000000000000000267c8b8c5e09b59277736caad12ec6986f206d1c1f48023356d8bc877a594c8bbd98981cec6382bf9bdb9a5fa38275ecaf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000002e28c245e71a7f6206427ee512f3250612785ce29b369682fbf767d06ac08f91de8ac9f82951574cce46cee1aa757720000000000000000000000000000000019b0dc35eacd961e0ca7d54a0e37c4ace37eb0200d5489316f3371412717c57c8f17c1379721f4dd67b3fde24f50d4cd0000000000000000000000000000000013b9741f7a32e5e5b1ae5400e32dd6fcc1fd43b68df54ade57c934720b1289a51deae77b1726e1955b6430f37928e2bf000000000000000000000000000000000693980b347ed7ee6cd93f565c87efb36fb304d7e9ae24e2b9f902bfc962b6c7fbab93287147f5ac892db2a709c9ab42106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000ad6b7cfc6cefa5783093b7d700360b354d0698d27ecefb7d5928ac5bd6c299e4001474d205cf3b85a32c600ddaf1a360000000000000000000000000000000017172c3d5acf59b70b340fc703e9b7801aeb4857ffbe7a9d5daa0f32ad80d1c0ef2f0b3b7d1fd83a757c076872425fc7000000000000000000000000000000001291f55fa7d14b14c578d57178cc707cabcdc4bfb444cecabda271cbfba2ab361947d045ed46d9edbd215fa4c8164e56000000000000000000000000000000000f64ed6c989eec5222239d888d08dfd638a0e35eff2266410dab0498941fcd1683654064107fb7e53b8c02fbe98a25622b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd30000000000000000000000000000000001d25b5bfcedc6d7ff7e9fcf729f858759936235d23ad45b14dfd0229bf3e50fc68799d19ef019b36728285bf7ecd0b4000000000000000000000000000000000326e300ba07935e0233a03ac891f18dc7b5a9ad9a28264136228e9e23e8f2aa31b7f5e5f3cb3354984f57a868a5d00c000000000000000000000000000000000dc92060e3403df3a92b15ba3e437ef0c403fcfc9c3545e544a78874e5d9b5e63b9ba6060c29022fe2594c2e6fbb6a840000000000000000000000000000000006a01e85f59dc45b1501309a350137d71147c30fb70da6b7637a9b1dd884aeb7e554215474784ecd3bef18d15d2c0524dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5,000000000000000000000000000000000b7fc0b44723ff0d1cb7c43e470d4d432fc4bbc7f9d98ddb3d91434a5574956fdf15f898e579236426ea44677998665d00000000000000000000000000000000176586b6f157e408138391e3767d0c1c8457857f4cfae571267ed64ac86ff8a4b61a28b406e1caecffaae6a399f4ec9c000000000000000000000000000000000a420992f850db20d4f7d2ddff33e4dc79bc0c39caee533920c5d03d1c2619d8ced769ac09f664c0921829bd7edb446b0000000000000000000000000000000017e4000f4d03a6707174c3adb74966896bcc0eaabf4ff83cce92a666fbd13b59efa2c767442062b6c8b1a3abd604f0ac +0000000000000000000000000000000016ac5146ffc26d1f0c33645931bddfb84756e1c7b03f4838467d1b6701ee9478ae03c910b6b4f5c14135487bd2c14bf8000000000000000000000000000000000e1d082f16e4d5c5f0b6fbe5178aef6f781a6fb165f775cf0cd4dd55f2b498a79edf373007113dcdf6b977a87e1fded2000000000000000000000000000000000bb94be280df1aed761651c0292f88037d172b675bae1933ec12323b947023b437d080aac9e196fe5e06e5c4137284d200000000000000000000000000000000064190f9725bfd5d56c31aef7fd1590c975e2ce549fa6d5c71b254851149e0a0dab0b9de3acfc2d64231c79bc0ebaa37ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000012199d02d3f1bd8c4005878c3302e6a731ea69d69accdd690b4667e847b079563d32e18eb7a440b8005ca936da5e73cf00000000000000000000000000000000125b0dbdb0058639513b007a84d3a3e6302f5d846f22f99a55181f097e200981d9013c00d688a11eb976120f1a5da64200000000000000000000000000000000081e723506635433528fe4a40fe4ecb8a9c3d8cd701c043c0418d149951651e21632cd85f03db33b89efadd69e009ebe000000000000000000000000000000000956af2e67f8ae676abc783c4ec9f85c50ea130410cee8216fe036cd0521b8ea38966288afe7d35c28b30f7ca5c6edc0c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a7730000000000000000000000000000000003e11a4e9dfe82cb495e9e698b16c257ea3f4ecb24749751e7334e0f31fbd6677545e4bf9ff78a82853560f7e7ba2ae2000000000000000000000000000000000caea2c527cb3aeae427e92fc364365b1f55e7128a544009be2ff7a5236d1cf8ffd5a5cefc87820bff5bdf1c6bfa165d00000000000000000000000000000000064a3186774da8bb5d013debf46ccb0d894592c414f32de6f77da47f4d42b0c8a13a2ba4f14b9883d564fd8ff6a4c90200000000000000000000000000000000072f6c48b6a05039e3a4dfc6b73501d6d4ca7e840b119da9c074bd4cd2adf4f2c6e9e6325ebf6f97c3f0b00e6b9bfac6dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b0000000000000000000000000000000019ddf708ab31f6f6f725f0e4f65d11248d3a79af30927a6f2673901fef9819b189502cb952bd4742d2b8e84acebb5196000000000000000000000000000000000d928535c47eafa5da4ce4f91467fc31aff8b86b850e4582a597b334491b14da71763f9aedb15ed32856382069c094ce0000000000000000000000000000000004d6b3545d067aa0768cda9dc3cca0f58eb546345b96f7d6b9355d47770e00286d962a6b3a64ca2ce22fdb4834a4bb6e000000000000000000000000000000000f4ef9366d342b309076299816c1ed9b424b68886a5c69e21e785f97cb0f99ae3a99ff6b5244dab817094449048a7552da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce478000000000000000000000000000000000236c5b4c57ee4facec5d4ff37a478c505217af66e029c3382613442c58875c75cb423789f6703ff3c1c0d80991c9e3a0000000000000000000000000000000016c052de3336002f362d9b0cb386b800860527e0fe81a1a6df0ccde31f3265e6246191b3febd1ea48e9391c44593ab0700000000000000000000000000000000078dcb04ca93c676a9a924e59f924d9d3af872849bc30ca633d4025aecd981ba12e626337635ea77886a45f4da84104f00000000000000000000000000000000027df6394b195222bb8357bd684088e3e2a398f0fb0cb812ca5dcdcd1fa1279cfd03db62e0f8b2800d4b8b48238931656aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab77000000000000000000000000000000000485ce58b387083172102145fdb3e26c6ffca8b35af0e1d84ce9cbd89055be083bddd3da56443924049a056fdb2ef092000000000000000000000000000000000d998b234a69d584c78ed054b1322ceb33f73cafb5b23c1703a9fd609edcabd44f1a642802b9c0b6fde6a6828b50c1200000000000000000000000000000000019235ff13567bd007d77e4dfab139cd57dbb309a3cf6a6198a548c4e6915778094ddf2b05a91f5478169757bf5a56cb300000000000000000000000000000000110f6ea19a7f62bc3e78f4c5c1c6d3efdf1a7f563576e758218b2c363fff8ad8fab0e72431619e4ebc93d2d739fc786c40273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a0000000000000000000000000000000001d45da76e3016c00fe65bb50f7067e4f06364ad8348184831c4932ea0e0f3a170ab5147e4670ee1b16924105b6fdb6f000000000000000000000000000000000b3468206db0613369b2b0750c98da65b660fc07c30cab4e459c311dab683b6b313b99ec0fbe92ba07f8aab43a12a2c9000000000000000000000000000000000f58a57c449a41105837d5e2419a34201cc921ec77408d6c0c7a2eb227be98ec1f6f6eb9fc088daa0d4c78928a1eacda000000000000000000000000000000000ba53b872dcb9fcabf35e673b467523ea77accfc1b38a5f92d7b9d269c28aa00d00b08d70eae6ed4d2e82bdb06008f9ab77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb540000000000000000000000000000000002b82e2b582b247271543117b939fd17ba8bdd617a223873296f7bd75de4790f0d5d8fe523792bc7fb4764d3739669d80000000000000000000000000000000006eb554347efc5f2ee79949bafc012e6d9964ce19459b3867865709d903fe3d11bc617f30f6279a9e62ea104565953600000000000000000000000000000000006a543fe5cfbae629fd3256575e3eb4e0b65864aad6c7f359e169038bf090ed9bd92fef32fe1ac20b2a8c90fbb6081690000000000000000000000000000000013ee42b0693b2f3b9b977fbae5c856e9e4c5e70120b5c29e0a9f898f6d04b7fe351e17b02716a44febcf0a00a9cdd9220be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000001ae7368f84e354e5758554aa9c72ab4b00a644cfb9a4ecba38dc72227d297749bbc98c8f5d6149143b31442359d8013000000000000000000000000000000000abf087f77c79cb8c69e4289fae87b2ed483442daec3851a5ba32c43e342be29433b2deac6dbfa7a787547a7361ed0a00000000000000000000000000000000000fa01cff7aea64b649951a8d85fef0bd475f31e47c706b96ee2753df9987508b5e5456cc49e88ec3aad720a2535f6940000000000000000000000000000000018874d020e2eec0e286dce324b91f15b2a4f293d32956b27524f478983f0e0c5b43df802b60f4f001753f12d449cd821c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000018f7d19362e2cba91023455e115cd90f02aeafcb026349393ca4105e270ab1cf589621b40965fdc9795f66ea0f6a053100000000000000000000000000000000170ce0eb304e0e1047617b709c834b67a8989212e5bf1cbd5a33242be94bb141d5366e636c01a229943bead9a7baf43900000000000000000000000000000000077a17356b3b31faf90f709042938b9e901817f7379b7bd486d18e47d22b0430ba70fb3006e9afa67d7dac71ffaf152400000000000000000000000000000000064aca92c41561e195fa8239800c97d5242ff0f8ce76b0d119063e2ffa09c26e01d23d5728765a59bb9587e885450ad1c6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b3000000000000000000000000000000000876a57dc24ad58416f910ee3ce220630a1297e6bc691c908e6cc16f975b146872d71661bbb869361623c61670627eb0000000000000000000000000000000000760fc65097d215ab9aeb3d5a5153977e1e399e2cc0b0cb9befb0266d98ac13512a0eadaba4e051bf56794621c551ec60000000000000000000000000000000003c8e205e53075a96c14ec26345c75881a0d67c7ce0d62d73c83dc353cd7b555cde52ffc5659ab0db2179a899f0fd694000000000000000000000000000000000d7e8a7fe6b751f7f478698f4f0d30cd0a435a2295a958cabedf4668769819b4cbd4e8b7721eeb5ced3f913156abcaaff3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a21000000000000000000000000000000000180546f697349adb2918129f4d0a979bb114d1b58e5baa6cc221a09d7083469bfaa61f80f1e3a6ccde0da54b24d59db70000000000000000000000000000000004074338380e3d7c0facbbc71d83e78b53191af9ba13ba0cba6015bf4f28e4b0b52ffb34c7867a335848f57b5ce5ef5200000000000000000000000000000000148a800ec38cfc2386497d9aacb4327d5953a6612cd4067ac13fb977046688e80032125d4b0e7cb49913e489796a50ea00000000000000000000000000000000132438d18d942e6dd3f69d117abf83c2fa18418e5145cc43b3cb8d18c873935e41279a9e13596f2863be7aeae9b73d172809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c0000000000000000000000000000000018b3102ce91af86cd10162d3a43e488a0d7b7807dfb9624c3cae76f342e86f8ef1200444a57e2ed7f819828357a6dfe80000000000000000000000000000000017137b470f3c8d1a03e7252e18f4466c9ff809408cbb2043d6b226ae2746d890b267ce3255114b2e073eb66e93c55eb200000000000000000000000000000000054dc1c981c9166d0bd3a54064c33f15ab856b240770ed44adaf9f32d4429babcd0baf2c5b8a1ff80728e9c63e806cd3000000000000000000000000000000001897595f836342ab54bc2e1b72f433bfe3b5bc989727de48575abe89386aaad9b1549af3ca55f39feec14355b29dc9e33521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac68000000000000000000000000000000000467f1a3093c72aba4c2d9e8171057cf88146eb32f38db0761a5ab2027f2213c89e12c67a338b4b342a73384109988d2000000000000000000000000000000000ab26c871d140c9c4e0512afe9fb576409ffdcb95417f8c6cdc0d964011dfb1e745045766bbbc08ff7dbd6935934bba300000000000000000000000000000000183488902b886200e63465098be87a905810b2e8ebe0364316da798e423dbb267743a0d2e3d93303623fb17df0e74ce30000000000000000000000000000000012c7e79f9ba36cc47762139d191e6625c850a03d5b6e0648032d1669575704c91e48a9ae432bb3553ec66e86e082de689c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000012ef4988956e026a79e5e904ad3d7ca56793321d62cad46de3cbde8570be5f0ac86d386216152b37053741fe342de7c60000000000000000000000000000000014ff7804312754d23b251a42aea65207695d4df65cac4f87fc96cb920843c022f24cd27731224db751cfb621886249540000000000000000000000000000000006ea693105a1b2afc79dbf75504c256c519f927ea0d79ddd1997a49638a67151dc81b84473208e8078cf71d456f2de0c00000000000000000000000000000000122d367c147c91517679432d3c7b56f2d529d70040109f803b89a04fd8540a6c565354ae420e1bd4ad4ff61427332629dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd989664000000000000000000000000000000000156e7f8f1412cec315eb76f10c92143157313b8eda0677a6c0236de5fd27e5660ec3eb7369f1604082c59e1aa5f94dd900000000000000000000000000000000018ca9f505a88ed2bf595fa9b55d2356748770af16b35bd5db448990b7d41c3aac53aa490791f7ac09d2f5a087f938f70000000000000000000000000000000017c76ca9ddfcc26b028928364ee35829c6e57fda40773a6bc0c259a1b3cdea715c664d7bd0340192aaf7dec7ad20a2ed00000000000000000000000000000000082a255966c4f9d0ad6bd3d88b136cb2cfca09ed6ae378c914c28ff3338a2cd466cafd839f3fff4a30b33ee56e684f4e00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0,00000000000000000000000000000000075c71e21ce327a97024c8ab5fcbef4fff76260a4f8c8489167166c4a85d25096c617cceef73097a4bb956be3eae8b780000000000000000000000000000000016270f3ac86c0ec43b9472499c4d845eab488a34ad9e2148c72cbb1db13623c5dbbc8327c47ce596521bd1f54f119a660000000000000000000000000000000007ad4914ceda9fbc161121c818bd05953836a581dcdc78bebcd82ef548671c899581681c908a337618a445f77c6b7cf400000000000000000000000000000000173f401cb78024e844adcc88fcf0e52d32de134f6300216ea0da7747752ae3ddf4d181b8d266b53d1b699921f9871425 +0000000000000000000000000000000017f40eea638a3d4fec417701c936d61c5b233c9d6b3e94ba9addd0fa0b20adf9f8e07c6b629977445c7750acfd18001c0000000000000000000000000000000005f1ca1ca9cf67c33e3ff174a65adfde2db62e74edf30b5b0156d6b9dc86dd619ad8863c055096685d611ac015ba884f0000000000000000000000000000000001683dc67710880b8af76b464291c17fb0ee4eff3f648ac0772f4a777025c8cda0342d8f5aae3123da7fac57b965685900000000000000000000000000000000143d919ce2cf00838b10fc65374e770bec3db8ecb17d2db08b6a10ac38657bb109f54c1b3040b661c3914ded6f7eb80fa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000000616299341f2921adf083d1190c212a7941bc0d9fee50b05b265f2e8c339fc3dd9f94607631f485e394f5a7d71ae73d00000000000000000000000000000000006b2f12e22369e8aff45b6c05a2bb72a706dc46a5d1393aaa9e5a7931ccff33a5df2967189114c3fc5dbf69d080e39dc000000000000000000000000000000000981e1b119d04343e075a80dfc189000b4cfb4e321575817aece6009e6b3a6233d1409e8e584f0ac9caaed1f43e40d7d0000000000000000000000000000000001ce4693e8c14032c35497e0f9a586a4541d8a1a68ad014b0850753a04215be2bb60cd7c2fa9be4f4f09a562d7b29f3892073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000012d634764207dd7a0201703f855365f7750291c810ff292b3e8dee682d7d8eebd6d6f3b3dc8b0c9e25bd2860e031311b0000000000000000000000000000000000eb0859d79fcdef546026fcd380f5c936e64a5665d73f56d92c03dfb50c534a00c857c86ec43275ce69cccc0b53137f000000000000000000000000000000000131bf000fd117ef722b33a1cebd28899fb012e1113f767d0ed46fdad82a32e4327b883fbe29abba1bb7ba3ecc1cbab2000000000000000000000000000000000e24ef1e44029366ae1daf06524d8beacb2b99f60f419cc2ec1a49013b79fb7a4781dbd37785f32ec67c0a28d61a3cea2117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f500000000000000000000000000000000067b6eda41cb8da47a424a02a142e2b98b9c69e7023cf616040993f41507798882194229cb6572806e82e9e5eb837b37000000000000000000000000000000000e38693cddf130d3645fd60ade780db84fa700e5bfb74ebea49cc95ab001bde442f363b4e4c61f683b3e67f1ec8c2af80000000000000000000000000000000006d593005cbccc55c5e336e19aded70da65a7fe42b6a85070e491a4ae54e18ac213556a91d5d62786b6d4d1305525a76000000000000000000000000000000000ff86216f5388114dc06deffa7b52a273b22fe0bc8d50804b491fac83e13915c2dd1b8c2779a46b5c313c4e1c05eb2979087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000262cf4727703fb227bd7fce6cd3f25c1897011ab892e79fa47446711d6867ca82b9b95f129f7ca24dcb60ac75173d4700000000000000000000000000000000136b5a304807e029d0a77b2ed505ee5c920248242f0f95aa07e9bc2e13d35f6f67451d028dc19d26095b55cdc2fae4fc000000000000000000000000000000000b511b2e19da7bfeb183f0aec91bc7db3e7c913f1c282e12d5d2f422a49e7fa78a5f35656dc9c980324717a5ad386dc30000000000000000000000000000000012eae443aae59fdf907bcfe3ee4366e252bb57e268fd569d742456f348429f009f67bf92f9dadd401104ccd2549cecc8255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000016a827938d8b98e3446445ce23f48a7ba0d634de826dd1ee3c71467eb57bd4c24e0d1b4190f47bd841183baceaa2593e0000000000000000000000000000000011d360e0c18b45ace82eaee902475127d8f18aa4a2ec2081a453f1c85ffe3c59c0f7016f966574a7c51bc14f1935568400000000000000000000000000000000186b5d452c6dcc1ddb4f47b07e01b6d64644f6d01cba8498c3059cc494a68bd25eef35cae05885b9f2689683e65161410000000000000000000000000000000000ff826e5a62affbfd6d2062bd329fcb561f287046870b8be461767759cb0d5f1ac904ecd1f136c5ccd784bc11088233e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c4000000000000000000000000000000001010e75c52ed0acebe30fc588961c849b7b6298bb8d859f9a9401737c467921c5e3cda101cd4e38e4318233d12b6c7b9000000000000000000000000000000001884db518fbe4d621403ce00521878c0d419d8cf476a1dfda59b7d3c7af2bd91058bbbf54ac0c5cf9a217beb78e3f98e0000000000000000000000000000000001272cf0ad917738bba052e88baf88347d60f63f5b875d604cf0531c1ba7d43e868bc70a682b7274067106f611f08ae60000000000000000000000000000000006e3236f6a66bd37af4be230d4edda6eaaed661f206ca4852d3004b5f358f184d80be6af81c62e5bc8c88e7a1072fe21fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000004fd1e2fd0d28db08224fa7e880abb8c48dfd0e488df4d2ae5f6649f448193acbe943baf22af4b12fd763e3e4ddaa08d0000000000000000000000000000000008df68f276f356ade28500eeae3b755c9af9b5acac5f5f60827b5b2044b2405129b00e5271baf9a80847d3b720026b3a0000000000000000000000000000000005e683d1556f513e6d093704405f312687c3b9e2de3b2840fff32e88186c89b18d1ac558d960b1196594730a9bc107480000000000000000000000000000000018161f8d23c394d10ba576fb0ceee530ebb95a670f2589d84c0646f693086ecb7ed80e556f3ed9434d7fa488430ccf430e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000017860708943449c2227c0f50cf1274652dd32e999d5f9b1a8d672feedde15e9f1af484a7b9462a62dd745bb6d3c7295a00000000000000000000000000000000064f8cb707494f82ffb6374641817a466af65f5c7d83cc2964e6cb8efd021e0c40934a3ffbb0d91bf8a7a616dbe8d220000000000000000000000000000000000eb37cc9d56fa0dbf050b557aaeec76f9f6d0a6c448ea298af78004e41ecd8a1df8fe8640e77cb76b593ee17658326ff00000000000000000000000000000000092ab597967544fda640b145edcb3ae6c3f027c2111dbc282ebdd48eb93287ae4729cb30e45c1c8999b3a45b099dbf0ca49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f00000000000000000000000000000000124fa4d48ffc5732fb21d465b559e995891fef98370a1eb73c9264988f75caa93fc134fde7f93c794582ba5cbf6bc685000000000000000000000000000000000b71d012abc1558e49831f053757518643ae04f79234fa92023db9c5483bbd872d24eb87a78960f12930094c4f8fb70e000000000000000000000000000000000651cf0016efea086d98e5bda8e1959e20e4947e302eeb021d196897cffde3e2c28f783521b2a28b8de1ad1a131f5e67000000000000000000000000000000000555ff8a930cc11d320afc3e0635a6f93da1487a5764d56636be4e5803d740a73d84666f6141ed5ee6b778a463823fbeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a7000000000000000000000000000000000974e769869719f0ee30895df837cff50d47382461c557abc4b8806b04776f401b76a5e630a6ccbd3484980d03ff58d300000000000000000000000000000000098157f0190e6bacbf34c20310f6471166750ea1b235e46a5fae313f90dddc799f21548088322910bb0fd7e41beb23450000000000000000000000000000000007f00d7d18719db9d91e2c32f51083b42c4fcb43c38087f86879ad6bc99600d4c395586187d26d041ff49dbbe517fca2000000000000000000000000000000000510cea4a7463bc5882d0cc25fa967a0b02072627bd57f9a5863fe5255953732846d4907fa301789bf02af9c1b25211c53961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000bf264c0b7bf68c595b89453ebbd7fe2e64f4ae2c7268ad51f4578c35d48040277f3dac9021997af02e492039348efaa00000000000000000000000000000000083a4fea41cb1e02e5002259f5f7b335c81e15cca93cbc884dc1b08ee981c55f2dd3c0db1a35ac9907435edd7f0ba625000000000000000000000000000000001468e508a02ed7b61f752ac38313345338d2b2d018f719f391c0f3fa1dd1602d9476f3d8829720d17021a459a2732e96000000000000000000000000000000000629edb2530c38ead8717b289c08036c12630cd8c9ae875111749ed893b8cbce40bcaeaf13df4044147bb665ecc2319ea04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001635830ebf227be126e13c634a84f3649d498e0999ad2dc73b9c7360db120dc2216addfe18c00676ed185efa1e789d8c000000000000000000000000000000000471e3cfca449bde0ba2b1e2a5b63d53badcb34da3251313190a35daf694d70ba385976d1f875242386fc74ae0173d18000000000000000000000000000000000986cf3f1eef587bcc70f66f25c60f353e6b15bd105fde9254487e9b522159658d0fc6b6a8a3ea38c27865f1ea4d76490000000000000000000000000000000015a2eccb9c10bc273cb712ee04bef01a11e486bc6a4d220a0f653582af6ba1bac0b5108250626ddf126f16f4015c9d2cb688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e22527000000000000000000000000000000001213cbd035615f09189171b3e22630d72df2df93fa8c14427bb00c34f5b55bc8d1b1a59404bed6549b582537a397eaab00000000000000000000000000000000161072d8ebec2841f0f34cb38a3e1b2094a597640a34178ee951e5c993646ecfc3a4c0dd753e7e76f3a6da5a091f9f7100000000000000000000000000000000077e9c95b6c6f726902392c3a16b5cc71cd9d4cec58c00eadca6091e45bc095e53006ce8ac8827565e867531013821950000000000000000000000000000000018cdf909bd9f38e57ee24c0f51a5f9f703eb3d190dfbf75be00969e9e8f8fee331cf32d93c3a956d12f374f8752c2c79cf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b6000000000000000000000000000000000cbc27995eaeef2bef14919d48a008a0b0467856f8a6659d6e68e47a2d9d41d217c5913aa1d67911325dbd4fc38e36eb0000000000000000000000000000000010639740654bad5c4ec93f2496f4dc54a7642bc92ed03372ad4edc5fedcdfcf37158d3f02279d4e15078e9d5a7f8b5df000000000000000000000000000000000155ff4d6dfa031b0cc2f57df41c1e1b1c81bf5a5cc1e3aa93920e93c2e2e7a71b56ac410a87855400025badf6dae8e60000000000000000000000000000000018e637da048e7e84b9d1654113978fb148a54d86e1d011d7f5a86cd4f1e5bc15abc5b67d00129f53c0c021cf933f399c528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f3650000000000000000000000000000000015d8f6e47b8f07b3e07ae0952a7c8f79519ce2828e3e26b284b8f2fae7432b337de17089b5c32f0081ec6c6916f2f53f0000000000000000000000000000000010ecfcdb02cff772db667266cb3f99f1dc28004ffcadca7a9c64b3b5853c09b7793ca0aadb155257bd64fa7bccb390450000000000000000000000000000000011096a52f3272955947304ba037e8b3fce6b2f07f2352c08d5932f4d2306ca511a74dc044d0f0e1e260ff40b0fac5e0e00000000000000000000000000000000130facbe0c1c6d077e9dcab647a44b049a1aba3df500bf27d1c268f71a59635e702c9ee1bdd68fbfcff7ae5b3e6bd83bc32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1,000000000000000000000000000000000b47d58802579e662f34908a4060becd40434e4934ff58790df2a69a759223ca29f42e658ab475cb92bd9c46566811c7000000000000000000000000000000000091d3a4c58a669d3bf0377abfe28d1817168b2a86375928d95df3459c83334669a59aba95ab2b9957d5ded0bd8925910000000000000000000000000000000005aa9c3fe0067338675099ee32f93bc8a5e9ead94b120dfa391651da40cf1ef5ff79d193b0b14b5926f10660aca6c11500000000000000000000000000000000058200992b111461f4d737533301734a5c3731c9f2e7b55e18887ebff4d5b74dbbfd23773606f54cd6a930b85b89aabd +000000000000000000000000000000000bc3eba5594666cec699a47fa13ee74722a80b4e5a1b28553de37c3163f2fdb8b801e35a19f6d99cd207d1642c0d4ad400000000000000000000000000000000104b334afeab36961824611f0fb58bcac55c9d36604ba73e9c8809085c019bd967cae7147c766df6565ddfcc0cd5fc9700000000000000000000000000000000026c3a6a4ace8638bf8ba7434b59c3aebd4bb274cbdcb898ec6a547b215f32d10395f3bb85a9eaecff5ef6d5ef2b50e000000000000000000000000000000000065bc419f9496b5e81ce72a13fc1bdce4738c2e3faacd80676be31db65ba3e7941ea75e370b6d6c0e7b2cdcce80a2fa14f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe000000000000000000000000000000000e7f1f5ead0f212439b4c47599581982712d2e6ba056f36cb04033ff5eebd81b5b41b874a78aeaa98562899418ab04c900000000000000000000000000000000095e45da9a4b2578cedd13af71e289d0067ecca1f09c014a294e0b250d1e8243ff98a9761030ac855a9d897cfe9fafdd00000000000000000000000000000000030b44b150d1337a3ed6a77f7b6332d7c8103da1aef0d445ff7467b4863e4c830fb782a81d01a6bf97e8d52bf333e78d0000000000000000000000000000000013bb76800375a45b847a96ef6edff3fc3c30e3d45bb4afe04230107f6a1802794e1dc23431797bc5e79e0d5ac6357eee4b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000602e0bd3d34415ddd517a73acaed5750dcfd68b633d51003edf79a169ad7a3ca2541d7a131c317c957a9597a753b5080000000000000000000000000000000007a964539081fff51e0ec24bb71257f6a1c513fb0047aad84b80180b22133246a1f62958ead75e4b2a68f973d17f1230000000000000000000000000000000000f48fe0f5b5a95e48bde4d8be1b2f352d748c1201b064bc88309a709b285f81260d4535d3e1dc7f1d6ee23ead35abd9f00000000000000000000000000000000135b480fc8a72248f7a4898fffc6c18b7f2f5b1af5cc3846610c299c8da252fb71190d9f761e037c6b47595bab6a03e56765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f0000000000000000000000000000000004773cd2884e52c2551c1ea0abb78fa17caacfffe13f18b75588484b8bfe781e623075bdf827fc82c6ed7e1d4d62081d0000000000000000000000000000000007e6023fc0e409bfc7d0b7ca65fa0e8d88bf1b4c202a8d1f0e1c3479b0963a646d16795fc5a128a54e624357050fed4000000000000000000000000000000000039f6eaaf99bcc9f4d8fb994a040af0d29c37960e9015d4e48466a9e554da30975c5534e76a1f08a55ed8ce7375b70100000000000000000000000000000000003d2b097d4afdde83a01cf2b4f9d12f77c8e92a8cadc225d40f974ccf385ae65bed1972a365d55e24231d58abed4395a2eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa00000000000000000000000000000000047b8c550310ae246e43b513d39e507f1dace7bcb543a49ae0854a397f62c408ae3632c94d172669ef3e013781796ecf000000000000000000000000000000001592914e260afaca80c0a240426c2828239ad5e256a707530f49cd65e9da2e4bb14a7d6d5978f52c04130a0d434cf4ca0000000000000000000000000000000006c0b8448ad87350db130373778d414deb738d3be97fba25c816826f59e3e926f44956c2e2056b7d769278cf56cf6fe0000000000000000000000000000000000a42d716fd83071bfa014a9b7af6c164d494f0347aed953bd2c1c97ade087a8bbea9f53c507fc0b22d520f28cc5d480cf8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000018026c9f6c86219d0be88955ce0afc3cb637b1c3a531aa2722c56816d368688181ef2fedf1525daec6d9b1651b71f27c000000000000000000000000000000000b40b15bb0621209bf9e33ebc27a7502d90fd3af62a1bb8f54a874a14c105df34ae34a43fc3805c1e4817ba30c048ac7000000000000000000000000000000000465262367e30ccc24632d39bf3af9cb160e97049d855176f665a185c138d5c529d11e53e56c65506e3e30be7b48c6730000000000000000000000000000000009485991319a311052d883b45911be12cf7648b5ca104ffe77594472f7047c803b8e9fb753b98645e630b9913bbc947e28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fcc0aca0d873cb8733ff7e2ea02b3736b737821af2db06ee6508e161f6159f9d944372c513a03cc4c9e30a707dca0930000000000000000000000000000000015c3774f4e0b30c9532beaa2f7f9b777f8d46bfd3888d6835f4a5a046153a98062efb17f78807fa17b3a995ce720c0b900000000000000000000000000000000083d48e01d2fb58244861a74a1261063f7d20b412c8a44f9945fbe373cb4b9a7ffd4c4ba4054ece0abddb6c14c013ceb00000000000000000000000000000000133c4976454b7be427c4c2ed437bc2e882854d2ddce42d2f97cd3fab1fcf60c3272aaa123a0cbecce1a774946bb7a8a0dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000f6ae7de1dba3b3030b208f61d182013231c4666f134b007b52d36bceb6f3cd77577be7b11abc097cf9618d351d61e270000000000000000000000000000000005803904e3e640e51900805f930638ddd8b86cc1bd50cbd32a142e10d85044cc52ff769bf1b40dcfb7269c913d00b01e000000000000000000000000000000000e6997b1f8bb649c56de5c4bf9968d19712abd22fb7dabee19e0aebd1b13adcd3e8b202975b4edc917d93adf087fb539000000000000000000000000000000000a32384fe03280962c5f575b47192e5ef3111fbbb0a01bda2db1e9733471f11eac0a37df8ae1a891de311770c482c06b0adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000531fad86551ac6dee15fbd62cb13f38d8d5c89d23a031b9977f110efcf16501534757bc5b93f0250ff02d6cfdf2009a000000000000000000000000000000000e6d78343049a68514271fc785de053ed7f50a7774b87f264c42e03e6f8f86285477f8cc57ae066ef0fde237c8d1ddb30000000000000000000000000000000013e313484da4d6b85634c5306444bdbe45d7db823616d72821eb64a2bb5f352a4f7e4273fb6557039fa563ce1b091bea0000000000000000000000000000000009a40a984be66c3442fc8946cc42eca722187dd819be9ab34a9c3b4b0de7de3d5f126c175fe84c51a6f09e18623214f9345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000008a76db551280cd43d4608e9fc629a021675bfdf9bc5a021546b92f3734acff1e97928850716b94d15b7dbcc4a1e0aee0000000000000000000000000000000000b2262872c268782e8f27ee8fefe0827d45131555e755c0a65a7c8b4185269bd621412b653348d7c1111d681f38d946000000000000000000000000000000000dabcf0f847045e01ef70ceaa32455f4c962e4657b840f97a1cff7cf5073cbf4ca8ea75a4887076f155e27e8d7406c95000000000000000000000000000000000a9c0ed94170eddfc485d9f1a770a8b493d4a59bd7156d6cd4b95b55bffa1b597ae9d6fbe529dc0833634d75906a4aba5ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c00000000000000000000000000000000170ac69c2bf9b48715f445524cab902b18ce6dea7b258481cc59986ae61c8fcb6708b1457be299a6e2f6f34dfd936fdb00000000000000000000000000000000107e855593b6f3bd2982a65167ecead47039065c9ae6e1bf963f81d441f0ebb411eec4b3ed1cff73044f68a4c114806a00000000000000000000000000000000063b470d158ebb4828e875c3dd0ca29a4fd2cd2af356233885a871cb5b77402090f29709c6d6a78f612c8ca4df2f4119000000000000000000000000000000000db75a60fa0b425b8cd2c955e21846ce3c407cb3f96c472cb412498143cc60212de0dfd0bf4de53ae3b345232180b4ad853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef38000000000000000000000000000000000389e79154f627463a7966252deab10b5e809b0c2a9e90989c56d4076b834e2081ddae1c02a9e01b71d96b772766fc680000000000000000000000000000000009109473c7aa614334fde410951a69ac45967f7550890e01b05279b6dff394775dac51d583ae0aa82edda18ecc5e66240000000000000000000000000000000019dd51ec6783c1618a7f12298e38cc75d4fa32fc31438f67eb15419a2f0e9d4b5f70ea59b69e531c868475cada519569000000000000000000000000000000001121c7a6cbbb54d5e30a11a73c158237dedac46385aa15d93592a30fb64fcd94a674cc77afd21a611f704734337905596dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d000000000000000000000000000000000178670fb06f5eb8a4f182913f46f66147deb3f9f634d620ed55da2ccc88895e75f76f55b979e1ba3c3db29710050c7bd0000000000000000000000000000000011adec68ef139716ee081db7122e911ec5a6e1fd7f681a96a713dddc2b742b6e7cf7485b8f45e7ebdec8b1174c02eaf100000000000000000000000000000000089dac9a47cbdfead8536d6cfe8b94d316123bd92ddf30091e16711ff4651c4e2d8dcaf6c72bc159d7de9fd832c6f5be000000000000000000000000000000000c40b871930f0c6826a943a229112f8bf9a3b7d7e07139e1a7d99f97601b6ca8cf3638e0265743dd732cee17fadf996721d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000001040c4cd3c28a752295b115fd80c8ef0e538e1a3906e0d326e46585d633140bd6b8231f50d50c8e7a9018a625c4bdc530000000000000000000000000000000008b966d9433bfc3bede4ddb005cd0c256a168437c31b8ecc83e6fefa6f4b1f2bfd057c78f82bb76279b74a2f7de493b5000000000000000000000000000000000c0f75db7a17e4b712666b16c31b10bb935e7127eb9a0e59e35ec54814a9de9012210ff1862aef5f765d4f7f673c4962000000000000000000000000000000001015e63589a8b56aa643a79c5a433dcd8f4933a10edc9921bcaa7098af435f7879a40868e25d1ca6f7852800df29c2eb3d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000000fab22ab380043b01d312004057488ffc958168f8fe4d9c86af622030121e14a46c4308d711d5fa9a414b9ef75d51ba300000000000000000000000000000000047c738fe5272e695f421ed463ce0d6308e05c23b6bd0973df9b55ca96d89c0771a45d53b4d17f30d8cf08edbf94490c0000000000000000000000000000000017bcb3ed735e5a302f76002ae82f4ac74889fa0e966f0fb611fa6a6a09440bc923f447eb6aebe47eef917753b7427efe000000000000000000000000000000000b189d5b64578eb53ad850c826082265e506ab620a9ab9684cc2a53718f26befc35e9431af012306a6190f144a9632bf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000049fff545ac239696c995eacc560580a0328af07376f5ec819902e30d5e7e40d5fe07295c4ccf54d5c06134370373c1b000000000000000000000000000000000bff448d5ab544a8cae0cacd216a6b6d48f0abe1b4bc946d95c1a8c4ae44bf049c3b572675a5e20c1b4188fa27a867a70000000000000000000000000000000011dbc52baa00712f66def2fa8fc77bcb07431d3285774e2517dcca65e611f07aac265856cdef0c1637def44c382230fe00000000000000000000000000000000090af0898dd578123c65d1f818c3f33866e4acea19aeafbb31bd8da029ed1daa2d7ab3b22147eb32a09021f7a78fdf2acc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3,000000000000000000000000000000000d52fcbe9f1776477a9d2149ca55e0651fe9d098a67209ce2e7d772d4901ff2c70be432b53dc94886651865a81ba8c620000000000000000000000000000000006b54871379e2be969f86c72cda9acab9bc99f73de987f17ab8b25c63c55ffa2cff61b87e8c30d9f712afb62a2b9cfcb0000000000000000000000000000000005652612b19c38650d1babd4772722ae2c560e2914f2e246725cea86dbe1275a981a592eb55077ee4b7c6090e84d2ed3000000000000000000000000000000000ee37a6d42ce69aa67cdcacb19efc230c6c34969a2e081ac77e8f9d45128a6e8fff923c7647a0f168fee18342bc6d845 +000000000000000000000000000000000f114e56d10dba7945d125fe1ab7871d9510771548d8388a2aec8a481de92572645b73631f9a60285c3eebcacb3bc0f5000000000000000000000000000000000667d3f31955df11e4e7896a1856fbd4e573f1cfc906b3953b5806a5d01dcdb96009d9f148156a3828e822435f722c5e000000000000000000000000000000000d7740ae776eb4766999f5671315c8965ccc84ff71757e361fbbb55babeefb96265c97df8892acdd6a9166641f656e62000000000000000000000000000000000166529d1a76ad784557384cb971728dba298baacc2f2a39ee36516bc7a761e9a7c29e385cf5784efb9f6e60e998b01e864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000133e0b08430d9318d98bcf58b3d8f51c7b717fab56fe25f434bf521f830c7d4247d87d3df910490be2ad38adaa8eec26000000000000000000000000000000000e15afaee4f1ce6290ddfbc13cb887e540efc3fd8150dfbf3a5e7c759ccb8f334ba26953c7bbc43b5234b857159f6722000000000000000000000000000000000e4cc685524d42ea5e435afec7b3d7d025e93ea06407a28c246a39dee8ae77514a0bb2d5031f7367d658027299762bea0000000000000000000000000000000001b231237f7b0538d51adfa4ff92bf313507996cf5255f191875970ed4d946cffa8620b44045f4bcfd8f89baadd331fd93064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f53900000000000000000000000000000000084128f1848b2b244e4812eccba01287b9d07e85450459c8c42b01180bdd843058d9926f39e2fb5f610651a00233e31f000000000000000000000000000000000055ee70765f2cccac966dc08abd4bba0d004b379a2c6bf188f300f5d413f84e77ca1d462219bfb820d7f585b914a52f0000000000000000000000000000000002dd8f1d1cd85a5e6ac793f7e1e3cff887204aa4a5fed92f2088c06eae95842ab2c04d30d56f4b0fcfe61379e8e7c6940000000000000000000000000000000013e318f8b6f4165a8096c76ada440154901de42d69c38e66d9df4ffe5476666ecf7068e7163f29f04972682c43f3b0fd5e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000003415c8bab713aa18d3f0d54e0101ba36793e6e9dd3471f8eed9a15e00d8312732a9ce88b5f0c30207aed92eb173ac680000000000000000000000000000000008a7e145e9576be8ba2fd980fb1735a2b73d1bf5f3e108878b721b6ed8378b5e0f03ecac179a6d148541096ba483b40200000000000000000000000000000000029e5554752db8bb87d58275268f24ccfcf3e0923744d57473d54a72e2cccb847eaa8f3bf638833a934c43930fbf30990000000000000000000000000000000000e0f2ead2697110a132c4ce1643b97fc652dd0660deadaa4e0c45e7ebfa64cb6a6fbbaac7c4e2b725beeadf6881ae5893f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000014325fcc087aa108f152b42759cbc02cfa24e7e7cb995c78ccaa9a283ec2029c08cd747d599e0685d365ee99eeafca880000000000000000000000000000000011da603d3a1128329af19e596ebeaa4bad034c59581e9fa2e42a0260032f84654bf5ce22ee32c34eed7515d7fb0fade0000000000000000000000000000000000189cdb5b934cc1ec7ea0cf4b8158a1416712bb59c1650e6d244de33bebfffd3691b499b3ff8255b1b513deba709f7d3000000000000000000000000000000000e7ab2b279d0d5933df25d8fc4faeb8ca907e7bb8588e618b92737fcb6959380abc205118d2e3fc128b89a2ead5ca906145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000895b6777e677732c74cfa82d5348c4c8ddd63ce10347836f5140b9a64dfe631804ea3be8e20bd4438f5e7fa14a121d80000000000000000000000000000000002422cc4781f007f732239ff9eedc126777d6ca0f0365dd90bab6b68c9e3d02ce726726a6d30d7d51a1f0b45aec1854100000000000000000000000000000000048af8a79663aefaff77a934f0af3a09ba02077c13a794ddb88e5c679ce348b3ab0fa217954ce1422f4e212d1383ebdc000000000000000000000000000000001190fec6c510b0b16e1505f737b25dc2401e9fc2c95bca92aa5d6e93b284b766bfed93a80b137e5fcb339983a86acd41ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000014fbf4d005f43563fb7408d1f20f672c8983120c66462ba9156b64a287e66960fecb41ca129b6b14466a5a0de91b81c50000000000000000000000000000000004fb283724950174d60f64af7bc8a7d059431332c8f17769df33f6607d72633aae3a8d595cb8d5af3f8909297844b3a0000000000000000000000000000000000e187476a19280ad9f33a55c50f37f765e343f92938e247ec9fe099c7f3df65e24af14885539bfcf3efe3bde9f2700ce000000000000000000000000000000000f086e6b9e845fe3b0c5100f82bc8aeaed166bed9fa4d34bc03ed86342a997101c508a4c096c4f67cb5791cc1a1fdb8187bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000018dbe48c54347635d4b6bc17ff5ba390a73925f1b180d2c516eafc0936aa9bddaf7317cc0c211fb2a7f7bb096369a45d0000000000000000000000000000000015544c177a4b8018ed60c2639b43236957c2d995fb0f32523654584b0bf052e0930366a93406e1ec5c6d2edb955e811d000000000000000000000000000000000802d2cdbc5e15b25c77ded4bdba087f1d5760e6ebf9549a37f3314b1e88d3d6f58da9d8c6e9ef85028a271b83dd6242000000000000000000000000000000001577bfeaf213ca8b0983cb178e9634dd18f74baf02f6ca31b2e3b287d80a32d4cf11afc71df09ca5bb0bc8e60fc7ffa968cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000007c059044ce0c15bc527b19ce85cade8b1d5a9cc6dd304ce9a3c461e631e17c4feec52a0ab5cfab6a2270c75f73df86e00000000000000000000000000000000076344286cedc8c180e3bd762f12ac08f0ecc51293b9f9b8e7c0056ceba1bbb6fab4ee39cf559fdbd601db6c3d201199000000000000000000000000000000000bf6e708d0a4fd85c7566804e19f21f7a00bcc3bd7135f6639ad30aafef2ed1e72c84c8995b0e59738c2bf1e4040621b0000000000000000000000000000000018ff3d0ade15b690b6e306adaa5c10796b78ed7f8a984f637271cccfd39fd17c1e8288a11b051ca94de2a9bd04fa96d7a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c500000000000000000000000000000000025cb808922f6deb0bed979b80a675d9324cf25c53de373534d771afd919a182af9aa1dc26a2d0284887121bf4d6b6470000000000000000000000000000000018970aa4f456c1b203817322df2e222516bce67ff9ace069599061c6229596e506c0286171f3551302e45b7d3b69a39f000000000000000000000000000000000a57d0da60f03fd4a5664546f9809c771ab6188aca5102c31f26b09950cadc26b0275417ddd9c4f4cf29794b739733cf0000000000000000000000000000000004ebf2bd93d7921d8bd97ee71cadf91145e064a33651da2604ed6fc8e08b1b8305005f12fd4e6b68b7b6a3b5cf123b1324c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b000000000000000000000000000000000c8ff29d0333e3f38fd8af91ecdca49e54ea5dced71b60d693b1bbade99ae668e4f994f7a5417a08a8ddafa410d437f300000000000000000000000000000000078ac1d0898a9e6cae29fe6b50e435e5f543d0ee233346728c46d659c4338295f27b42fc4b2851ad5035feab2bea8871000000000000000000000000000000000b3a566d2ef4467f21c27e4a3dec99a26c304b32ba1fcce8276a8518383a7de44de5b4011ba738dbb8761e67e36115560000000000000000000000000000000015a0aab8c3d51fc3fc8aa35dcd07f8a08188976883f9d3ccc87ee148525f2115ca46726a2e3c550167c169977b216d6217ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a00000000000000000000000000000000115589e8e1440edcfe72c008f6e9cdf13fb7baaf70aee16166e7f32f4651db784f4c5cac15d91ee13001169fa777f0d00000000000000000000000000000000000f86710678b01c8f648bab2289e8f90648d9470cb13d5145ade526696d22508a4a59164290586c2c000dfc55b4a20350000000000000000000000000000000019b300961b40b0d9fe6e292e9357d04f0483ab3a8cc6f8f522153c51d22de8e96a812adf720d13ff7d05d1e68264638a000000000000000000000000000000000a80b61ab051ce413ec838167fce393f88c8a25f403bdf07cb60391fb15306a5271a7042d36f7c46b5978106a7b5293c4615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000567c33d22805319418cb1ea7eca6205a6c44f1f881c03e37bf3c66a1baa5153473cc73b8c25d497b0b0057ceb0395960000000000000000000000000000000014d7a2bfeea6a746e709f6108eb32581ba38a617e4450b3567c77a992988d91f4da31b209286f8e9fd0d7b8628aa6c4e000000000000000000000000000000000ae6c9fbf0e06f2e38e91699cd21596ba90f92f6022a4f3c7c8a6557b7e1331283bd4d7a7d31d77d9d7cf70a2945ea1600000000000000000000000000000000066b8132c73e1da8ae7fec9169770a188b686f223fd0306441356040bc9070f34a47fe1bb8c94de9fd7606c18b1d2b1dd38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f7000000000000000000000000000000001460040d0a19c37fb0736ebdac0324d8a38c94a73fc5f602b7ea5b7255be9d4b6ffc22fea5043d948420e9ae3476f56a000000000000000000000000000000000b37c0078ab8babcefa8874c6cd1c5184d713b976852d087ed84337073fab3054899859d0fac2f4351bb75ee0e534fa70000000000000000000000000000000004150f3b98e6166d9d6b0388342042dd8eff9b8e1239f479330b64c5b316f98fc7bb401b737efb87e1f6663ca4efa26700000000000000000000000000000000043e6131c1ff621fd6f8caf0939487a927550343e24425ada33cf622de757e6e75c9affff9f04373a954557181641617364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c74590000000000000000000000000000000009fa1754bbc957d2a8317a2eed859457073571379cc7c6d65bc6a0b5829f8142db77654eb98a2bb0cfa5223a27d756cd000000000000000000000000000000000cfe8b8fbbff7507d3d74f4f550b4c85e19b8929d3728a462e12b4008c79014103153c69ed8dc6b743e1b6fb4720bad00000000000000000000000000000000017ca0c08c320c12502a1dbc841425694bde68b7806eddbb40702e58ed26c7e112f9a821a6c67afed174f51896ec2287300000000000000000000000000000000014d08df9cf825b07a387642ac9959e8cd15ea8e752231a3047fa30816acb1ecb79f1755484af9a98b993f50128c2bf5031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f000000000000000000000000000000000273b64e867a9111e257c9b32484655e4d7e676ec50f174d9ebc9fc4262c037b176ada941dd8c1abf645e275dde04f4a0000000000000000000000000000000008a63b9604e96a5034d92e3790411f3112c2c7cdaa056f9f1bdfc0b164c37fc9f58dbb566337132cd1626f9ca2618f800000000000000000000000000000000006a661167c9fb6c26bfe0a3902f309fa683fd22729bfcb433756182e7e1a406bf44ae1d13ef0228534881daa339394e400000000000000000000000000000000193c6c5ec200d225c43c6e37cfd15e16e49b7d87e5515bb7b4c918903966f4f6ae0d42af6b98f6efdedc9b0301fa1c0f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f,000000000000000000000000000000001403c7e3059135ebcf5e752011fdfaf66e348135314f3f4239b066e1c6192ffcaf89bad4228fcc2be19a64f4f5386f5e000000000000000000000000000000000aadbd8d0e53d5b409f7fa508089337bcf36212a3f613b37a95757793dd6b0ca99d1b3578ad8020d46e29c9c4197ea070000000000000000000000000000000019e43bb32f92ed187fc32d9dbe24a486e38316a3cec0fd7f7c19b313af43a10fd63738b78e609e04a083de6761d53a90000000000000000000000000000000001490da7d36ff16304b27f6e57412975497e9f3a6d35cb162464bcf69fe141d34ae27a33afc75a2802eb120e90d4897bb +00000000000000000000000000000000038ee0c2c409d8832437ea450ed705589c82791b8319fd0ba6fb4d302d3c5b73ea0521a0253716e5810f03fca2e9dc720000000000000000000000000000000018c9d748aa685bf6e11e6e4b6ad2290ceff59c8837a088b41a08983fb2c5ef077adb0730b298c5df9aa02a820a19a4bd00000000000000000000000000000000015d248426e362ad2489c0c6a567d80b22d54d6a79e198198a771fae4c4e97eb317da9feba8eaafc9460ef45b1a5e5690000000000000000000000000000000005a2342412801cb37911a04d7ee3b1e5d3dce2a06e0658d59f2ddcaa9ba32804a1ddbe8f4d00f4436aad1346ed1ea5344aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b90000000000000000000000000000000019d49748f05458cb9b316e433b0d341e23bb5aaa724b824bd147596761c11efe8f4940eae09e302e563e14e96b814f4a0000000000000000000000000000000018011e7ee4988da168adbcf81cd14a9232edacc06bbfef0fc78dc0f96b5ac86ea67be8661442b5ef60e3889f3137182200000000000000000000000000000000175a2ae3bdade6551b23656c16884ba0fd4247df4ba7471cf81022d7e224b23490db153c8289f95467ddf9671f8b6cf90000000000000000000000000000000013c58c0f55c46bced98faf3865e3b6a836252f252e97b6d2a799b574dc569f09ce33082880a4d0c3b8a2c7c0d4c30eae22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000ee45d5689a8ea6132d5ace000699a157c1cea3c0c98b38d504153d64fcaf1702ac7a1cb0889539d6b15489fef415aef000000000000000000000000000000000b320e0cdedbdc1fc5733488e6d2aece6386a030adc36b0a69dc3809827319947049f3861c2edc859797d30a3689322b00000000000000000000000000000000194096079b3a1d6ab1080dc71bf6d5734bc7b5e7f30bbb0f9b95c9495a6bc4adf76e198fc66accbbbaac215a8932d8c5000000000000000000000000000000000ec07be0cfa9b3d3a64c016471d9e6d25228b46dcaca6e197be00b9ca5087162c35f1d6326a3cf83f568cb06da8c5220895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f6832000000000000000000000000000000000a7f3a3fcf2e7b0ada6d4fce179bdf229454002f1271a39d5e99daae72da549c6ccfc7c574f35bb9784100675c30b1120000000000000000000000000000000000fad14ab095fa09bea919ada313727e7aa5aa06a1cc7746d006e3eaf70f79c5e4001a8a8de03540b45e0598b22710e00000000000000000000000000000000015345ade62c5691690c181da09d8f39c1ead42046987b8c7c975d40690a286a816f8cca519731d0ca23349c54b30d8570000000000000000000000000000000019f0a32361bb6ecd8b1d87c2e15d31c0e0cf995eac9facd5eca123c0799c465f156b0142d98e0f315e9b3595974a7b824c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000017af46e78904915e348734d2450fc6e1938bcf002989f855082e3b4ff3366d81ee8d28293609c3c3b11568668b1305f80000000000000000000000000000000018b0b3859763c2654fc00792a5193b7317fa5051bcfd15ea42be2fda0f43adf322219f34e54b2446ef73a4562151f9a70000000000000000000000000000000015c23509a1b324c649ff878d004ab5f253d041670ef172ec4dabec7a525d5ddb8f9f62f383e3f71b0e9c98532e247d560000000000000000000000000000000003a38564a55fdbe05b047281fa153f736edbf48c901749005473255333590f967171a6fc88751eaf57a5335bbfb6ebe86d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa00000000000000000000000000000000100322c4a92c136437714a6586c82a6842027ee218bf1fdfffaf95ce47c9c8b6c8f61115b092dff81ff2e645d0a7a4340000000000000000000000000000000013a91ed8629acb5e770683015c3c248255d673d4b2e6c96334d1c80326d1a8b4b655c81175e4a914a45fb37c1f178bd10000000000000000000000000000000019075c2eea3f64f42be82fdb8f83f2c68c08e858702a0225d869143c0b017b76a7a40d809116ffbdff6700b288f5ca3b000000000000000000000000000000000598ee9ba9d56400b59c7f5977aef1e179855a37179fbfe97b95f19137b6034568e5c7f616943b4aca804272955d42334cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b138400000000000000000000000000000000025a526b137aaab5ac1b5f8179a18b06feb7c905b4a843cd55e31b7464c2b6d432b569e9bfc3222511c18255102aba5b00000000000000000000000000000000090c20c9f78a242e52daa339d5cc1c3f35aff7ab802a3e4366597db8b6ca43d30fa0fe8d9484e49fa4fd0bf5509f19e6000000000000000000000000000000000e928b2173e32e5fc9c373a2a6f126e1a3a472c01a5e87677be0d29907022b9a7dbec3340cfc89e67377ce472c2d5d4c00000000000000000000000000000000147b4eaa2dcee39b918b7cdf24483b29466120677e5d42b51353a9b2fa207bd911d9b391142a13a212d0ab38adcbe10796111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c00000000000000000000000000000000007790cde9ff8af2d7597d33909f00963eafa228817de1ebf4233ef0831202700b99641318186aec80ac913a1b1143eb0000000000000000000000000000000009d42ea1386d8b019dcd26068ab156f399c35b7d492722a20da0c915f7abe44ba688d9486f4bbb44268542c5a49168930000000000000000000000000000000010611f233bc1c4af0a14e1d1b945c91c077ec3dda592e2f852e2de41e09331664e1a92f9a0b7416c50327bc943a17b9e00000000000000000000000000000000048614243262dd070a754f40652b96a03326fc51273dddabed85df0654890ff38e0da7abb8190e4ebefdd6f78a5fec509d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d0000000000000000000000000000000008451d24fdc873c61db44e57372d43c35a2a8098255f9aad3a6b244913b86bff6444042e391685b1244f009c5ccde935000000000000000000000000000000001177c2da9972a2b96afaf866f97dc149482fbaaa93e194803c09c8334c2c7025e08cad4f7898959a57b07a545ecf76ad0000000000000000000000000000000016f40426cbd1f0f4ca5ae1dfa4c3960a6fbd51a1b5b24ff5d03fb9911e908406a0ecf4f20a78a280d24dc9bdd1c0799b00000000000000000000000000000000194a8c55f549da1842cc3173f3eb7bfd70df26b43a3059a3590992e34fb19b2caac4149f64d442965e166225b9013e2b11ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d3000000000000000000000000000000000a97664c1d7624cae0e969c728a84130fe260581305435ff8ec701cdc51a73977f58c891ecee637eb6b7c972069ebbb80000000000000000000000000000000003f4ed6a9e9f4229f0fb35394bbc10da9adbf4985d4453da64eb312ec88cb15bdc189a3b5df1af3107a36fc001ec92ad000000000000000000000000000000000ac552c5f6170a70563fcdca8e0c6a7c6135af2f9d5ae6f60a2c459d1be4cf76ebcdf9bcd891db8a1e2fc905a23a97b4000000000000000000000000000000001734a46c99e776d1ed4b807f5b313562e0989ad5c67dbcb961c134f8b7b7601c23308839569dc224bdf7c370c4498303b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf440000000000000000000000000000000000a523182c886671435ccc75cbc78293274802c6142465acb31a1809e43b1d656ed9c808068de167b1ab126ed0f73a4490000000000000000000000000000000007c4616080b5a002fea3589d54c7510884a3ece705d27dee315851746b1ee748e8a08d3516d8c6afe1c0482b960a9c62000000000000000000000000000000000dd1bd9b4b9c140aeb97887a0266bfb5696813fea034b78bb7d0cf1cca15b5bb0ed92a97841c8d8cc614f7721b8b7e040000000000000000000000000000000012a41a8941b6f0e4c87f8188718f9bc75305d41d6f4441eb9682473340fce0bbb463e1b922d3af8daea32b8a8ac9c3b4a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf241169119000000000000000000000000000000001763ab2b361681955735ae00b69f26e06469391af993c8dc6f2e1dffb52ca01e49d58d6e2249e7433ccfb5ddaf8fead40000000000000000000000000000000003858f3bb01b2393aa4d4d7889bdeb0bb9bcde0dcb9b39c4ffe0fcd0b865baaff75b676c715be275929ff4303c416e0800000000000000000000000000000000086d64bd1302b0b3a620b87ac29cac3d9e606513ec8b47898cd852bf552c1364291aaa842616b92c8936e076e59451bd000000000000000000000000000000000967c9f59c15ed02c9b2da6e76fb0bf3d445ba849010afb7f9c994b1ef6a05ad577570d4adad043796eb90e51537ce5187fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c58945000000000000000000000000000000000e6b0da7b406bcac2dbb90fbf430fda6442cc2860ce633ab84404dfbb426949d55ecd72992da1a2e8e1ce229b599232c000000000000000000000000000000000fbe3a345ffc8fb85cedc4b8dedf9d952c41b4ff6f1c7ff4cf91b2276621969d905aa9aae5fc89bc516f96b9bd1bb3c10000000000000000000000000000000018c2a7fcc35099c41bb851ff66abb047e2af9cf4fa9fc45f030124ea2c7efd26e594abbfc7a7f258c8081a3a80d15105000000000000000000000000000000000a27cd33c2121c9c542e27b52a13275ef7e81dc0c6ece883b65e71d2bc3e7246f95aef7c6b41eace382a1400568cf298c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000158bd8e6198d22b52efb7f3b945668666e1190a4a8e70307ba5c1b737316a8f8568092f219f683c0f53f56f25745d4e600000000000000000000000000000000097e64e4553371c81a9bf553ddd9719f59b329284eca0d76f023d603c29a034d123ab777cf173c5f2bbc66412d69d4ce000000000000000000000000000000001298cd5501e136a06ad4fcf87a75c0c7b96c73e844863b74bf6aa581a0ea98c2b1f608c668743a3e37ad5ca2074af9340000000000000000000000000000000017ff9f1336d7f2152f17daddde9d3e1679cab8120ed2c0288b0908d4e2099a08c9bc6f79425f004ea3ac4d684abff6dcf29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b4989911700000000000000000000000000000000115baaab8f0331894da531ab557bb454e2003010ba1dc1d96e3d983d49b1312585c6d4c43d85dc074b23b2fb28c8a1d6000000000000000000000000000000000db1621b721c8a54ece26a355b190af5f3e1dc1b43e0827a1912ace651cbad4b980e77a4c3566aa809157229b234c808000000000000000000000000000000000c594e0ed3f7ee55886e251deef9732aea3de11f094ec53907a843b755add8fa5d00779a66621e615ba7772ee821c4030000000000000000000000000000000004e80aeff6c4b85188903b4d2dcac4f94f7cb4285a38f94b0becb556d83dce8735d1db5810b409d45a8dd1b9a6dde29c125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000371a74468ce2ad90e19b7fe3f57159dffb1b0422b32ad693b2fe6c45c5d371b97a90054095da887019d25c1ee8197800000000000000000000000000000000010575e1ec9a3e609ca086ef8bca679c4548482d9e0da2e51878158ac8e5b29d824c31ad7ff642041e748efc50c2514e000000000000000000000000000000000ef36130380f1e84b2f462b5f970abb8535431b79813015261015c6d7e74f038b47504de01794840d93fbbb4b386e17500000000000000000000000000000000018419e85fc2d75f007d1e0e02c1975332e03d42c3b41c50c3538c3625e702161cdcf8913babd2995aea7566ff15abf2bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6,00000000000000000000000000000000125406a942ae0119575453beb4c093d2696d3bea7bc031d7a586439197f848e1d5a82b925b4e96138a3460eecf198ffa000000000000000000000000000000000befcee6bd1412c54674a3d519dd2813b87b18f2ab3375a731197e9f539f8f8fff634f15647e7fea3c65b93594343c2000000000000000000000000000000000011e4d432ee6babd502a9cbbb5cf4839dc6da6176b6bb0ba51d99a3587465f5f3f83f4d4cf2c7e6187de93b859ca61d800000000000000000000000000000000168509010b867aa198fc294a5879ce14a51503c1d0e8fbc02ec08cf62afbd357ceac24b633bd0fa99f83dda92e10724b +00000000000000000000000000000000070a0d060c6e9bad0d1bb15417578daaa8b7a5c984c4947dba10fd874d93fd1e3994337c87799d78a674087678d9168f00000000000000000000000000000000128985b69d5d6ea0ad0b19eba7c2b430f5242a7e89626c66fb83b58ca7cb65a479de4b2fca6886cf55b8cfb52394102a000000000000000000000000000000000bb0bced708571662af042d18956b5b7d797b61aba70823618682287deebe69bf1f9a94ca4059e0570e25a39e60b9a8b00000000000000000000000000000000193f0793324dc78c40f356dde030b632feeb1609a1bd75ce88f0d313a0864dbf1f5e92826870866ab9b3c98cd1c12aa508c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000000a61a310f90a5ffde617b78f784b2e699cd77e7c3e7c483a2ccb768f94d68e59a2a4521410c22ef6f21ba589ec3abdcc000000000000000000000000000000000e6568c83e0f7e459b27a28e5bf954983c5dee478a009c244da16041e710ddc67479cdb3da6f47e7203fedb8f765b2490000000000000000000000000000000001c5cf6b948b85a1c426fe932cd87605f1fbf6c932756eb1bfb43beaf012bec4612d8dd0840efd4cba3f5394beb65112000000000000000000000000000000000e02d5bc20c40d7cc2165a21ab37c6e4eb71322c01a43f2085f93b5b02bcabcd668dab90323db0f9288737d757997631a0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000165287d72eca1ecda5fe16a555245b0a34a04beaf9177466bfd88bbc675442d206e70f7a2063b6ed0e15e9406232f5ea0000000000000000000000000000000004c0608bd7e01e65a15716b0c505111a3abb0abac3efb846e05e8db59c063950dcee052f04d1c4e9e492bc6740fafe6d000000000000000000000000000000000de897f7ebaf9089f7e198ee41e1efd7d84fbec7327799b9293a489965cd36159442eb0dc1f79f6b1f122f592b013bb30000000000000000000000000000000009774586dc359e5d20486f00dcea6ff93948c5a8b74058645d1048fe46ae3330dd56d85204d328f43f15e674020f353ec252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000438ee51a560aa419ad6ae45e1014c38b7c43f1f6a512bccc2d4f10a35838369b71799fab4b6a754fd938c1a1b874fc0000000000000000000000000000000000c1491c85965c0b74d08f5866ca727fd30bf641a6ada0ab8363ff01916c37d10b1b7eccff79b396c587d9beca2c826c0000000000000000000000000000000001452f254ceae9626443265ba31a1a750a425f2a7789e69cde16b70eb319c744a6221e74a9e2881c6bafea161d29638df0000000000000000000000000000000011bd6a1bbded174e9cb95d74492f7b07a755339a6c40f2a1a76debccc0f3a32c7017ca4e6679fb2c038c751f19986f526d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b00000000000000000000000000000000139157c34aaf70cbfaa82be655281b085e37d6406df4cf8e291b221394e91d9e3cf04d431f15436064d0bfc8cbe13701000000000000000000000000000000000353fcf6e587e71e59d8f05d4085961d37b1f62694dd5c7f40efb5875b90459dd66c4d2d6c01a40834307ae9e82c2e08000000000000000000000000000000000a4975c9872fd167d0ff4cc80a6ce179b1e6e1eb21c8de80321451b1deffe68d8a13db26218f14935b64af25d63644c10000000000000000000000000000000001e8a2824f21cda745a24844ac0336994fb18e30608ac61201a932c0a5a58f1acd56cbd9353bfab4944efcf2859ad5915c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b1973000000000000000000000000000000000537d7a9d7d9dc451cba4d50630caed32e182cbbd95212577b8c2855c327530e447a4f3d73c7d63fa3ad5111254c9ed90000000000000000000000000000000006984b32955fac4ad3c0d181c81b98534ebaddc316d51a40baa1028bacd6a93a20d4bd6cad6a0f8cf7ade96bcd4d68dd000000000000000000000000000000000720c392a663884ad4d8daeb7279ac41717ea602108c76519da13a45a77d2acafee842828f5ccfcd786bf7ea88afd01600000000000000000000000000000000081f1d3e37ebaacc11671bfe1670ed65ece2aee0e3b5d746a8d618b44bd4b7dea905eb8e958bc026a092b2bd5a7b87cb11009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a00000000000000000000000000000000005af33731879a574f39dca99c5c1b9517eda13121221be77a0c1bac82fbf29b37889c15a9d32531a3f6bf9137ce82dc000000000000000000000000000000000c62939f00d70a07a85804cd97fd34b9764565bdba225cdd7549729ceb9735bf4d09a80ec3055c483e1e24b66c41e403000000000000000000000000000000000e415677988c9d4656e59f77c608926c83028f91bf4c0634120b5f774ba07180b98141ffdf727cf9d0fc7a4cb52f4393000000000000000000000000000000000c9c37eaca857151a0c4a49b079f2f061e6a8ebb77e11eb32b29227529562f8dc8e2646e25469491eec5a07b11943f203e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf00000000000000000000000000000000015113f8f9100cd18427ff48038e1070fd835fce6c0812b7bafa679ac733c80bef56492ec3ca08c1117bd0edf19cb26f000000000000000000000000000000000789cd90c0be1de5d0b359c030d4b9d8aef93951e26870e37c375b9e7879cf277971a05babd319a3a6ac53f00f3254e40000000000000000000000000000000019b1cb91c9a1b1ee49c3837339778806bf0c093f171c92c9931ad43e35fc61cc08dafaf55b7b9e0f49dac28a12bcf92d00000000000000000000000000000000066c7864631333226f191e313436453e59f48f91d42e68874fa4da45eeda1f6f7f6342204e64e124d5ecd861f02ef4f00444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000129d887d694be0ef2f84c343a9aebd0a2aaf19a4e78586470351ffaf0b1309593363bd9c6e7fe39a6e59445d935414ef000000000000000000000000000000000596d7061c2399b6a9be7d4d495e58c0377b18db1e45cf3eb431d10cb8b15ae42548a86a26086d57b1a71cb5857d7917000000000000000000000000000000000cce7181fc87dfe1bb493043279a5d93cb2d980eed38dab2ace8c9fb335c2890447434d80df6e7c95729933ada7b9d8f000000000000000000000000000000000f0e1274ff70bc6d3f1d0d5b251ae528ed94aa3a1b9bbdb260892bfaa6213892071b8a6407abe26105b2f81df90569492035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc7360000000000000000000000000000000001192050735b114c19eb2bb9aa01f04d1fd9bed4df877113a14f7fbc9c31acc10db3ed0e0d15d8433e7408bc237c985b9000000000000000000000000000000000a8a66cda780790311b56836fe69479c7b94dbc6c82ed5886887dbb539a40390ebb2683c04078ed105e639a2ed8732a1000000000000000000000000000000001678ddff677b99011c73e0c9875b5b2ba063170f4d565d261b4c6d3263ccce0334b5bbb7ee08692568037fa96782e48b000000000000000000000000000000000ae15f79ad7f790f8ceaf7709f4b5da71642da0c1f7c442eeaeb165c7dacd8a4892fdfc8447a03a7c56e12513499e43c4bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e8260000000000000000000000000000000007fcb5ea5358074d06b64c5f46454e682dd9ac2127374c83f3ac5ad46bc5fd2fff7c5a80ffc669a1c159ee8c9a01bd37000000000000000000000000000000001010ada1bd493d6282ac2d3582480f50074a02fdf412c63e93c5857974626ff464150c20bdf23a87692bfe69a075eeb300000000000000000000000000000000086bb5664a8738f02af5517aec4c6db47653a6d76bd4b5e37ba4d8b27a7819e82e6a4c7ba4f8377e06a5878e7c0bffbc000000000000000000000000000000000be1463ab76e468e47e1711c158dc9bb10d1278f5cc676cff937f60ba457061bacdad7b8d3286f40219963b147cce4bd369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c3329d1e1c76b0bcc7ca3766b2cc5ec8169690f45e0ea3e37b7173bfd6c884921c7523ff25391a85b47d5de395ca63b00000000000000000000000000000000081ff066c008d5a4c893a636d24e9752c6a06666dcbf80082167610e73a32d70aae3e58c88ffaa27f05260b86b11f72a000000000000000000000000000000001178e88c652d257888cda1c0b65ee2c0636184194fef9e6ae3791a85417c43a31fe75893773ff3e7b4d4cda9eafa8de40000000000000000000000000000000019657ec4604ab5e8812237a28e5ff320a0d728c60c541142ffd87fec2c703665638e5eebc33e308d5582cd043d08d788ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e120000000000000000000000000000000010789200f69d8acc70f108145804b62b521a30a04176c449f52bedff5975ad7b273aaf4a32f8461ced8e92b2229e2cef000000000000000000000000000000001178c36174cdb783b5b09d419ae4a154512bf9ce07368521d1576b2f1bf39f98be29bf533bad16ba9d96aae621612aa70000000000000000000000000000000002580f2115d1814667b6178b6bffca6a4d992eb66e9601c0d21e32a5f3b69e3f85e1205c877b2dc2696a0e872c5bbc6c0000000000000000000000000000000002c94d7ff016d57bd5f589971344c6499577bc2234e18e6c8dfd7d27a205442a4236ac54fe279d1bbca76467530140b42cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca36100000000000000000000000000000000108b6fef7396ef71b46339d421726f83b08320599d66da18234011720d2b524d24075a255d2771f1ae904958c50a9046000000000000000000000000000000000723d5045b65c0887da1bb01d874714ac86d21441119a93a1d5758957215f399f5ef1cbc00558db01b295bf0cc988cab000000000000000000000000000000000994914a3df9d3094dab0c0c41a45315dce5968a99e6171fc609ac9e50bee5ccac771efaa04067467e95709bd924973f000000000000000000000000000000000ac746602f804f52e9a485c30412adf92eb9af3f6daa8f23b974339a0ffa6f5aa1b70a80a9f19cde2a69a4b7251ecf5d60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff790000000000000000000000000000000000a2ffeaff148dc5f70fcf53e7e8d7b6100cd6e7df5b3fa4aa33bced243f15b4f77f48d25f74366a693404b6ed7d3075000000000000000000000000000000000f3e1b34ac8fde4caedf3d8c3e24db02de3f91487db300f09c779e7e4e96ae55229288abd946abcc3a8adaf18a0c89e000000000000000000000000000000000166a68c5191dd7f9d44eade2ef1a9b522dc062bba9c55e2ff03aef400e5d2765a12816b4ba51e10bc21e06113c8ddc5100000000000000000000000000000000109c00de20f7e827375c1841348e684fdb248fad116e9643dbda8be2bd06b71db264e9f2c40dec2092e7d518540a6d82652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000014ddb61173359514226c150a3343576b04fb1b06fabd8fe2f921fb3b90baf5513447c107f6d2f96c8b03274bfe451dca0000000000000000000000000000000001d1064860f6c4d62a282147308e80ceb0c5dd62f39b3232a231b1b287e497df31cbc5a3905a7687eb2f24447e50a395000000000000000000000000000000000859611bb3962955f92bff861e03d07bab7fe1f69e90c6bc7928be8d1758c9194ff7a52b16472d04564607b742543eaf0000000000000000000000000000000008a3e8396901a205a071aad06ba9812207171f33775eb358de4232826a5f0ff50ec3e137b1344b583849e8a5b424b46676a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20,0000000000000000000000000000000008c9db83241e7f3ae6c2eac8fdcff5f2d35318e24c3b4130e9bb7048a3b84a52fa3f222a8190121d2a5b8835bf911bb200000000000000000000000000000000002db79cbcbabf41bd8c715e024f4687bc0d058d76b8dbe58ffdb80918212ab6e9b35256fde583c0fe903c34a4c41ba70000000000000000000000000000000019f37d05f5c9e65c6f004e1aef03ff0e1899f0739c9cc4e9038e18f9d45678388454d144495b2cd993eb3691bf3e96f5000000000000000000000000000000000d8e0d7715ed71291729bf480f5fee7ae04264015732677488472bedc0dbacf8b35eef7adcce196e3bba9cac0991be81 +00000000000000000000000000000000064a134260b753af73df3764ab662e3b1bd624c8f3248e9bcf7676d8fb0825ab85ea33387d4641c81fb8ba3757e0870a000000000000000000000000000000000d67eff1936a395cd3f808ed7fc89f8b6a227c4849a6941d4bf762af6e41ae41c8114aeccc2565ba01fd902df530df1e000000000000000000000000000000000110ca2339832e7a9468844b94b3ced0c9216654bef1c8a5cf66385a99d5d452f978bbb7fe15fb477f56753488fc909b00000000000000000000000000000000173210b548d1b98b926539049996713f53108cd2911105235c1d5258360d5620d330951db67219ffaa304a67fd6219f39f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f000000000000000000000000000000000f7c3795ac3d511f93a3d85e65261e4c09cd316787f74ced6e472a3993b7b5b0ce5a7c91d99559a8e0791f712cb4e1700000000000000000000000000000000018eacb2c5fa9221881c6311256a69c7616748deb3235c61cc11412860450151a25e3d6a220bb23e0b3e3325044fba68300000000000000000000000000000000121827286873ad31f58cb3889fd01cb7d0f91ff1c241295f6ef2dd0e8aa8638b63a7e6061efc2e7ca1d3579b4868f0460000000000000000000000000000000003a57315175d70880b2b53c67d61831ab066b08d7ac68637364ab1c1f3efad96d42a3cf5189c45012c1f73a1b97bdb4c1deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f10800000000000000000000000000000000180648e5d0bf727101417f515cb9578bdde3e9f6c4176d516454ea7c32c1712610cc8bbed303bd1afd48f580ec11b77c000000000000000000000000000000000d6ffa9b85d69b67abb77f5c8bd776eae82d1cb055d2dcdea31ac66b1825014ec7f7a2aea320ef9f6897c9aac8c0706900000000000000000000000000000000073214fedbade28cc60ecfa4e1fe2fbc05f3d71528aca315312d50214f680956bb9e0fc12783843b00b3f4f0f52efe2700000000000000000000000000000000128f87e7da7b53f28944aeb26ef0f6c99d84038af51a1d242501ec84b5a6a8593ef1a0f6b523478d9fa12e36c2fdbe694b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000001443980d7450af1e19949fb328776cb7238a9b26240cddc565aa9d52c5592083b1533e8103dc07eac80e4bd830f209f000000000000000000000000000000000afdbea7f1cec534c03d3269d50017372f7ccbcba9f096fdb2754af4d6b4956decbab2b0afb69f97a03beeb20b4ccc31000000000000000000000000000000000a83dfa3197dc65097601457a97d0df7710e001e90657b150e289515609f13997b454167a7589ef218893309460139f300000000000000000000000000000000029c362244510c342358130f877de947acad5a379295f3149d5c713274316e06a169501f889e4b9cbf86f10b9521c1bb124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010a83f13a185c70ca3f724dd84efcfa3ec463d7c05360056f8b5304864b20025b0a82c9d542ba08b645e2334f176472d000000000000000000000000000000000848a6a18bcf64d083e118190805d68f7ffea8b5a66e0807b9cd3733d31ffa5cc25dbfa6ada604646dcd8dfa622e08a30000000000000000000000000000000009962205c0ba43e5101fc3d5353f429a57a97bcb84baa0942a7e7facdfb0d032b9307aed8bd2ac9094a2e5b1460db7140000000000000000000000000000000019b1012661a10d31a4a73d0cb31f7eec0e7be729a42baf560c1e90a9124fe8d5fe31ecbb6d4954dba7d943a7af773eaa5a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000e7f02c1d2ceae60f314f51374b338c329f2eaa82553c3fc1643c7f1910ca24e277f3d658f552a47f780d4d9e0ac5e030000000000000000000000000000000014b6b56afc4afed5199191ec13dbeedd797f14ed493c25658a9658f031ac8d43de12e6a8c4b1671c9e5ef78da1a55e2600000000000000000000000000000000194d8a50618ff55ba3fa5602d41cbbeadc01a348ad1484c5e9aee5fb7241fcd9018f436e3c6c6dc64beaa241513a6c8300000000000000000000000000000000052681eac4bd59e160b67ebb27582a6d3ad5286d652787a0e160026607acfbfc5b9f38b9b171375079d052cb242b87fe8c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab87000000000000000000000000000000000d96ce83d917204e674ad9f5e5728651f5f23df25236b0fe769be48adf482ed8c36ad9c9abb6efa3719bd35324bd700800000000000000000000000000000000107f55ab0e5b60dbcc0632c345a9e93818014d7657b264031709275744e1c6722ec63aa209e655878a57704ca6cb3bc10000000000000000000000000000000018d97fba324431fa28b8845d94f62fc9eacc0253134b923908f06889d375405b51610ac21a75bdfb27e3533dd4debc22000000000000000000000000000000001667856804a5471238ffd64bf3bf266ce3a2351ebc68265674bc86ce6faa8dd50a3dfa00c647fb4265951b3a9607ab99f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff000000000000000000000000000000000fde2fd0349e7a47a9b6858014d551aea569ef9802629bd9520e303ef0487c9d2d399682ac16ce6fa03adb6f4b478fa5000000000000000000000000000000001858ae58920dd0abd8ad94d2f9f946c53e050fe89c61f62fccad37e17f8723a4fbecb6b1be1e3cb853f045d0dca8e53e00000000000000000000000000000000093615a7f9d12e92c90706a47abe9620c4db41e95e42e478949745d6b73e021422e40b969e9e34263778c8a4d4907445000000000000000000000000000000001006ae7963b1e1c4d8c2c85175aca958758fb380019825b09ca3f728b5356254ae4fc670aa29812320b921b48a069df622d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000000ac407b75ea77789748e7607b5d6edb1d891875aeef2802715ddc393818fc8cbe82cde9f96377e3ac60107ddcda7e6610000000000000000000000000000000006e63e49356c38b816736d1d7c360ceaaba875c53c98ec68cb825962531855dc6410a125b914b0ad99f6f4327f5450890000000000000000000000000000000018ffb4ac95b8ffde112c8bdbf07a1c97b1d30a42dd4a97c82617698617ceb169e8702437ff6082a2ae387b462cd86256000000000000000000000000000000000497c4b3788c4d6c9b4cd8b3d3569ac4b4332b2f76c5f03f112e089bb79d33152b2469f7ad3eadb8b954775aab73f47de816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000003e16f2f5a2fe15fa02b6217aed7dc688dd2670c09c02791cafeccfceb7d99ce826bccf213f6a7c6064687519f9283de00000000000000000000000000000000095e6638ac74815dc451b3ec85a6a8cc18643b541e8be99052ff6dad39c971f2e8bee976ab2ed5e1cdacf92816249ded000000000000000000000000000000000f2703c08b1d707fb6de215de80b53ffbf2ac48f3dd059d2a952b1031189248fad27beec5c8591ac93625a08e3420f0200000000000000000000000000000000024ae36412ba6f2fdeb0777b892f1ed7bab0527879d93f7b71b62f437f5c1ad1f04a5a7380ae5990a455f11870c7208304f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea0000000000000000000000000000000000f7ab1908c6d4b152835f950b604b55fdda7eb55c6b90c05e98626ba7cd014683bd3e219fd0d5983e9dcfaaa5d389e560000000000000000000000000000000010b285c2884dbdd540d6dfeca704e00839337f12d2267f6a3fc731fa0f724cde19e268782b4b9c2e11ec3aef9a72a6ed0000000000000000000000000000000014a40cc55570e8f45369bd9dc622e05f03989bce6a98a0d87f4fa7add67eee3e2ad9a297615dde05e64203e86153ec230000000000000000000000000000000007f2b6a092adc595e4857e821579801301396321d4a20bccb3296a031d74a62bd79ea4ea094d2e545943138d2fc930fb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d200000000000000000000000000000000108834a685455dc0be10aaf54607a06100673140b012ef23a16d3df204a81dd8505d62ca3e0278a2581abc59e0fbc421000000000000000000000000000000000bca7130de9896e8d6858022f24308af7ca66fb4c91f38b30f717c5491996ef4cdb01f4d38a730f9ba9ca5af5ad1de7700000000000000000000000000000000007d60ded107a06114afaf741dc8826f9e14bac6014eba26089c4e31a73b0f30c4b6e22533ac0db7e73621cecf753590000000000000000000000000000000000b538213a703f7a0bbcffb4aa8ce25ba2a538bf599d3c0251f5e8acddfd596c9912d4cf9a1bd8d3ec070713328ca992205edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000cdf0b9bc829cd8537918d665e5bf344d309678d01ee80c71a6d6efb45ee8a7beca35bb5ee046e0a3fac76e1771520ff00000000000000000000000000000000014e5be9dca2f8ee4da18e5ec9c4caa891dd78acc47f553af584308c72988435b85ad21b14abf8421bdb9e25164d568f000000000000000000000000000000000accdde22a1c479e47a17b8da6f1d2b7f780ac278c68a68090e5402977d897bd734f5af8164118d613f480c1f65e5d8e00000000000000000000000000000000029614458afdf6b572bea02a0af987d178c43650ca1c80a297b1d31e259aabd3e2a2c8e4b2c044466924dd6e5e3483e6f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b0000000000000000000000000000000004e45cc43d4d10ed878e18df156062c799a687b8e6beedad9fa6f66ad855cd053af6918e234ff9a43561da7e67f3dee10000000000000000000000000000000009c9ae47a76c199c93c38e7213c8d6c030cfca709714c703839b9ae9b65207e83486f9c8c16373e2b37756f3fd4355fd0000000000000000000000000000000001594ce9c2e229491b22317452938115747515ce62a0d49f4dd12667f5b3e7b541b3775c9b1363cc185a539b9f7596330000000000000000000000000000000016bf68e05e32168c69ad67331d7bc88a6d130fe8aed3e42eddfeb1d92add266eb69487b246a3ca961ea6ac0a35f8da78059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b41900000000000000000000000000000000080d165c57354f87008eb97610d4a596f180e48ed3190779591a0f7e07278f8d2fa6cd21d1b10e6347f11bd9731fdfed0000000000000000000000000000000008d5a1e66ec76743ca366be80fd1cbd5efc9112dbcfa84ce6c44e8df03140ca5f07d4bafc6c6ce5f2f190ede55fe8718000000000000000000000000000000000d0e1d2e5ef384a4fb314fdce54ab7895f895b3bc669acffd48e92c6320024d4f371f42071fceea550c8cf68615b00960000000000000000000000000000000010beae4ffbb68cf6e5d0683dc0629411ee14563f84788d50b1c8755b0b06092cc0f0ef7b55a39d51945b5178e374f8e047b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface00000000000000000000000000000000161486d422462460923bd98834f0cc270982087697747fe40eb9153a7923d48eda191e4e7a75964f18f1df9365901a360000000000000000000000000000000017ab168a4ec81c8db4a74d529670fe6332b3870004f696f3a143cd1a62abd747d94afac9485e5dc19b0f4262dd379c990000000000000000000000000000000001e9cc85f03039ea53253f0fa2420012171fe39ed8696ddfbed57b80b73476171e59631388d75fe43aafde52aa14a64100000000000000000000000000000000109a5d5449002f4bdca44c0bd141175d5ca1cee449302f0314fcb5f282f022a7a3cef77f4e9fb515107e797726ff51d767d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c,000000000000000000000000000000000aaa5de171664fcb45439b17a024806ff7e07d02294e0592ca74752a5b66f3365d1b49d6893b3bac3b8b0d10d026e48d000000000000000000000000000000000418354ce1820ecf848321a07ce22117303e5a15169a9cbfd141fb4797de8871d84d577e86270a9cbfe31c088ceed0250000000000000000000000000000000016884caa03ea641e0660a790975d77c5bb03568f873800d0559b69e3e0afcc10ddf031bb5c25c46f136f0791bbd3cc8f0000000000000000000000000000000002bdf659df76cbaaec030448e8f4bbd6b424037a8dfd7c4b8ccaa2224b0852c168f49c6f45c04f23abc85b8df21953ce +000000000000000000000000000000000062bad6816308f1c8c6941980caf71929a4006083dd29827902ffc92ebd9b14f1ef662f3a0125b1e74dabd039f9106400000000000000000000000000000000118e4ae76e2c321a5b89eb19b58f58f44e80dcbc7bd6d619579da40e1156aab32fe81df8eeb1bd047f96d65aed8b3b6a000000000000000000000000000000000c8c93e1beeb4efe52a96e5d5612338721e3e487c13c18b02475f9ccd8fafc2c95101aed291951f2031bee5216dba26f0000000000000000000000000000000016fba44e9aa39a12ae27e3c36de1f14e3f37ffb0ceaf5fed2a0d9815eab02c5aae91b254812a8f3a2e3654cec01a341caaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c0000000000000000000000000000000013716488daf8586719c52fcec80d35f17d4c595b66c7f2138244f3c8cea69b819778bfb50e49ca1d092e57c51674fca00000000000000000000000000000000019cee25c4731bf48602ceab23b5fc4f764993443e3622107b4c33b29c23d1b5916380431b7ecd94a0ce99811fe6dadba000000000000000000000000000000000562b28b245b7c1ee531a320fa0f4e12d7c171c7e3932ffda6cfebb123fa7f5993e5ed5e7b7d295405e5031b339994bf00000000000000000000000000000000180c4a8158a26d34123c870bc694382352a8e4de712b650d3e45e6baa16d6950ec15d3a4e032c1d1ae8fea18faa6f3d8db48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f20000000000000000000000000000000008472d40e0505d6b8b92500e8e9711112048611fcdcca2377481ae86a7f6da1571f179183301e2194a42dac3873a3ba5000000000000000000000000000000000e2c5b61c050a8a12298f76b5f15383e72b90b001fa26889b67a24bb374b63c1e00979b05450e44ed63e72042af6d46e000000000000000000000000000000000e8723eace9c7a72b3e6097afc9bcadde61462e2ee03fcd5ad1b1c0dcf39f437f80530c2a1c5e6ecdaac14e8715f02e30000000000000000000000000000000002e21e0f451d035a5257fb09e9ed17b27f0994e6d85ddaf8d33153628adb194c97db17656351c029be4d3125bd29dc22ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f40000000000000000000000000000000000dce49634595869d7858e95a301bcff8112eb73dca8a22042137456d6d4887998a541489ff09f8e006176e6beee4e300000000000000000000000000000000010835f7336dc49e62706da4ef21d8e3173629b16742c317c1b397d4f17ced40a56520ea63557d7ac7f251568f4eb3a220000000000000000000000000000000017446ebe659a4510a362ee3b406b636bea8f381503e51ac21031c7cc92acd23046d62c2f32cda01b680c0f107142ae7d0000000000000000000000000000000006ef82deabd8983ebe4255d8e06f4a1b3585c057b2a1ca3c3e1cf04b582b65792e9980e3a1735a8ad58b053b16ca03d036d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f46000000000000000000000000000000000cd8e7422ee179a0499178c3848cc4fbc87fc25c8c882f036a03cd9d3f273f7f2bf71bd3c9cf5e30c42b1ee6e90b36fb0000000000000000000000000000000005005a471d77a35e922b6d6a45b13a90947c2b31d8e7a2e4b6388265b039ce23ed958495dbf904186bef60fd547b941c0000000000000000000000000000000006c337380065eb8a5f63cb20fc61a9eec4ccf0e23c4e0f231a5bc4d765271b9c5697bbde692b4828ae22ea12423ad932000000000000000000000000000000000f7a0080cbe72a6e6473f66ed729f58683a80815a1748e52f7b67a6bf2846b7df8e7dd8599f87fe63706e9823bfe00d21a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000c94834474ac91547546d7d179b2091e33c8812c1b582ff186e69b63011177283a74b549aa342a7f3882ee82ad8ecc03000000000000000000000000000000000d72c4308e9ae695acedb9413445bf6a40d59ca78bd4f74ddbc1bcd8508cfb521bfcca99c98dad8022d3d1ccdd98bca9000000000000000000000000000000001487d006830d00d84a567c5d031019035443fae4791a05253f91249b32a4b3e7b3ce7eae885b8caeaea411a90b3445e0000000000000000000000000000000000d94f17aa100503f605732a48e4f55c394a8df1421a3d7c78bc85f4cb7a53744eadcf76e1620fc54204b123d6071cd3bad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e900000000000000000000000000000000101cfa8d9c7522277f2bb4bae6c09e8b93a876c749c91c61784feeb105be61c2479375abdaa81deafc2fe754ed6cd9da00000000000000000000000000000000089ebbdd489ff670a70218f5aaca78d4e7ade483c7f20de4a84d39217be8f560fbf7bbe36f3f8b8361ba16d17ce609d200000000000000000000000000000000094f094372b2315fabc219099200e7b9e2f3a2f6fef2ede6f83c82f44792da03aaad06b8cd06dc3f140746bee2a45706000000000000000000000000000000000cde6cf9a3a7018b2b1c0c26b5850820080c7e4b56e615d577a78565431c93de78348d2851d5ad9f120ddaa9ff3da31b8fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000059bb8e5dc5f0cd31cf674ea78b80b67b8a8a753e51284a2ab37d3f29459250d904e70ed00481b73556970a7f5424e5900000000000000000000000000000000043c6a53c413bfa2f4bb14ef296afd97ce801a37fe63d11a842f8d66160794c1a651d70f4c836af2c73cb1bc58c706460000000000000000000000000000000003e7b67da1513656f7b08fc5a77682477349ac57e53687c82b6d98772b5f929a2b06b0c7e14481d522aa94fa3a6e1cde00000000000000000000000000000000109e07928216eaea36fbb20a38711e73fdc26e18a6967b54f308b10116a5c8af0c8411406ef6ab1050b61c23bb746b0a3023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e000000000000000000000000000000000166f26d3d26cd48e498578900a8c830ce9b80f162c4b430749651b945d9f60ae6a26306ad7711a1f9d3428946074912d00000000000000000000000000000000165f1bc59c9c36d12754097ea83e9a63fb4ae5d1b93a1b9239a6f338cddf4a9b30415d58076852288c6a467ce9b6b9eb00000000000000000000000000000000198e73619cb93fa6a2bc700cd400519d11a7d3d6d945ffac9754a6faf37da8596b49b7a3a4f2cd899ec9c84f1e79b7ed000000000000000000000000000000000a4740820d60034d37bb85e3e622783852779d36d6e61f81a7eabcd094993dd7d81900277550bb4299d550d2805466aa8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000001714857b0ee07b94ea928ff57aae9fe003c0c85d8564456955d14fc8d4ae14a7c9bc303983af3e2999c6db2d000ea51d0000000000000000000000000000000016512cb60aa372cf5098ad514291d8168ed31bd755861dbd9ef020252c01379d343a9c058839cdec8d14f2fb9da0db80000000000000000000000000000000000af74d8ac711b6590e7041e80ca40dd4db659e42b950bdd68c56d676de654c1a47867bfe6483dfe1971eb7c1d1a70bd10000000000000000000000000000000019e56ca1ef3fffa9e131fc5bc93100577b062cf9b2acd234c79e5e54aa799a389f30002b4bd683edec5fb100f1800d66415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c00000000000000000000000000000000019d1d1e1e2dd4ab86df81a8246c902a573d1fd1598050663342e411a1d1b3c8849473c689afcc8e0ce5e51a9dc9c3b6200000000000000000000000000000000190d7c923bdd6336fe3e0509563b2eb6067354d8807f66e6052e97d5997464b9f07f29f3022f78779a5c4ac155a703ce00000000000000000000000000000000128591bb699c18a7b9e6e4e894654853f6a68233dfe8c744b42e057711b8d0efb3a98bab6aaa40ae7675d9200a8427d600000000000000000000000000000000045e0560e0936b16d1e055d3d3f4e0fb42d129546abddebeb78e871d1442f4796d939929d354b0326b95e50fd5208fa9ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a97400000000000000000000000000000000092373dfd7d4375d6bcffa415e5b36a31499e881a80be32400105a6d56b34d64f4fed09f12640a43289a710f034b71e6000000000000000000000000000000000fa75d6510b3b58a32635a7a6cb4b9255aa7af46905cafc893f29b7866e12565765bcde498dbe87df3d1dd53ab5628320000000000000000000000000000000010dfd3456cb6a8bc853b390380a13f045ab43abd289fd05e7f98839477dea1fb1fbe38ca4f5bdd6691446ac0219e453000000000000000000000000000000000112567397f3fda84db6042817a99aeccd0c46a11fd3ba44e2600deafaaab7014dba98cdcadf81b97272fb7f275ee8a4e039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff30000000000000000000000000000000007b397f093e69874d2bd3592489d93c80d0191b157e71d08a6ebe73063f77e7c5e084a24b34da2aa6354b1815a694185000000000000000000000000000000000fcede3a39dd5f905d072dafdb6f56d85726f6f362f91f079fcd47a8c1d3bdcf199d64edf17e3db1dfc96a3e59f69bfe0000000000000000000000000000000010cfa13c84e750d8af8bbb88bd6d16adf3bc7b532447c2e6accb359a5576be08c1b25f336047fb8e01a4d7f9080d0392000000000000000000000000000000000ca0e88b5c2035bcd3a65e8bf1aa219cf428b6f80617040ae02a0ed41559804844df373ac61a85899bec83e5a6243ed42c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd055500000000000000000000000000000000021f3b793680e0e3127fa53034e9fcf286f5279cd167ac1e8ba051c440aa265ec6d28fcc2f6d3bad126180efd4503fe900000000000000000000000000000000182b429f27996ee070ed27e7015bd70191b814bd02ca6558a9be81d6898161aa525197c1672ae75da92729f2fae9fa3c000000000000000000000000000000000a20b3922e07da4ef6696de85754eabf1f58f7f5d37accb6cde4f62066e789bc64bc8ad6ac827b8c955acc858b03d053000000000000000000000000000000000814faebd3b60fa1a8fb86b3cb57d36b9c85d4b28e97a2251e6bc1fed1ccb18f17664321f38f3723cf8b09a2161c6aeaab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000159f9d329f929a65e41c7a0d4c05e11db61ca7d6d82f8b92a780bac66568694656f4c845a730861fde9a313fa49bdf0e000000000000000000000000000000000d556bdc8dc959b00f74209dff27023c5521d387a40bf20ae2a98f3f55318eddd347bf1e9d856f43a4b5fcd26c3567ad0000000000000000000000000000000009b4b0cedf477ef1e0f99627bdd7a7afeb9e29afbac553a516fab479913b23a9be5e0b38994215a9e23849bb664201ee0000000000000000000000000000000010899f4dc55ac5d1f56a7b8d55ce7f6a5e0a8647bf1ef6e9050f00c5fcac9f679f138018b9aa611be73d3bdc0af2056e297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000000226c8a6b27437972ce29c2ed7e5cca4b6691e3a5dbbe713b5d309ff2f4cbb95e8f1571314444d65ff5fbc3281f9354f000000000000000000000000000000000282a49d0c560d873676967700c1062013a2d4beee96a09af7e14436fda4e3d2a32ab8ee4e591decec39a811ddff130400000000000000000000000000000000167bfe499f1f4609e67134e12ad91aadc37bdabd0055ecf7f96162c39a02a86e62a7b3d39f514f63edd82d04beb1958a00000000000000000000000000000000191673ea5470e4704e361f5ead1c56371d6aee3035d92d9e1b96fd119c4f877cde6451411e441fb45aa9fcb90fe4c66ba87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99,000000000000000000000000000000001488532d83fddf0bfd69b32f965790b3fe4cd9f64e8d17e78189c346518c91e69db2f0b742cdd5804b3db3777dd931230000000000000000000000000000000016205c470c6371d73b012a14d519bf214ff10de458605097da1b798977bd938727c5be19a10f4f492f301d2ab6c38ed000000000000000000000000000000000142cc08f61d3c9bd4c7bfd0b7a0b8693af6120898fcaff49a7fb5abdaf1d15bf70eb033d6ff09a75995547e6856c595f00000000000000000000000000000000164b2807e19135ca3b66bac9aceb371165c930ae063f3cb5a06efb8985a1e0c39023d8f01df517713796083e8c2cceb7 +00000000000000000000000000000000023bec14deefcc20a90e439bc16912e90191dc7142234b1870e4e8d70c56f695d5cd30a68930ff9b007bdcae8ca90d870000000000000000000000000000000000053a6e226f3bd82150e08ec3690f36616d5ab745b36a9990baac7ad3429a41bc60c7f7000ceda4cc9298b10043639e000000000000000000000000000000000b81b331589ac332093928faa60d6819d3b5559d32d37d2cc13c78aafa1cc34e32d317695c1c4b4979baa1865ced90150000000000000000000000000000000010dbac5e52f9a046ab88aa36b3c5f6952720e174bf8f1732e886e66e5803aab63642185aa24ea08c991edaf8375bcadd9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000001875ef3f90df03d49ce6cede2c791b4d8503b75acff2dcb1c7c88026394dfe11481da72de4ff58ee9a98e75577b6398c000000000000000000000000000000000c8ee603d1404e64ea3ff08c70b3dbffd318736ae95f9a96ca07ddaa449818e6c5a17b2970f572f53c90be893e5c323b000000000000000000000000000000000f31af63c68481f527092b261d29d5c2daa95873b68899c28ac7753d95a64f455ebabedfe6e72246e494cc5fa2a9bd040000000000000000000000000000000009fd06bc51d4dc51de9fad6d1eb763809cdb5ccdba8e0427859d878904bdf295983b318f311856728078e7cbbecb0c5b64be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a67690000000000000000000000000000000003ce75ecf6b605ce73f4e215b1aad4799f91e624daf0deae3a273968490bdbdbd0250686ee91a1c24c2e2f2b6024fa49000000000000000000000000000000000e4d9b65d71b7593310fb5145677d170663c0ca29636f7b7c50ec1988bd2d2f1c41d542d4cd8fa23fad94bd6a84aef5b000000000000000000000000000000000fa4accea53a6362651f6c6ad2a68d20b5f549f8eb961718e0c14cd05249a121e442a6a588eafc83d6a43d8baa66882400000000000000000000000000000000121e325406767852620ddc45677495fe3e0851fd3c70922896a3e92033347d2fe8d07f4db8f26b8127ec39d619d596030c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da0000000000000000000000000000000003a0ac3ac37932b71672b9c48bdbd368d64c11f57ccb952f633bcd10ec19134c65fb2cbad655d773a90cbec2d9232b3b0000000000000000000000000000000007553c470bd8f38a48490dadea29df81ad901ecaaf1eab35b1f497bb58acce77b883e03e78702930dda72e2277139a2b00000000000000000000000000000000044973913824b3326b72e62ccbabd8c9f1b5dc81b423d0dca37b6f33972d993a681c326730717036bc6f0286da9177430000000000000000000000000000000017b0407d2864cfb39dbb0a5fa8deb4ed4a690a4042153e829f51c56bd0f2953a440d8305a318e6d6f67970d473753021a2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a000000000000000000000000000000000b7478dda7053590ed013b7c23431a21626e748c3843e2332bde0bd3890ecea95b6104bac420a8be5f3dd9b075203616000000000000000000000000000000000e6dea641181cf796f62b196652f952ee2a26ba998cce1cfe9d65ae49198d10badffa561e2bd818eb2a7f350c122fa820000000000000000000000000000000003c79917ad5a9c7f046b34e5491ed015695aecb00760f3009dde4cfbf88ad1c03e44117fcb6cdbd5ecaa8df8760a3da100000000000000000000000000000000034e22ddbdeb9dea46c71ca2144ffcc8356c1a525c5ada69a6d5e5c1786aaaf0cf532e31a2f78371e04a72e8222ed4c7e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c5890000000000000000000000000000000017964112272360a38d3bddf89da922ab50be076bf71a094fc8afde109d3817cc2db633e6408f5716b76d70e30ae00c0d0000000000000000000000000000000009bed28bbf43846ab97b92aab9ce094b077bbc59db648dbb469f21842058ef20318a1a8c18045b3de555bd8c76132ff0000000000000000000000000000000001297110789c7aecb0fec577f6f4a4de14608d9aa26a8de68289adea7f6b53b766b840d315152ea346f8c10b2d2729e730000000000000000000000000000000002b551c6a7846b96c6895e55ec435397af70eb435dc1c562ac71a44c36936c2c6d3e6a1e3545513516513391aedaf9ca76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000003d463ee4d177d78849fdecba52b7e83ca90d54177ed39e82b4e80c17994a6a2bfd9c46edc0ddb256f8955428f30eca0000000000000000000000000000000011dd976dfeb8ecb7d7f5cd10c235131709fb16d8a827e83d7084266c2504cd1f5276ae3333bc7fbb4ebab48c0d97a9930000000000000000000000000000000005fd19477fffc246f5991603b48085d95256b273631bcfc16f19c6980a3ba01ac098061faa149b475bfce37d586464b800000000000000000000000000000000103ac3dd682aee109dd7fbf60b50c28cf7e37642f05b424773a06f6cfaf7e9fb01d5074ade97ef6cb0ace2e1fe07d54c7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d0000000000000000000000000000000003208ce7f51a96dee053cbaa66fbdb921c2c3b42ead78b39b4f1df7ab49f05cb88d0f4ac18de5839749416eba5535d4b0000000000000000000000000000000001ff7f9db52aaa0fddc8e96a67b99353b92d7032f59d200bf69da3b446d08435d2ddaeb93584d3b68a1934566187922b0000000000000000000000000000000005f05ccfa5704652cecfb42979c538823fb9d11a00222a963d00f1a4b9a040a0222dcf45baad40c6574d85e5617dbbea0000000000000000000000000000000018637b8c3ef111f6ad4538464c250d780e7f081802bdf720f4c925154f4667c5d50cdbc4dbb7d0b2747b97d2ba2280bfd35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f000000000000000000000000000000000eb400becfa5521b824a4288885fe46642c31576238e94f95e9b4bcbf62845ee9d9ee122f87d36fbe668f0e605fa2ce00000000000000000000000000000000003c8cbdeea0d09590e1719ddffa0a116723f0fe85585583f3f271ead66fbc2107873181915cc41eed3ec6e2c5669e9d3000000000000000000000000000000000e61c0768561517405952c6462f1c5df95be272251d8a7060624b62f9be310cef64436eb2c4c04e8352d7b75fea1756200000000000000000000000000000000036cd74a8efa8a1fce7587f07d5c2a6c4b7ef161b0faae037c9bbe63bd0c92b83e514c8c1bae4a5d9866c0889b1b914f3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e8680000000000000000000000000000000013019d0fc8b93da2c79e473d713d94af33eaffda65a7a49d0cbae9f5259b8323e6f29b83da9608ba7d6ec004fb0710eb000000000000000000000000000000001505d30bf8f7c51994d896d91e8e2259782e2b49bda834015477f18c29e64da4d31f8b96edd080267b77a9539afca06a000000000000000000000000000000000eba929531615d9c0f59c4b33c1fc34b81e9c77cd8c6887099d850b3e39326d7caee1feeb101222f22bea1e9853d06ea0000000000000000000000000000000019d88f62cae047ddf2cefe497495f890d9ab8499e56f72488af65095e992427bf821f63555a67b0afb00d6fb441080a010325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c11382000000000000000000000000000000000b7c8f3d0c56b3b7d96c0a24fea3394551a186f87acbbbbce41d1313b23762945bae2e911725da4211614b456b508c0500000000000000000000000000000000125316f64bdd0c5bcd26a0e5bcfc3139045b3a44c8a8dd1cebbfaeb83b963c5a5abd4a5961465cff261c0e49189278d800000000000000000000000000000000095a327f488b901fe7dcc9f9ce6f4f25876bb09b053b64e9f4de9506a0fb95fc0cd443473c2cc5436750581d39b8e51f0000000000000000000000000000000015d406b31c791ae2d25ce462304c0bcf341686d7967c9dbb6734bc28b02123b1730d0a673fa8071dd90950d9411a2b3909545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb3000000000000000000000000000000000c7029af9422246d0a30784431d6bf9eca09481589438fe9a6d2fe1d5e526ec3d176a3d550204aadb85353d99bfe3ce50000000000000000000000000000000014a0dcb26c40693ad19a1edccda05055a27ca24544e933d01dfb964571071f94c94233f81e1ead0925d24e6d3df2c21500000000000000000000000000000000147a55ebd83c746128ba9c7ac57be125ca5c95f80f891e2c5893caa779484bdc1f9c3b3ccc4223b2343ba939251f7fdc00000000000000000000000000000000125622a040d8b157432ad81b8a83a9b1f0920b92680bbb65050b4862b89017b3bfaf81a3402ccb383265ba7200ce677feef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c810000000000000000000000000000000013b394fd7a0f3d94e5fe4cf5cce3627d425ec848912395565b3e61ffe89e56be799c4779d3b9a0222ecc6538ca3346e40000000000000000000000000000000014ac1a87b333caed0f557fa5692d1138a8c1e92d1f9acdc9f357e2a46f27513dea42f367b046d389dc831610be4fbcf40000000000000000000000000000000011fa243a0aa8b0c01c7636387d60021afe6efc223b7deb69d030651c369643188b9dd5e08d6d031d71dd11eca1e825ac0000000000000000000000000000000015bf8fd7fe438407db7f1b0b586b2c285777c5b6dbef9e45b46cc0a50dc831f32a70e7d4316d4869bc769ff6de58ac30c8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e80000000000000000000000000000000005ea57c269c9d43d3f17a83df04c95ea7e7bd85aad1dc2dd285ccdbd52bfe707a1d2476417e848ab119e62fea30520af000000000000000000000000000000000b99768ffbe95e315b244bf996cf34f8ac356664adda5aa7f4ff8d513b2eb5934b8ffe0fd9af94bc9b934e0a8bbd51ba0000000000000000000000000000000003b02c259df189370dd2700c5cccfc8b212a4b332a083adf9771503f5bd0c9ef040590320fe4a86c555a4ea87531268100000000000000000000000000000000003ebb1e610bd055d037a410cce3ae06aa654950aee0210ed0ee79f7a332be7342e308347d7b17a146a8b4c623029e08a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000015c4da691b5e6242af870e06b29bcde467b4644f01080eca60a28c7f941590192be30e6a4270a36dc8959b80235600aa00000000000000000000000000000000080f3d3d5c35ee24179f51ad854a37ac4ff867a2736a0e3e8f3312ac98c7016beea6ffe2bad1dd4842d6ec77995ff97600000000000000000000000000000000130c29dc633aaefc831b0bccb13fde1212fdce8cdd17beaaf1d06e74ef5b1b69bcc219c8d63f054690af1b6dc7c0d647000000000000000000000000000000000767290aaa1ed4c1dfa5603d976df0715b417599445ca577ded7d99e685118bbec71443fe1d9a65e0f23436353df152cdcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a00000000000000000000000000000000133e997857f47f8d6278b8ad86f4692ba0dec9da336f2726704db593af368dda7aefc0b218ce1674f415e0d9e2dee5c60000000000000000000000000000000018db87da1272bd386f7d8b5245dc2de30e82739723b680dedd36f4ac4cf5042bcbada1e1bb307ba444431d73a4248f9c0000000000000000000000000000000006580be3e67c7a615408aaf9c95c0956678af0e2b1f536f1e69588193387f8a05b03d5e1060ca60c4fec9eaf3e72d39900000000000000000000000000000000050bd9879ef9eea147678f552cedacaee84562e6561b3b7338fa8f9d514099291c3f2a3723fdb22c88f1c9243d411ccba6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce,000000000000000000000000000000000d8f69d90c871c08ae09e7b3e62e36514fd056c41fb596fec2fc9ce8509ab4f6675d7e85aa6b4b3197f5ab781f6f2e490000000000000000000000000000000011c4bd3cd156c34065e408efcaa5e13ad23d114458b71c2a6345f4aaf82af76cd4362db7ba9ee7e1e92ce72e242f570a000000000000000000000000000000000712dbbf20e9b24d20511d01717a3783608386408a258c2261fcdad5fbcab36c6bd21473c3d93ef8518975256c65a945000000000000000000000000000000000d13747be82153aea8076fd7813ecd7f60a214c31e88e25b14dee5cdb9336599e40b136d9ae6deb85606d35406b2675d +0000000000000000000000000000000017da08f2faa32570d95b9efd2d2fe358faec1ffe304750dca1dc3a273be3427c70904d58864f76afa19b0fe33ab1535f0000000000000000000000000000000017de677b713202f23baecef2b0618da140af624e56b876f2d7a20cd437c3868ea00ff6cd9c8908c1ef323ad294edd9670000000000000000000000000000000011d50aad957c54868aed6d848b2e67094b129282cc2df56c41d6ffe976d02ee83a592c33370d3715588a074db503b3e8000000000000000000000000000000000b8aeb019d120959b21627c1dcfdfb67ade22a948fe433172994d4a34084ac9e1c11333a9c663c87acf50962e21c728e92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b70000000000000000000000000000000001312ebeee36fff8152324a3ed24c37eee50b3099619a33c7a6316470ae722548b4b9e0f0640453caf53f374dba504830000000000000000000000000000000005ea81d2e5d9edeb3ed6c200b75beb731c31ad666e6e37db72ffd0265378bffc2724047c7c0c6e3f1598345fd390e9270000000000000000000000000000000017617a836beb12e637c5bbadd4fbf1ca2f5cc3280814ff5cbb5890b31cf2d2faee9e3ea8134af97ad4feace50aa194140000000000000000000000000000000002606deb5d57dce5b3d2e5f7ccec3ad036992beae238673641ad6042479ec3cf83bcc0fd03b7dacb9b4bb6c181ea9cc8c193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000001203ef36896bfad2a2841689a964328fe4ce3d83798671630d0c8876e67ceda03d99555aac46d984f1d3bc38ffc134c50000000000000000000000000000000013e7461c256c8ff9144b17f8cc2e270aa94b64be62588280baca2ae6b6efc4d32b3800eb84da62561e0e96d5f0387a3f0000000000000000000000000000000009454b6a810647350cf0b364eb1c2b719670af45bdba9d7d1a534e23d4e810c3ef4d9318532e46fd104a83bb10159a30000000000000000000000000000000001034546c4288f642daeccf5b56beed2ca2d946bb4391d056df9c6fd6771048903fa330ec16d59d05540cd715333c4bc73dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae6000000000000000000000000000000000d7782404dc6721f52648fc6969db33a9aa209f8baf5faa9678437c76c9e1635fa6d22d94aedefc90112223bb81ce33f0000000000000000000000000000000001e442e548d3045d1589817d0b57dfcd66fc64ff978186f784bd576faf57607170d49364a72189328c9837c9a2d8b0a0000000000000000000000000000000000da2b207bb7720aeca2e6ea02b65076770b960d4b7a96ed941a7f409757b952031a472384298acc3948bdc485088501c00000000000000000000000000000000048f85bc05ed78c692138f27c3541ced11b6b0ec158b43d133c3450a905416682fbb8c83dea06a06d294c48289ddb829e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000017134f787c920bc15cf2228a186dfa1d10194087f28b6dd8f03e1c86226928f0eb1c27020a5cc74d94b50c4b4e36b8020000000000000000000000000000000012fa1fdcbaa81c4cc1e37447cae51beb29e55bb19b91e2b575afa3754589ee0151cd9e83573edaaefd341f381d34f4f8000000000000000000000000000000000ecafd00cc87a773a13909512466ed11288c842716e1ca5c37a4d9a4cd7585136c86f32140fdf02e2997a6e19e3d76a200000000000000000000000000000000104cf007ea863dbd473d7dbab6f55e74062b18986e9bc09bcfdc9c23e4bff8683f73aa998a5cce59ded10499d18a0ecc4dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d72000000000000000000000000000000000ffffc98e55f4ba9a642c40678d625690464bea39d085dbc9c99b4c36ea8bff5154eae3c315e1dec29aa669840accf290000000000000000000000000000000000a3df9595167048c52b8170596d4127968194aef7fbaea4594a27c6af05c54bb772928a7749d74311038d1c115e91b2000000000000000000000000000000000b317a3abd808e94a7197e0d3b2515a147774f78d0cd7d36e1156da28a26e33bfa76d75c6e3ae346f9ace050c9911cc6000000000000000000000000000000000fb5fbcc2f74fc30ae7e32143f219db7dfe5db6ecb09cedad8f087b6df56bf9693c8b7d78aace064e7c31785f6869541795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a95680000000000000000000000000000000011e4b907a72f34af899a6c4de211af5fbe0265e5bf24d406798de53ecea273d5df4f4953d13fd7c9dc3bb0f0c143e3e4000000000000000000000000000000001623de5e87b6e1ee920e1b7d979fb9c431c12abb47b93876f9ddfaf28a7b673c18be634f96b813f7e0574c55b628a8790000000000000000000000000000000018ba994b02dad759ee79301b42ea20d7545844c0ea4bff2f95dc9420194cc4196fff12cc09bc0cef03cb7ba868c273700000000000000000000000000000000004b3527c8d148bd9e6006bd298ff8d7fe320748dd3f6d23449e874fc0c2f58d933c1e038a74f60fb6032cce41a3dbf5725b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f083800000000000000000000000000000000129641af11fa92056236ef135843b2189d46d870381261d5781a5fd6f2c5cc1861ebb2e801f19f3adf2216609a9e196f0000000000000000000000000000000007b4007c55e47f6bf3aa420ad75fd191ffe0fe824fd30c3f1961a8168922476fdb3869822704999b044feead470e3b8f00000000000000000000000000000000174209113e2d8c363b04f49487176dc6d9eb4ecc0b22daa7ecaa5548d038b3b7c23ebda4f1b6845425cee13493385302000000000000000000000000000000000a58c80a02b7f93db01d2f8e0005839625e6c4f121f3d69115f435526a7f7cb53177caab4db86273bc2d2f0474235f31df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e000000000000000000000000000000001738a03b46a8ca3f3d1f4f4447497c59f114005400f06813b24ff462ebc6f27c1c3c788b5f83f65958cadb34fddd08f40000000000000000000000000000000004dcfff2bc9ca0282016f38df484655cce7b872b1ff047351ae6b903e05f457d7fefae93104f9dfb549980394dfad2760000000000000000000000000000000017cd89434225dba07be137a73892faf0258b3fb19e6c8cec412fcda912c0613f2a925ad50ae485187020a371ff2dbc59000000000000000000000000000000000f1f9f87d3401e7b3b59331a89d9535adc973f869b81bfd8892a37117d8597ebab2800c966e623469792f4ae2a8eb232959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000000bdcb1d2a782541ff7884dde4167ba060fbd4b117944ae69aa2ff685b9bd7d475f45adce0c9f92695b4f4ecdd48cb9b50000000000000000000000000000000012a55432678043888bb9e7e47efb17700b3e702e389d0f58dd454224a02da3f190b2fef4c9d3e2074c7bef813fb56fb0000000000000000000000000000000000efa51ba64f1e7a1a269dc083179a222afac916778a967098582f55a41394bff3747f8d024261959f6d399f44a40d0fe000000000000000000000000000000000845dd0974c5789a85c3cb09ea441f2c433f0606928ee1b177eb851530d6e6b620b4fdcaffb8f75623435dff99b3ad9526ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d700000000000000000000000000000000180beba92bdb95c7803fca0407e29929ee64e03d61cad96ea0e6c469c5a888cc5ca5eb20983b3418a8da6596a5f1b2ba000000000000000000000000000000001322f7356eb3069fe20063f4be22c44426162dc8fc117e4e382bc4e33bdf3d971ef662fffc1d58ce187c33a43a4c853e000000000000000000000000000000001601a0aadaba846f11ba5c9f48e13bda1007ffdc1b8bbc9e85e83e569e9ee17a1e9e780a50ce617e6c780b8155675f2100000000000000000000000000000000105b2c213aa43ead42d9cfdf1d6c0559c25b4b86af43d4493bd75b76986d0d4f1d9b3bf9e3922b5c08a37a1629cab7d8f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c20000000000000000000000000000000017bc70346765b7160a0a5e556805c7944304acbecde06cadba474c51f05f22445c3d943674cc8215f973cdf11b9ea2e9000000000000000000000000000000000bfdbe202619a1d95359941c249b25462d3ecf09fabb878943a8a37cb9eb94abd7e6399f8d82f90ffcf904f4466cc5b1000000000000000000000000000000000f048db8530a288fef10a5ef9bb3cdd9f3d3b0ef4824609efad96bdf52d7c3b10ef628fa04f8b6513485e55f653f4b990000000000000000000000000000000004ec35f59287eadb1738bb50b0e2ad9d280bedfdb0a201e72594bfc4322ade0b7ffd6b532ebc7796cfc71f88a194bef4b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb30000000000000000000000000000000014424c77af7ace8ebf66f556cf219919712d96d24438466ad620221ce1ae9b2cd75b9c526e25df7fbf3c9250583757f500000000000000000000000000000000198aa00723781714152b3494b76ea3ee043b363b3fa81806cdf7e440b4cea907f226a3c038fb95c932710dc9aad4c9dd000000000000000000000000000000001360e4c775f6fa5e987231dce25ec67f61429ca9fd8160c3074383c30a8c0d7ff068b1d1215b2c0cc87129d9c9aecbc9000000000000000000000000000000001280ee6160800c4b0f82d5c2775238b4b223d8a0ac9a8f8013f138d554ba31c9fedb30e0eb5c330da17f5785b2717422ad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c1000000000000000000000000000000000f1f84251204d9f9328f79a45d15b311984df0715579633a82b5a9f680f6645cbe748b0fa64b9ce1e696e20a5645d6d300000000000000000000000000000000156901506e502a09917f76d825614824dfbc34d019ed53c2ec5395b51512da512b27541bc53331444eac2f618ffd5357000000000000000000000000000000000ea8736a97a33112bea9d07b729e973e3a942422f1d2b24c30e96637b535ccfc10cb5930bb59ed90bef604453df8772100000000000000000000000000000000187378477f60e3eaa225e89d8532bd95babd4a5c51729cca800d364b61575704992639dc5035138664e8e074ed0820033c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e5281c1c9210269a7f5ccd02cd5a7d3648b56d9ca6a4ee50beadf151c2601e0291fe7f1b89b694500e6c636d4e445c4000000000000000000000000000000000d5d5399f49697e46013558dfff544383b25f3b60681ba5fa2c5e6edfd3924267d0992abe65cbd5109ba8a1c6eadc7e30000000000000000000000000000000012a2104aa92871dd8e41ae1ae6dc18ceb7d0f361a5a4fc67936454b8866b8aec1602dd596459cccf6d9e1319ec3299d4000000000000000000000000000000000268795f6f9892f5b476c3a534673538647300203a51a8ff60b530094608b5fdf16297f02ab7ba41d6fe556885f064a4ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff7000000000000000000000000000000000a5585961328c52e0fefff16e66e3367e34339dac1a20cbc5e89b78804b8bc265e6e3fec1da6a62cd8a46be2f08a6d960000000000000000000000000000000016fbbd698784beec5a636332c0b20fdcb68fd3015cc6d18b541346a5e6af76613e6fcb14c888a2b8133c0f4132fc079300000000000000000000000000000000041805e0adf2a32153b89d1131226cf0ebd77cde3116a168e792ae8b88ba2edcb1fe7275658a384251b805d282ee039c00000000000000000000000000000000024213e4a8504cbae4875617b9b78473e7842ff72415ceacfaaf2e8b415f9f7e411989bada8101be72f9295dfbddfa3f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af,0000000000000000000000000000000003c4f051d528166f256d9356aa9cb885db5680c51990d9474a948848888fb82a9b86daa7a2273725ac8ec564ebbf15db00000000000000000000000000000000010a6c4c7067f511ca8f1b66bf9ffcbb275c7575540909262f7c4332c3d75b2f6d2f3ad2848c0d455410afb1cd60c835000000000000000000000000000000000ee5e582554b3930c5670d4e3542bf32e8b871849d7859eafc077bb2b533e936d462f614057f9fc09c4010afab501c1f0000000000000000000000000000000017fdbcaa065d301adb94a60dd20dbae71512d369fc82c556ea0dff66843be768be942e060752591c6eb0718985d8e313 +000000000000000000000000000000001353960aff58d45691c5378a0676a8e837260f5819cbbac9cd75c8cc4c6f1e17b9dc9843eabc0b1dfb27ff7631e4e52d000000000000000000000000000000000d6279a43d3526c035e88b0b640b04d42ea573ed07323aaac1d9d5570a8be64782682892415ba2be5cbb13f56e3a44db000000000000000000000000000000001250fd14fd003f88eb6e0e80e9f2ebe204475fc6c06cd10fa45608a17b7039afe0326474ff80c357f86c2825cdf7a16d00000000000000000000000000000000186cd91cfc8ae625e302946f2b393ea67e1107c0bfe938f5f36d28879fa0c0780c847aac77d0310d43211152c1d5f5d314d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d98000000000000000000000000000000000e55a98e8b1e59600e86cabb5e8db8ee622009b1618ff0df3e93fb55b80985bf2a8ed060aeaba53773274d4186934f75000000000000000000000000000000000bb7215fc43f465f51fc8265477fc8c79493966f040e02f0eacc4ebcb3414b84fd94ded822bd24dd5ad5720f12bb8313000000000000000000000000000000000b23328e15cda8a576ea352b5dd7ce382ec781deca6c23f646e42f0cf63e28669539579ea51e3c0afebbb58e1e8e3243000000000000000000000000000000001716019236169bdb4af7bf7d7ce0aeee7900b74023acbb16f6965c2abcf28917bf88d0f9d5bc26a81710496f7821fd4682cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa8626000000000000000000000000000000001718a4d6f5e78524d8df23d2c589abf04e3567d2176539a30b9f73c6251de573caa60c2881f3da99c48d48e9aacd7402000000000000000000000000000000000ce0e35721379077e6eb3b572f7f7718bbf775b116521c14acbd3ff19549c75d50bf70ce84326cbc3f9e5e53605d8ecd0000000000000000000000000000000007cb3305ef0d2cd7de4dceaf25d2eff44d4f437e065f6b244bf1b0611c891626eafc4b759d55b45d76e94b85852df1de0000000000000000000000000000000011cb56d2ed32a46bd951836f8e0f92d3824a4cddf011eecf1e2d92d81bff407a04abdfcffd60ccecda6e9443b328d51eea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f982490000000000000000000000000000000011ba705da23100f853882dd166d81ee1d7621550d156b14f7c2123e2681887ec3724626061db68b2c63987325b27d6230000000000000000000000000000000014271414fe078a80587269398afd127ce34c8dc2a4851f76613b81dc99d766d75c703949c1093b04d66a301a79d89bc30000000000000000000000000000000011b7935ff284b0f812b5da5b28ed338dc4c21ebbc7fee04db834732b11fd76092db0e8d80368255b0f1205129081e9af00000000000000000000000000000000104ff0ad2e3db08d3b4890b2e54f29e456e627cefc3a4f07c1109b764dae4142480e3e5312ada43fec9ba96ce587e8a4e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d000000000000000000000000000000000c60749ef36d63960022f3127d0ab4e12acf05ba1e1a136dec89be388b9d7144c1d78c04df658727763dbaa9725bd8b90000000000000000000000000000000019932b1c205a765bc9de0cc136999deb153222a9dd9e9ec3660fb6daef56242d08791d440888e69ca0da2bbe0fcb7d79000000000000000000000000000000001764790d12f5ff79ee4f2c9fadd5dfb1cf47db70b9e86018bbdbffd1be18df193c7dfa71533afa381053a77e02719c6400000000000000000000000000000000044b2b0211cbb407281ab2abc4725c2cd791b313bab8779954a2461ce445cdae60d4a9efad9f90f80e66b1438514e0f0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc00000000000000000000000000000000175cf9e7eead650e7ae4fd657bc288b6b6392773bf1bbea48e17172a5019637fbb2bc0a3d0d1e3b8054564935c908db200000000000000000000000000000000136da2a625cf72403d0861b9cd947cdad12b1f1e6cdefc4aab6756536425285a7953a1b892df40ec12ac3430fec889cd000000000000000000000000000000000c2d10c6d71cff4e1deba1984bfd17166571e64659ac91b64c343cdf587c29d52a2266c00a57c01feddb1df6439d21d1000000000000000000000000000000000384a782fb31278f49c840bb8f0552ac2734ef36bb3d115be7df20333aa747c92db990f7e879399235d122fdba0eed76eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000a9a821cc63e7c9857b0f39f7444a1e00a422f7cd5d0575c26bc5c6b98313abfde51e3f6d5f4c817193bdf391344e5ba0000000000000000000000000000000010daa8c7194a75cea757b6ae4eee85006eda459ff2cf155b1b5f19c3ad341972f72e28b781c4878e8919c7e5abe9a1d5000000000000000000000000000000001154d5d5764aa2b8818a9dc5dce30ba2197a86d0bdc7dee3e600462e295cc3a69dfbf8db34acf138e7a1f16b62a45717000000000000000000000000000000000b4243a09b05a958d78ba8ae25fd3fa85d520b95e56f1dff44e556b221a075f8dd3370313886d9dbfc56a75697454d72acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a9000000000000000000000000000000001820f953fd22b71ce00bbe9e9b78fcf5fb28bcb925f6b5dbf5711e00470ed7fd2f38d7291d40514ab4258807f29150270000000000000000000000000000000007b737b56a2ba33f76bcf66c0b26fb44d5f79879273f6ab21ecbfe6a5744da289464ca2b46c55edaadfe3210b907f3f7000000000000000000000000000000001735d1b39c5369bbf886c5063a96dd12b85e56fd9d8ff9d84520918e1dfeccb62bbbe1c2ab440ccecd0fe66f6ec55853000000000000000000000000000000000e591b7709bf00bb2a87e9edb95720de19adc41a42378cf9ebb930c6d3f5993a1d7b6320040d5c69908685d978be8f980cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d2000000000000000000000000000000001732ac410b2a7d10110bbf7709dc6fdc91ce742f8cb9b2c3ba37ba5f0934f8622c675753a26d04a176e24a630d090d81000000000000000000000000000000001111a52da6aca10cf40127fa8ab7683505305e0d474eed28a5e1735ee6877aa00c1bd598420876f2154b814660f3fe7600000000000000000000000000000000098c6d19c2ff42c2c57a4924693325de1a91135e3474ec699b70439d034469e72e844a5511e23dff3948a66cc2a2165300000000000000000000000000000000175fb79e5e54963cdbb133f38dccea2d1abc3cdf005c17e8f2de6dba9b9dbdeff7719983aa9ddb602f0cf966fdd430e0b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a05440000000000000000000000000000000004e88468d35d72dba6b3e4b9ca216b75b5d20c447064a48bee6a6ddf994b1e22fd6ee8abd60c627622daffcda219645a0000000000000000000000000000000015eb2ae16e3310b4c4ff557f0615519c13f29109d9863418fdfbe6309b5bac4463456df8ebb0b6d9022e294cc16265ea000000000000000000000000000000001288ffe0ffdb96708558d914bc412758770d048c4d50523e2b134f8468d11a57da97e42bea303ab7137e2d26c0b3b8f30000000000000000000000000000000003ce563b63c50b09a80b71a1a82995238a9de31aaf189c6d29307924b6f0990854507b7dc1644f689c5abcf931dd5a3c825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a30000000000000000000000000000000009e39ce653485caf699ae1d1d9cf2b8c5ea85b80ea042279e57f0beb81056159e49f73d67e7b1f9ece9f9ece7dcd2cf50000000000000000000000000000000008d6492cc335660c54e4a34b29b337b5800f1ef992d124524c799c04c852ccd3cfc01bf39515cb8b96151753147e8c49000000000000000000000000000000000ca779d87aaa3a6552f9f1a10b0d2e635be90022326db04e6072f326b919ee55d4124b9268f55751dc0f18172bd327ae00000000000000000000000000000000112eea543d6609d0acfaeb7be98be609f03304f50c3814ee8a010283146e6b5dbf170c7314598cac06efb9ced1ac2930307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd0000000000000000000000000000000006624dd7f6eb043da41a36a15752f370eeb3cb2e6bd88b337b370fe0660c5ba8fe64f62e112f91d2524e9324f3a049fb000000000000000000000000000000000415b964484c9246385cf95461ab955ed0390e20209ed405d84fa8c8af9fa7ab39ce89049691a63c61b12bbf6aa2a4e80000000000000000000000000000000014411d7b2db7c9ee78ea14c6a315df3d90827b511db2e2423d660176384d8f8afd284879b22f5aeed73afb2eca4be52200000000000000000000000000000000105bfb471340e76f28901edbdbfe2ba246a8824b501ae2d4a73cffd2690181347c1e6530804614e88e2bb13a8edef8f4bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d0000000000000000000000000000000002b17f4b0b0231be229d87f075998435560ce9046a8b0e8f15e3a9f07cd52f3316f6d8c00d6a872362e7066715cf990e0000000000000000000000000000000003110eb232154f8a06834e2ddd33c0207ea552f439a6127b652bc261158209a00654e50341d333cd1b206a915fe0691d0000000000000000000000000000000007940e209c8934c185e4392f12fc0afe3d234dd1ef3f92df18d76be8fc42bdcdd6d1ea8d5bb6f07b3f3caecbeb5ef27f00000000000000000000000000000000012ec903a8442f68c03300ab02ddd08ec935d97bec9050d26a5e276584592df3ab87d596f90768d2c0918099b28963be58aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000010fdc16bff0fea02b325c672fe06297e0669094e2710d0baf3838f3e234c3f776bb3fd41b967c9ebbc72a6bc6eca70850000000000000000000000000000000009d64ce322e39d5b2d0872760a61a831877c450b1cfac6cacec52d4070b0f179dce90afbdefdaa8466f6a6e2e83ee8da000000000000000000000000000000000cddca46f3b24e05b76e61b4584bc716ca7036afdd914731a61347e453a26d07549e9808e553ee056bd47e53c75eac8f000000000000000000000000000000000451cccaebe1a188d3eaadd40090ca594f071c8b6d0e0d82f5b2d43fa784f8437e4226104c4cfdb24ece1ed75375aa616810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff0000000000000000000000000000000005832ad778dca8dfcfbe741dcf311024d76341d5920b6830cb75893a112c9d86719583d1dfa7287281fb73fe21650c3500000000000000000000000000000000044feb86b4816e45ffb98e9a670fcb039fd9d8844a2c7ff9b7752f20e619195fe6ab1148f30afa393936d3605fa4c8da0000000000000000000000000000000018db9365370a8c703364ba6d9c48b3512da46cc603a43c3fb91c0a8ee59777d7cf9ac646c3e4274bd950d7de92ebce840000000000000000000000000000000017bd82310e251701cafbf8c4dc5b9e6c88085b0df287b6dde7887e1f64f2d9487a25b31abe07aec7d99a75baa5983195c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d470000000000000000000000000000000002c1df194f01dcb503dcc8a283f059b82d141274c8f37cdb6441aa33f84f16dd288d566752a93ca23d26ef5834c0658c000000000000000000000000000000001700fa4459dd4e609453284f4f7dab479342675a87c1cb42b601908296557f39256f1597ed3b9ec38ad0a40a2c728f0d00000000000000000000000000000000135ed4f475eb99397cf204f971215a0303316a3ed8b62b303b4bf756ff753410b7fe263c4e97fd4c4b399c319ff3ad98000000000000000000000000000000000a487e179bf1b73627af9d7d2b43bc0e43127a8fbfeaea7ce958ddd53ecb27741eda187745e3917f1cbb60adf0286f5413a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc,000000000000000000000000000000001327c57e16f03fbf652bbacd16cf574113860eb87b8f2f6e498dc5dcc4f2fa63859d922d88ccd6683d503d0962db5336000000000000000000000000000000000cb06948c539cbf686f6936b6a1ebef2e148d98c531da36272e0334afca5c2b16a52da542a0fdbc3bf764eb877f5778a0000000000000000000000000000000003acddfb5bc4fd5579d3f592977365840be4d3cff96434e5ff4f01ea798e4401930a1f5d91f8de3ff98504dce398c2ef000000000000000000000000000000000a5a332805f704613eb085d6639f99667d0d9247cae34eabcfa399eed551f24c5d5cb05d6458530ae270b1be682e71f4 +000000000000000000000000000000000b1b06a76e5bdcb6c1c2f1952b49e1820a9d8557229fbff8740269a0b819b91cfd0de20db0afd76a2cb0fbc5fac12ec5000000000000000000000000000000000347654df2084082efd32cba2b270f66b0ed30fa8713b27949fc9d270ee8eaa9b9a7896d7a52dfd8faa3e0cd112a24e0000000000000000000000000000000000bf5b7a2c0c8bc223ab334bd1df5d9fd4bc0c635379ed2b32da13f6178e07217bb88a4bc2eae0b975f2e566f657d23aa0000000000000000000000000000000017042f8585a07304995853270b1b03bb08484104f7498a12bc865f2a0e37e662fc4b0331b94ee5690efe74056567000bdedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d2000000000000000000000000000000000fcbc73d0628537eae417f8efc67af0a4c9c375d82406086bdff669911fe1307576333c389f189f49677cbbfe2ee98730000000000000000000000000000000019d552b85b1445660ca49518d202afdc67b0eb5be02c8d3482dc1b12e5d40a4ff95a49ce47809e4d6644d04aeb67b3c2000000000000000000000000000000000ed536c0f19f592180291bbce59a72ce5e516199dcbd4fbba736cae2edbe3cfb860ead0325dcc8f8d9be1ac126dc6cda000000000000000000000000000000000f5d4f0c0ae3e76b1c41edbbebcf1ff17c7cefd41e7ef8f75dfc10170834d05820149d5f721a8c6460cd0181571fca97db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d6207f6684f8d2f083c963551bbf0a674ba40e691a34ebe6164ff80ba9bab2cc23024a896d7b906fb74c95016a9adfb00000000000000000000000000000000145855e7d610b50cde39db8995b127145d68fc9bea3f075f65b7793acbb14bbb313a1a39bd96fbea6641baae02612b000000000000000000000000000000000005b533ee83cf72f0e4d9c9ddcc6b91f4364e50a106becf766987c490d559d0f733839ecc706bbc9c2c75b243814068a3000000000000000000000000000000000cd8fba13b9ba7557c7577da183bf50810fb14eec7380e3b3d4f2fed62bb36f2b5ff288736bed0578fb6f47fb6d22ac86e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca4000000000000000000000000000000000fd6a466f2eb12f6337ae9f9b847ac1481820013142af1a474229c5f5f5e1c0bb2d9678c19c7a3a1aa22cfc7b5052e0e0000000000000000000000000000000002a0340f5a0caf5c66719f7d546972bb4b89147989280542787d281901ff036b7c69d41418c21c43127c0158593aa5cb000000000000000000000000000000000deeee37ef96f26a4907e1a8a8f3f030dc09102799bd0c6dbeb1d208a0c86a423d0da6313e0be03c026da5614a6a576b0000000000000000000000000000000007220475449add59b3cc6570701528dcbdedacb9a3d39674ad4aef4d94114f24d2bff32f40b25af97ba883905ea6838a27a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000009d7023ebb73df81455f74cb2708c14ccecacd49521a0cf67ecb6edc8756e286ede59eed54d89eee5f77f178ea8fdee900000000000000000000000000000000002ad48fc3192634e7b01604678473e286afb0efe67a4377bb885d38b59ea00202241fb28c93232ce7c9a3dabb136a53000000000000000000000000000000001934664f2bfffb254f0415d6769f4e2ac710ee88cd822bf5da5df3a2541f887e4155dbb7e8056efb2a0370d6f9173e3b0000000000000000000000000000000019df518e1ebafe95adf683279729a3298fc8d7eb39c9a3dfe4b6665153f970e243e50dfb16fb87b3be54192f69766659446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000001870048d360f397877321904563d35bfd0817ce464e0078e9605a4744e2723f49f9cb21dd3d6f37f1f9aff5a6a99bc530000000000000000000000000000000000e29dd0da13ac451d013d4a38408827cb0e739772e1f250d31e4192ddc13d651ab576ed6b8f4ee44e928fa663244999000000000000000000000000000000001646183099579322e0115ab0b3bd6c814e216ae6b2b80206354925565b7bcd97bc12668b7f3530a95409456ac99bf01200000000000000000000000000000000092f6f594ad0d92c9c64f78c819c44320e6bb5dc1dc8fbe58acc7ce3c101e49a74ae6d50b1a668a3b7436dc445e3da345f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f0000000000000000000000000000000000428ff447de18dcc11b2c5c679bc2efd125464f589013c6964ea6cab33d9b7cbcce3a5d6177bf43114ee256f23fefa10000000000000000000000000000000000d1ded695e88dae6dfa702375959831f4bda688fc0faa289dcfb90a07f3a7963f2c9070958561909a2051a852cc15e1000000000000000000000000000000000c39bf1d11fc5693167890246c81133faee93a8639f459429757965e0b62e372153ce53c61f2c539247dbe7747b27d1c000000000000000000000000000000000e84ecb6dd9cbd4133c22350f07a976ae13dcbe4c6ae09ccb023f2118fa2dec68c20ba2266f9b571bbe30dde97480e0a94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000015e040fc8753f06ed1112cc06e2cb7142a4fc984834f01faae718c17cde782d5953547857ca9aeee1c4a7d91df060d330000000000000000000000000000000006789ac15d719a7159b650b757f7d3cf58fca02d3b8f3685478ad5e5b1dca0508dea7a8203ece97c7c6d32b2f194458d000000000000000000000000000000001824d75634043cac3fd17ff0bb141daf7010f70b5941d8f75f1ae076713afaa7e0a0a25fc71038baf1b1255d64c914c6000000000000000000000000000000000a2f71bf85af6392a8a070596e30225bec9e3dc12c70e8df7c545bd6bbcee56799db2c9a8d2504c4f90ecf6a5e18abc9b677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000008bd859ff1f22d682f86e1a0e3bdf3a332ae78d64814720687a3de44c9bdd7506d2696b4daf81a94d33f64983967fdc2000000000000000000000000000000000d7b4b958e0087f8edf18a4370ff98700764c126808d5c52afd3e71ee326c766c1e5712dfa351cf5b3c518e52133ce780000000000000000000000000000000013a145331bdd9c93e63edbabb9f6c541a7c4dccb1705f07eb353a0407074a76022a8e5f5f2535b41ecf6474649e257bf000000000000000000000000000000000a12e461b7439bff0dddb560dba21ec53ce88f71fd3dc10723f3d8742ed63a1ab725f7e9619ca1ccb729564dfbdb1be7f5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000001430a8184c5055008a06ea22ca9c997d1a24ddce7e374937c32ed1e487c80537b238a589b5e50b86fa194666bd3410e80000000000000000000000000000000005c78c94f457bdda242deab79524bd2beac82bb1cb427dcb2872b56d1f46d11fc9d69ba132004958fabc5da7d6d103fc000000000000000000000000000000000e985e8ca038b5dadc9fcaf22699e75cad9d2effa47fe7d4c579ee056b1e34ccc540372111a665041062fc6c39e05d170000000000000000000000000000000018c865243534fbde740de0ffbdeab0d38ee878c20f5d84c0226d1f2b14ed3359f5b5b909808b6b3789bfcab3be75c4cdfa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000c5b10541ec34dc0a8b8e42d9d6fd6f4f71e1fe56b5afa323f4ade35c0170b5e224a66771326d9edbddf2bd38c6c68ce0000000000000000000000000000000019cf33c19936f7489a1bbc095d0f5c6ddc1f43bccf7e8d1b30fb8e8cd1ef747b483b9a8e9faf21cba7cb17fbee887ad70000000000000000000000000000000010e83916faa7bc9de9feb8a7f34ac6f2aced06a771b662cbce846107245edb9c07632782300e838957788a8d88c8253c00000000000000000000000000000000066127bed5ac9f2871500fdd68a03ade57c35449d4b4186b9fac7c89e91b4ebf2f2a02e94d0b578aaf60b32017f147a493d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000000cb5a734a28b44f04d39ffae049fe8b63b138411661ca6dba00c72cadd47b50ad4b71e858e817561682d6ca378ebbe870000000000000000000000000000000000baf4d689baa09aaf763ae7e142b801223c8ff58f2b541ee4c44ab2460fb8f6dfc1e9f61a8d73aeb92d7d08c281cf410000000000000000000000000000000008a0c736f19bd0005c9d25f88565b1355e53fa3403021577de536712ec986567184f4dd626127ee80dd03cdf9044b2ba00000000000000000000000000000000063ffb7a3b4e057a9ffe233296c11fb462136fc4b187be6f9e36f9e6d335a3d673ef8b9ae6f60c146a075a1789f389cf3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d06000000000000000000000000000000000c33d89595d039722222b9b9ee7ff1a0dae896a8de97f202d3aca00bd81d0169f14676efc4b051bbd339dce862d8b60b000000000000000000000000000000001109a24dc6f70bea47e040b24df395bf561cf5f1ee79e90c9b0480fff0795677483a85e6f2e9ded4f36ca849ff39d6f60000000000000000000000000000000009c7878f3a4e4e3149b72149a7da91bf527c4d7c94b15ba80b02e0e50b02a2c482ecae9f458a881c87e669986514f6d70000000000000000000000000000000004284448e42187c128578b801f76d421fc508cfee9360a7203a91d6f9cc7ccb6ed3211fc5df9e15f14aea98bc298b2f95143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000271ec1a3f8e3364ba8e101b49c0bb17e2b7c7f27a4aa4d4db5c07203195050f30c1a05d33c524a84b1a2f0ce31a587200000000000000000000000000000000082ce9d1da5d7f192c537b2bd617b36b65f88b308fe1ff85e47c64b62dc62324458493d1cd1da9f5fe308d27545fb6510000000000000000000000000000000000b30356b59eb04258096d0c3f357fb04471583cfe6a060de5279bf2cff4413678c1716ba87d0b6de6b6e79a96ec26030000000000000000000000000000000003c02470a14211fef14d754f6f71efb33a06a76e099093a5b9512f907ff819e1e0e15f14995febe48852007bb5c380bd0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000172df3290c3c5044d590eea59980d02e02d4fc6fe7948168492362de8f0a85df0c3d09d8cd8b206cc4d1608311ef4c130000000000000000000000000000000010e4d14065315a0d9e48204e47955ee9652b08318251a7836f32e6fc015d4856444172de44b3b88efa1b54dad346e9b1000000000000000000000000000000001549b9c85cb2fc2c7495d7ef6aa1452e58937baf58717037069e6bc6d72ced3a163f800991cd26510e71aa64c44f66170000000000000000000000000000000007814c2f1734fcc8cbf9fcba06b936c86d0452a2370f8c9480b97105e42f9babfe0869cecda7e15500e9d8d868290201b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b0000000000000000000000000000000014f849e9749a5ff6b7b10daac7f5934be5f783d49c8593367c4243664e01b1d3552e878802d7dfee823e0122e9fd46f90000000000000000000000000000000000d0b32d7904dbf08269ca3c6ae3fe582501f55e32337ae361fe4a58dada560db54205e56a399aed33bce8758a05ebcb000000000000000000000000000000000cb21440baba44c3cc6943c8cfa2fe544a652f06423d3de06c2ff734ebbb544da07ba8982b3009b6c4857b73ceca570100000000000000000000000000000000174ef591975fdaa0e3cb05bbb4140abcb38f685ce4de77c95e2cec1911985557b77d9229940b8c9157ccf9fb553e8e0d98df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af,0000000000000000000000000000000006da1222c7ae02843ff289931fcfcb315f621972f45e4fb4160b8bf48cd8102d50fb53d2c699afd36892d91f5e608784000000000000000000000000000000000523048c5de2d0139965c976d8c3328666e99c249104712719e992334442245e955cd6b14a1e3d666220617d78edcc630000000000000000000000000000000009f669d4e7d89fa8d999d8d5a6323da9445583344276bd6a29494a91174aeeb29132926a893d5a0eeee9c3048ebc0dd200000000000000000000000000000000099ee1c33d6f09a8d063393d2a8debeaba93027e31f7b23c5170b6747f56bd6e6494de966dc280dd67a38d39ae35a336 +000000000000000000000000000000000b46cd281a09b85d977e88cb2251cc61cf13181522cf1c344b114783c4fa22d08f2d008faea3dfee8ea7c56aa35ee39a0000000000000000000000000000000012b530bd34f5b31161355436c7dc444d41d634b0ac3292c0096558618005fe82d189e4b208a48240dfdb4d50ad44dc620000000000000000000000000000000014d334e7835d4bcee2698ca4f643f9692f28d8090ebb8ed8f67c60639c64eb116067e5e8a6d04a0c88c765c608d57ef1000000000000000000000000000000000578cb4e243b4b20c99afefcdc0e0a9e308ab4bec6d655a8c4981a711515c807067472a6fca30060d776213b8ed98d74e49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c8900000000000000000000000000000000046ad1212696acdbb686543d284d7cf2e1e8e8c10af76c6ba51d55f060c006dbab25d3a789c71c428f5bdde9aafbf6d5000000000000000000000000000000000a6a880d52fed6a45bdc61d9ee78d8fe472e76ccbe155bddd0e2a967f4d116bb9f2dd4c62cc6f7224b835c8060213ecd000000000000000000000000000000000786544589eda15edc433edcbaa46d4953da72473f04169ea64dc114b99f0a58181d41dce1fcaf7f3109f66aef02e53900000000000000000000000000000000030759c3bdeafc94fc8fc0b03ddcd96869459bf54ace74582aa06c179323ef076aef89c09ce8e7bf9109ab2e8c8fb0be79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f3953000000000000000000000000000000000e6addfe0db96a7377fcab1fb92183fd7d7f13ec003fdfe0740bcc8cf03d8cc602d5d808b4bc874f34944a65b249997a0000000000000000000000000000000014a4337107e716113d8ba0fc7f75e85edd1c132e2b3dadb3f9cdec1440f261513646525314b5c0de6fd372472aafe877000000000000000000000000000000000d472ee0484ed831f8ddf7ad86faef5443df8b943c6fd4c3f94c8d52d9eed6fbb53107170a60f25be52219ca4816788f00000000000000000000000000000000035d06ffc452c65a31f80c3f8a0c1e2d15e32d993ec06c50499bc0fb8f669acd3d2182ba23d942489ea922baf61dd49cd2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b000000000000000000000000000000000ef1f5f6b3041939557368d613279043d1aceaf5fee3ed90b3b756ad409d700fb41e62b3758c8c2d325db7a37f339c610000000000000000000000000000000004d66040a8e055399bacb6a1e762b698afbfabf789caeb957fb7a3dccb01d7dff5414e90f5a14961c4e980b298f834ec0000000000000000000000000000000006efe9e66078000c26d375e87ffaca643aae9cd3f8337f5718e0e268b74f4b7838f7661dc0ce60f557e162a21ff467160000000000000000000000000000000014ab782a3b2c06af7e9c2f28f1604cbfa8a676a874853bf38195780751d306936cefd1cc38c2192cb756e28793d2abb3e9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d300000000000000000000000000000000057270788a199a894b37a526a26bc4d293780d365a6b66247e7417884d543dd752ef7c89f2f4b38f4b51e6f9d86b45ad0000000000000000000000000000000000b59fedd6798487ec09d226a7406b27f04f7983075b4659ca6a78c6bb8aa83828fafdc6488518e2cba6fa4193de938c000000000000000000000000000000001105c18d92b4192833302814ee9b176831e57fb64b703ab3c2d3f440ab302c8fdf7ddc81933d3b1adaad16038dd6dc1f00000000000000000000000000000000020509b08e6ed980df29da649051c7095edcd4eed4ce95cd797da430cd09062a110bae21b6f73daff2053fc0289041fac818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000007be348ccf6a76827d3b9b33e7a89378c133c9b226e47dcb205ee061423ee6e1b838bc262a7befae7c15aa385ced00bf000000000000000000000000000000000689787c19192ad55b9c6c260a5ec3aa203ef71f0b746eebf10f82526c4fadaa8570936d7049c1a46e7f3cdc455a63a6000000000000000000000000000000000306965b09678d481aa4c754d56a0bb4565f16f7523cd0b404fbd39dfc3b6ed483f5239fa30f13aa3e87918ca039d5ee0000000000000000000000000000000000a2586143f9610a96eb0ef86593988770db5ed49663eab72f8c368b9388bdfbcd02fc6bee09f4fe055813d140ca0fa89ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c49645000000000000000000000000000000000018f46dfdde786a88e582ff6addbecb4f58e12c2625e3d6440f2e5b5781beaa95cad6f63b7d132e84700e7bd344fe3200000000000000000000000000000000185a4fc339a95a50551d53c18bb0dc3b74e9c164729c2b0d919392f7aad2be3ebae3b8f676ab81ea05233b3039918ab50000000000000000000000000000000015395b020a9d0bb336066c1347dd91c557b6ae7b8817cd8a2cba9e5bb149ca3401d661227c26d52a9be234faea894c8a00000000000000000000000000000000103d9d7e33a0767554e13b57dc756981488a3c7dfcc026ea84b35b0af21193e301226cb5a4760962707d19a95841be9296acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000018359aad8af59cdda484232b885d1b14956ec04b5584684b13a64d97b8310c283e5d66637dd75de405f5f4bc65a6879a000000000000000000000000000000000849fd55e4f3d4dfc643dfede6356826eef21290b84f7e8e226deabbc84273d95f7be5479e9656dc907ec367a7ebf8f60000000000000000000000000000000006ee01b54eb7834b4de53f821ad46f467cadffce6df09751b728d0952bfe615253d7ad173892a52c6181810a815bd90600000000000000000000000000000000161472d45b56dd9fd276fc607f2eef84c5c843ea05799e732d7eb6dce96c632335949e1b3a06815e410e919f4cdc3fb360bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf00000000000000000000000000000000107ef91cf3a3068c4e5644676f7bc7c5f9ecc361524bf3fe2ebfc606f22f8f83b38c0d4bae89f3cdff6119cc27fedf820000000000000000000000000000000006a7f7cad2fa9db8824e4e30da7158f7737d2536554b904ed835c37add0341c07c5220db0f9801da2587a456300c7b75000000000000000000000000000000000f6dc3adda42dbccb1d1e3fea8918f5572e8b26ba3011429e754edd28559b731853761d33777f4e767094f80e63d417700000000000000000000000000000000107d93537a79173ba9367732fa3a28113ec37e053cdf31ce6970dedfa8a9b4cd55238289be9a6f40319e3dfedd132f95537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa98302150000000000000000000000000000000019dc19a1663bb05ebfc0b7cc23ea9e07376de413f77e15a685a3f11fc19bf0ddf38d5671e2a5e6e31624cbcd47a19cf60000000000000000000000000000000019e78aae57f327fbe8ce794afc22bddde08ff9bc9ad3527601cb1fd5dc0b8ed8fdf3b210f86760954b48bf61d74162220000000000000000000000000000000013954a533bf871e99f4a7d81a8b9931c480ce7fc47260c3708c590ade42e6b7bb887d4d24aa18642d010a8170cf85d34000000000000000000000000000000000a561d3f64ba31a6d45ffcf1bcac95f8f665133a1e962e31351ec78e369042bd3afb0c43d12b3087168c1142107241f31a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a20000000000000000000000000000000007149094366e29537b0ad7239ce04bf49f253e4b746b9fe440dbf9b425bfff21064fce66e286e08c87dd83e22a3b499b00000000000000000000000000000000045ead132e0d03c842656cfc82a45c8b4a3b0cee7a5d071c5f235791ff7b5ead071b2c529b446a15aa8837aafc11222d00000000000000000000000000000000013159458f2123698ad4e7d41da47ad7d5083b928839e346a32f2307ee69f643ca11335d50e47d328b0079f1873cc7e800000000000000000000000000000000167edcf807ee723ba70e352367705448047c6b5223fe703381af6bb103cbb24da739ed005b14fab5699fbae6574505a7c7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000feb6f6f85903b3c8e4d6ec2ff234775f12727fdf7c35eade09c9773b004270f659b00248338f0b749d6715778f1f4d90000000000000000000000000000000003300794df19b9e472e8b869a2762c07a9251cdb96b508dfecdbd62fc3c3843b37118d216a64519bc3bdb71e40f9bd700000000000000000000000000000000005fa144135a5d6cf1c73055750ab6582b4c6d368566172b75902b1fc7a6f5de2a251ca7efc7ac6cc6c0bded14df02b700000000000000000000000000000000004239a7bfdefbe78116a588810328024b1bcebaf8f28f09387dcab66dcf2b02c94002df09d12db369fef9dd960783c0b84f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d00000000000000000000000000000000007c8a24005575a3098c12ffa65095bfe227ee59e5e978a7ccab7a9a72391fea61690648c102ce24af723945bbcafece0000000000000000000000000000000000323d57bec7dfbb4614c8c3b286860fbadbf71901fa006149053ea614dafd56b1f3d6a86fa55bf1cbdfe8af4ff08dec000000000000000000000000000000001180b2b0b9c4c12f6d06eec07bbf6f5a220722015fe5365d1c4ca9e58ac9c8f67964d8230152d7a2220575c756bdf8b0000000000000000000000000000000001969a364c447f07d0820586bade587ccc816e50696aa0c5ea4f1daf6cd577769a890b44caa013d93e7f21f5ea269aa85c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea0000000000000000000000000000000008a51c01c3bbed13d42a4da626a8b89e2811db1d83d7de3332b36881ad14a5c8668ece4f5ed2b71204810457aa3d75cb000000000000000000000000000000000658a56aaf627e3f776d3f03caa2c00425bf197c6fa20c92f563f48260109a8f935d0d1638f5039486ce0c0100834fcd00000000000000000000000000000000126d1964f2d964c290cd7364e175ca4a855149e5c4ba488829a436b09ee5e21f6c964e439739f15317873088726bd51f000000000000000000000000000000001803186f88833393bd853970ca4fe414a43b7a619ded1f9c830444b4d43a94e9146146e2284d690436b395bf1e3fad15a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000002dc3756c7f4bb47559cd720a3acf4159290d7413e0498877d1fe321cbcb7cdda90b6c8b4ef8e27b2642b82ab9b3174d000000000000000000000000000000000c7490f1ccfdd91aa37a3044d265cb0612bfd9c065c370adb813b2d96f02d44041e79921d1b8935dcdb8c83ea4460ef30000000000000000000000000000000007beb34bfb9ba9b6fb590c7e830400888095d1958b252d187c184de91f165e12599d66345341292fdcb662deadcded030000000000000000000000000000000001ce203d58bebe1eb5b7cbc6038f75b2f7534bce9f50e7e4c91d6cc5ac1bb68d9fd8ce99206c5ec92bcabb71672c6ac195a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000765acace3e238e51bdaa08c0f6d737c9de55b5ce9ac3523335f0d35bfab6f4e7e2944b8aa4ee031ae9d39d4db96e9ac000000000000000000000000000000000b0fd488a6f9e92c4bdb5e82b52a0035f9a0aed7f69ec65303632017669f34d11552f849326e4dd204d58f50f3ad124800000000000000000000000000000000033991f66588b5e39eb78c7cbf62a74bbde2fa1b7c96164cb58040f0887c485b372e0ef4def9d38da9c6f5c4df2d59a700000000000000000000000000000000187d41fa7905739078d2c2f8775394f830d20352a9d91e97568c6929412f356009239bc9e1da3a8c766e89d09893b5b5319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40,000000000000000000000000000000000dedf92894c567ee656051a7f02384edc7206152af6d3c5f662ca02559a3cc349c6b034c6fadceeccf652a396dbec6c900000000000000000000000000000000089deb173bda620678247a7218408594efff7ab0cebbf627b93ed37e553cf944e09232b92afe2f5f31d29bb9ae442c26000000000000000000000000000000000178bc39b2ca8b032d3cde53d2da3f8797026d78c76c51381b377c79992f027cf55ba4e182773c99c99ea6293a948e5c00000000000000000000000000000000195d9cb91537e77e7a4be4370b982b6d36190342ef6ebc2250a9cc8ef6ef45211736ce1f41da899178c1adcc4927a9ba +0000000000000000000000000000000004638ececd7f626a069b1bc3ad9d0f7cc71e5f0c1b11711fbfee1f81466b574f7a11de8161e55eb574ab367f56b9d6480000000000000000000000000000000013ef4f403f139771afe7e97815d3b3777818a3054d02125d3a25138e504c8c2c6696061572322aa19ace9ffd8e3ff308000000000000000000000000000000001910f776582f5acbaea626d2378e9da133b63afa087f25c2cfbcd1e7b34f6a237f2e9adccc303f9d5efe22496ee2ab75000000000000000000000000000000001963bd62098614c4ca2fc2a9e2d679c2b74bf9d322d34377cc63c3b8e7f8a4eb7d6d440d081e044606402fb3f51b0cee2a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000001935ecdf4f21cc6b510321b4ed2663e339954cd7399b9d67f1d9e2ea7fb9bfff8531f83ab59d3f0546393dc289ab2dd7000000000000000000000000000000000f390b86fb4cd4c1a072a83e1d1198a57a650fa6e94b69d983b693c592bc0c8fcd9a46c6883adacc4c7e2546dcd079fe00000000000000000000000000000000136beae11ea54ae26a8d69015ea7793675625b2013dbeb081a5ed877832849d67ac709b81fcc4fb322b262ec3776c0c00000000000000000000000000000000011f1df574f63f679b6464df463b58948fd337a4b3f159229ba0313cc040303345e75a3a2b0ed0dedfbefa89d8331d074f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b0000000000000000000000000000000001510378fdcde4027999edc99d49bfb46423ceb0d740829e310f8a381a7632fd0d6b6aa3533c2702a5ca76d386ac0145000000000000000000000000000000000bae237bfcc061552ca07eb14300cf557c974611885aa6894f7933f7dc7a0a1cc3b5587bbe1ea5fa604e3cee1db5f7c9000000000000000000000000000000001743207a1814990c798dc3de272a02b1b194a485bf09faea382dedd957861dc15cbf981f9906cda50cce2899785b9a6300000000000000000000000000000000106f902004c66c80437392e92cad73bed6b73010bdc7b6a75de4c01f0b6fbe5b8d1f47378279bfa42b3af05120854ced07f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e400000000000000000000000000000000123362e41268f7821fcd2294b032c6a51c6d80506eb052ab6132267fa248a1a60c3e4eb2e75a1674bee1c9d46d82b9180000000000000000000000000000000006296670461ca67081cd76528446867e1a4905f88742d0ed8d1f7baf86e0a5e5ee86c8b0eeef07c14dd821dee0143bea00000000000000000000000000000000058bff9544e4e02c063158a52a68c93c7544e8157d37159dbb99b51b09e3d8f5b307bfe63a10fa409e20a35219ac244a000000000000000000000000000000000b135edfbf53187004d0977db94eeabf426ad7bea84ad76c6ac771fa186a073d430af76d717070e3c4057a7a2da095984e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb605000000000000000000000000000000000133d3a223112dc5665e78fba8fc8d040d133858d984e66d2382d5e629f9369dd127e93c7a4da77fad98a0520ebedfeba000000000000000000000000000000000e88515db391bcdeeed2a9f64d27387af0391bf832164fba79100b560d8150debbd703c140dae3ba9b1ec35c1f45670600000000000000000000000000000000042583722c69a19f413392c6a2b75c8ca969be85eb951056d7e1d94e046dba49c346d5774009b8463a40b0576ccc1a6e000000000000000000000000000000000ee61a9eb6ad497c57405a44d798868e22b4fd5b8c480e9938cfdd3f1817eaaf331a9988368680158c59c2801add0a7a27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f228900000000000000000000000000000000020352de9b4e8ea1acc8589bb22e23dfd0ae3a80de9e21bdb3f6391dd05a012e635de9e1f5f450bf4aab05728c054f8a000000000000000000000000000000001733593b94ec800bb59ed97dacbefda5ad882a8023346bdff8f471c5613c67247e27d72cb4ed8cdaa0f236018dd2128a0000000000000000000000000000000011f272a3b25bc519fc3e229211b846042031e22fbed22ecd0d1a4ef1d05feacf105772d71157e3d7293575aca257cd5f00000000000000000000000000000000153b4b4d7d65f7bd13d20fee4812f04706c96cd1a0d27b7e139c47299805e0ac86e8941aa38d90331c78a61c2dd56aa3c2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc00000000000000000000000000000000143e1f6dd9397f0e89a46c6ec995bf6c87ec8a72b309f050dc5b3134e00e2a16327767cb0573ca5ea9776215a5815df500000000000000000000000000000000186cb3af2cdb4562bf2d0c180079547cfb345cc3943fd7f9203fabbdc1547079cb9ed854f9b1a47f513e318cd409df83000000000000000000000000000000000c8c9197fa5a1e66b371a653c5d18c01fed8d17a8aa92d89b2cbd954b9fd2931fa61abb6676e4851dc9481732c6195610000000000000000000000000000000009026b259e840cb5264f6aad6ebbb09661f5b6d980389817309aef99e4e0cb228d3a7a06e6c25bdb1aeafe5acdc44441911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000007bceb74ba86c07d0fca20e4febd3b12b1fe9f786c9a5da0531550244f40261d7ce728498fbfcfe16cc235db6ed42e11000000000000000000000000000000000883104ffcc0d040d70bda04dcf67c1197c39e200d4d9daf5f3c185638a13dffe3dcac94fce4175187dad867e8d2b78c000000000000000000000000000000001404e48e86f199486db7d40076cc8dc4e2aa2c1b6d4bed8f027512e2c71817905b26ff4f0551f9c08a2a7a27b2075b6c000000000000000000000000000000000b789a6addb98ea43c0f9e85831a75b8ee1977936c17929fb45d4c06b4f1ec33b9b41e32b52cde542c9e4b64d27c686cfd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b000000000000000000000000000000000654e7f3985bf90dd1e3169382690fdc0f804eb6384ce407a060f539804fe6e0451094abaa0dad611c15d3ee52f31a92000000000000000000000000000000000deeec957d58a2246ff8f7b7448f5198647576c16c1717369ad155ae36d5a6bdb42c8d6a1f0a095891fb0890b6203f950000000000000000000000000000000013a01a6ca4c296f59cfa4a5f5399d28af76ffcb8b218c861d5e6dc603e140f730f632028c8da46c823d87bff5ca703280000000000000000000000000000000003698f659e86b96613ca74a480c81e749bce4b74324976c1d241a0911d078926fb2adfcc3f901a7a015a02f525ddbb808118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c0000000000000000000000000000000016ce72e1798ffd84b52ac664a184c6cf5ce1ca2aa263c9d056355cf610517e9c7bf7f057c342f6e3ae801b84c2082c0f0000000000000000000000000000000010992af1438eec10881b5e2e3fa3b1e91b6b5313ea58dcc0cd2159f8ba6ce5912d81b38956929620e04b3596f6835a6f0000000000000000000000000000000014315dbebd532d0c835e8e85a02c0814574cf040a20c18d06573718223c8ea15b7ea69f0cf342dd09037258398ba4bef00000000000000000000000000000000136d13a83e72525b2d4af54d14d5e21d8bd9bed18543836b02ae0a7e51d433c93aa1943e85f978a8a9ff4454d8c5d120c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce7730000000000000000000000000000000002c1bf7dc75006c2941b89a2de52fdcdd1b4fbda5b14fe3fc165915b90fd9d93cdb8105898ef59d5b374707f0afaccd600000000000000000000000000000000049a16efcf81de84e443666bf990f6aad2145f9c9c2c61a752e256e8f447dfb27b462e4553544971807f909a666af12f0000000000000000000000000000000000aa4702fb69d791ef958826753d3f74f61c7a591ab94bb6c1bd5d82d94c5877121ecfc1e769d0d16ebe491b775ad96e0000000000000000000000000000000008cd7f2562eca6c53a37382fcdb04be53998f45c2241bfebac3d1fb08d8e1d4df3182f2bd63861d0de72d58072356ccc982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b000000000000000000000000000000000a6b30c4ddb692ae33c903693cdba00ba48efa48e90b9cec9dc747004e57a8d5a05b5522634fc0de306d38c28390dbf4000000000000000000000000000000000601341b3c4057767a910bf30dd16324ad7abcb55b7e98e73584f26d7f87d8a8d24ff2113c12ceb3077bf65e0912b2360000000000000000000000000000000019dc9c50f613470abdb5c763c0272e88e34ed38e617d6757f4e70d05b8ec9f67a023b4ec1363e7e60e38cff64e18f0510000000000000000000000000000000013fb1858f7efeec5fd03d9f7f4513e3e9103c340eee7bfe48ed3cd3dd073b96add9450a17f12e161f1d44669b1b2f813000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000d6c135bfe0fc7af93571a69b7c37ba691f051d69582cf159cbeb0bd59b48342172a82a3eec2e3d440805934e1574f2a0000000000000000000000000000000001b04e56cb3bb221caedd3582943f89a33b955f624f9e473941f1dd987f2898339142a654d11d87bf8bd2fd0fe0d4c1a000000000000000000000000000000000f185fd420b761a1e38d542558b0beffba916f369b37296fdd8878a7c3d2ac9d3ab1d8e45ad799f0d81bb439b5e5058100000000000000000000000000000000002d10ce460c414fa1094ef2b7de8f1ce024b6d086d10067be0fed4e45dc25c8e50bef361d39a2743be1e1ea4fb7e2ef773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb2000000000000000000000000000000001972ea36bae504d7047639ce6e0e6c3b16afe89fa3d6c6b33c910c8d4b70782d8165912e5bfbe8bd84f78f9f23f7f956000000000000000000000000000000000ae55c4fc1c01f1bbdb060191e8551a7ba5ebd3dbadd138202090d7dc6765fd1ef5fe8204ae76a8bcfa03ee5985a35280000000000000000000000000000000018ebed295805e0fc14f1c7b0e6ee12ca48cb7177c1d367a613e0d6cfaaac5128fefce0e8f38d4e2f11ae0d327be466a400000000000000000000000000000000157068d89fe48e77e0f62e3b5b0293423f35c5f4ffb9e0577f5aa49e91cea6bd312a0e65ec08af9c1f53de6499409c8d759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e00000000000000000000000000000000120e4a8935c08032dbfc19a43e2a770b12b05cf1dc229e12f683f0d7f604bc13666bf318fcc38038b618ef83c9448b870000000000000000000000000000000004e3f851be46bd85f37c8b1d84507f4ed63ea76bc305cc26a6f4cfa2135d5affcd3b319d9f57619e21c964c6246fc3f600000000000000000000000000000000138733f352029373b19e1c40d5958a04257e2b344770e1bbb8f377bcfb1c7225ae7a8b0b0e57795ec06a08e13c90d7de00000000000000000000000000000000093e85783c556a017829e28bc42b607b1035890fb9743bf0e279df4dd8a695c1dd07a76a213087c3a8a7e614b29b7a1ecce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f0525302938000000000000000000000000000000000be1ae00f9ba0a2e57f94728508e0029b1bafd52c91ce718ba41790a3541117d1a9f846d68440978cdef016c3b9ae422000000000000000000000000000000001947683154204c9fc93e3aeac17b417453a24d01804e8acbf6f67947f5962dce875f49d05e6ae65384602828784f852c000000000000000000000000000000000859dc1c00b49cd1292cdc65c6aa4b11e27637b949c7db508930c557ee3ce00f98f9cd3dd0f6d73a646d176a91d75c070000000000000000000000000000000015a7a6984b5f42aadebba1e1f4682aaf1a2d01c9ce2afab7fed2269373467787bb1361b493dcdd862180e9159ec2ad5785e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f,00000000000000000000000000000000047cc33d9decfd059724bbb014fb9cd95de187e2dd29cf4a9bf9ad06d415e2cacb5a4e49266c13e0c38f2563d1a21d6a0000000000000000000000000000000011c79d93fa870d541e79ad4037c20b85d3cec053587f9df60dc11e7dc2727d79059f75bef76474c09fe61ed10b317cad0000000000000000000000000000000003df3f0db20c5ffea4dc9f4d9333d76141447bba50da18e521e89aae1e63750c71b392570d79e597967dfc9952e903c60000000000000000000000000000000014e83ea645b1019ac2dfafe370f616af0c5baeabe217ac1f9ecf78877717475b25911b339557422526a8163434439923 +000000000000000000000000000000000cd1d25f285c2073175ecad5bba4987cc52012eadc7b19dbaa20fa82d7a6cfb8a52f33469b6308d921eb4b3b23f7022d000000000000000000000000000000001707b67a23d9212d30c06f26f0040c38389b185057e80236d2c828a8d9ade4f72eee1d6eccd78e4f4d71e2c28ee9539e0000000000000000000000000000000008e5c04effd14d915b9afc2083afa2b6d4008cfa0e47144a41982d8b5a8e77922a2609384e2c5d18c871ae24a7d505b7000000000000000000000000000000000f414acb056fff2cd6d9b408adb6eb7f34c8f66a66ee93945a3381d46c2d181613047ca6d4067614c190da444223cab685431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c0000000000000000000000000000000008ea841aced2d0b8dd688947648a8ff18d0f6f03f63ee1c331f126dd4fc0da3d386535156b80902bdc1f65add7769cd70000000000000000000000000000000017a32ad2763d99c38c954f62466e78c0332e48875e15afbbe9c78376f1bab12346c73a573738353e2162d3928091dede0000000000000000000000000000000010ea738884dbfe5bc35d031bde9aa4109b1fca529502e236aebb5ad0bf71dd2f3db250d924415b0bfca56519f8ca5d290000000000000000000000000000000013699e29cc1871f51a469898be8b3c732b5cf7860286e655e65bd8176832804d17b48d0ff85eb023360db78162581297b6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa00000000000000000000000000000000004d504e62b2825651381ae862fd33407309851d5291591cd0f541fd092800d709ede00a9134e65ee752eaceb0a344b50000000000000000000000000000000016481efba290c37aa4ecbf940c76ee5df199b0c1f90fddebd2db28120bb5a14dd9f4a067b6d4889aeb683cca0f6ab337000000000000000000000000000000001400c89942cc63417ca4cb05c9d81dda3251e5611c27fc7727c3e803170672f103bff26f7216a0b646533aac3171488d0000000000000000000000000000000019889641be9db08880543ff476b9d4c72167092548ba49a3f3ace4518d3874f4f7993ae7b8cec90f092f144ec9d66c1a54dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000001927fe80adc6dbb581349c603103ad8831e645d9275af8669939b83829182cc6e2a30df2fdeda6d3aa2e2a6126e00ba3000000000000000000000000000000000b6d7934d5ca1098a85a0c60acca075220105e221b802b1be97c2967820bffc2937fc3278ed0f26905c60d44d5fd8dc000000000000000000000000000000000057acc1379f23c0d1d37427d400eb1b0a89f3736c83d3ffd797ae279e01e2acddd84082f13f3c8b8f1bf7c275702a9c700000000000000000000000000000000038dbcd7e08d34c553850a52336991a7d48968e98057e930790d78b5c6368eb2fe01571b60c4aefb653ec04122953d56eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a00000000000000000000000000000000014052031b88af979b7edb06c99c2e46bd9df2c862c7e1b71321754841fad67fc3242b51141e49ad86b61344aec913f40000000000000000000000000000000014806a86d078ee9bdde99257b67f50dc2ddf9bbf01dde931742ee6f739aa986cfdca06cd32d23d86f2c14c3b09033d29000000000000000000000000000000000e0561e795d35ceb8bd9e3b276406ec1f697a38ada25d1dbe08715a28bdd3d6ce6e0aac01f7dfc7c2b403850ab213b4700000000000000000000000000000000146a65209b09487e00e356e3b29952280ebc6a06343c4ce14efa0c6281bc2482698bd02295bc35125275ff5f5bb867dfb273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab8500000000000000000000000000000000021592cd7f4cf9cab3be53561c889c9ee865961aad51339f6393dd6a0b7dcc8a7c48b753c947b15cd3add01abd3d76d8000000000000000000000000000000000f9e1a80bad58055a8577700c177889c4d702de04343c1202eaed9485a76493158547b20bcb552b66c42a0c86df809ed0000000000000000000000000000000013908dcff1945cf06f038e3caac9a7fbb3a6466ca18627e93468a875759a2b5599a96834ff21fcd6bfbba82b79536b9a0000000000000000000000000000000001b6354665109c5a64613c3bd7d805b3a34098708f3d41c7b77db031ac6fa0b2d4e2f2f70c84ac78687b0c0f9bf334771342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f0000000000000000000000000000000019394063202186e141dcebce7b8f0f267ba6057a0f993bb1cbe22a5bc528323823bfd1597a87017d478186a18f09a47800000000000000000000000000000000148437bcc43d432d70b47dadac8e738616c97d38d0f84dc132599626612f7bba74988bb23ae47ac15e6f70c059d607ed00000000000000000000000000000000180851594710f4bb5be7ae0104a383081c50f59e4e048614660ab5a4e2661e171510f5b112d8cf97a6af27d56d137c860000000000000000000000000000000000599f3f82f29b493ffe9ee3a8363b9a599a5ef3c9c5c680d4e003f4ac5a7de0562cba8e2a4c6da7d07cbe86c3f7bfb85b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee82398125000000000000000000000000000000000cfdce7997601afbe484901893a1b5fc0b83e8d238d41d2f889a58fd4d884df1c667a000b53b587df2c42ad46aa2c3e0000000000000000000000000000000000c50bf3e06400cb10494cd09bd89f3c96ff49c9f74dd5325f9489ed6be13b59bd7b0b2351411ac854d430405b8a2a3de0000000000000000000000000000000001db313a34ca4073e4fa2287e234ac32bc579742de22e5218f7873b922f5804894826e6054925a394f125fce850f33ef000000000000000000000000000000000e0627a66d286e8d4d3654b32fc5f552a7ca12f0bd47eb6dee0dde22ee48165247c067a0f4c3d422bf3562d38a3c0cf1249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d170000000000000000000000000000000005285ba39f5bd981fce2fdf853706d70992acab2dc6d4c4198144fab397392a60d631056b580d0d98f3f350414ce554e0000000000000000000000000000000013bddbc1180f155872376fcdfaff2fb12d3d9645b81bd1475a5323ea855cea820ed7eb693791caa9bd3fa5c66036439700000000000000000000000000000000125644d32df397def58dff875d7e3f14166e765ed49a3991f45b38d74db3985fc7f5052058d85594c8b97afcf850e11b000000000000000000000000000000000fca4662eb1b39f576ee820385fba88ddd2fc01fcfb9d9f874453ad725cd5defb357be028fae97ce71bc5ac26d11c1bac014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e8950000000000000000000000000000000015b97d7c74c8ec102083b41d7ce5490466e1c0e261b5ea5c756d3f9ae79dd2d8ec6eb5075cfb76dfcf7bfdd80442f7d10000000000000000000000000000000016812f845faf96b8b69ac7a6af3c8947aa25877199e3c12552527706a17b768bbea259ea61ea82c4624a96cbcdf4040d00000000000000000000000000000000123ad55e5cb5ac5bdd3ca0a5afa7c3f8e4b98ad91a205f073fb546fe799ffc57b3c1c3a6209547ffc6ef05fd24be6f5d00000000000000000000000000000000017719f31946aedabe0e9d88ef3f90eb6ceda884f5e3d2ece368373785b2d8bf0f9677731803b25accfcb6cb716e0aa4ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000000497e3480d58027c780f47cc35a121ee0cd76c4e84d9a2f9002c04a1c286be990167a0138049ad70467132818f48ec9000000000000000000000000000000000ec0ddf938553105400f70989140ca322d996f48ffb1b35641ca36a6ba9ac1daac1603c100822f80cf62ec3bfb442158000000000000000000000000000000000a0b6ebee28a792df46d2f727af812c15fc91a471e0d8e34b25b26048f3b9606d8375b5b268c40fb04ef8f098e1d03340000000000000000000000000000000017843dd19bfabbd0cfa41fb58e70a8900397d17ccea783087ece90962560f5cf090e8d9eaa873a6a6ebac45219ea97a68b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000b27c82d71f7e4aab9a68596669596df3f62071e921e131ba4d9e59d8d81d370e077e93a4a6a43e059661227f40b38c800000000000000000000000000000000093004917ceb2fb4a1b33960ff74943d520f86e83aa02b9a6c85e4b9a489e9331863cd30cb6ad6f099d03289b4ada5520000000000000000000000000000000016f04e35186c7deaf730708e1678089bf3e73c1164bca24bf8f70c4f6cccd5bbb34bbb5dc313ee428aec4ac9c638a01a00000000000000000000000000000000011052348cec9dc3e85e01abcca5a652461f08a9f5d72b3fc27140a6a571137f0065ed7ccf9ec8cebe314ad9a214d5ed94ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000007083dfb0738d58ba8933a1f60283e5da8bf90af5aae4053ca573ee7223d3b80e4bdc30b4a831ce6af9f52f393e9742600000000000000000000000000000000130c627b7d3a527c94cfeba9f514e75eac047e1b6088c082229a8c95d0765a0898ce1e45694ca2c7935bb8e41e44e8b10000000000000000000000000000000009610645b074e652a08f2b89dbe594afa3009d795ef211f7c036a56274b1e1bd69a035c4f356b6b21f69b9cec2bf7c32000000000000000000000000000000001020f3cdef468af700269aa1e9d928e71b8c521f23586c9b0155314f0073da7de04ca41ececd5edcc052af72c05f0e4bb1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000123540047f0768b0af841aa4aeceaf3dac31ea832daed86c8cbd1d33ed0282c6f697d5881f9022af032e90ff82efb6bc000000000000000000000000000000000113daffbe413075f5f4f6fb42f37b6e9d5e5822aa24d6f865792f63e6078584246bcf8b17117385db1d6233974f6ed9000000000000000000000000000000001067b46fd221b6995d25d4bf0adb088e0554d858d4e5d9d6b59e1ae2a7d57188d559b0208918a8944aedd62b1ebd4f4700000000000000000000000000000000087dae77e483d5c0baa37b9b96dad5ca92b5869fa253bffad24dd8747446f7ce60858b52438e58233210d86f470f765fe670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af8500000000000000000000000000000000039d09a094d655c139cb9714aa258d9548473162548048b0f07c9317a41a7e5dbaa5aca156992c8a509d4071d9ae4394000000000000000000000000000000000f0273a38b1b9d006efa43c15a53f026587a676912d0275968608519e97994ea9c6a147e377f68b1738ebeaa178f9c1000000000000000000000000000000000132cd92417578d2e46884f1c1a1080b1916c8c8404d2533a4de02bf8575c80ce7e8097c2ddd1f95737355521c0ec21ce0000000000000000000000000000000019adbf09a268a3ed8eff936d25fbe8af2874e44d2580c7941dc14fb89c5da963b468a7088c4a763eac89f4d15deaaf5e54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000756dced467ea32c3c425590b7690a45e250e464ac6927ad3f5d2d8d2826961b8dd7572db609375c8d06cc3b9bc3a157000000000000000000000000000000000b79b4eecbaf1d0f8a89f9ef8fc144b3aff38148ae260da7c20e9dd3866d946585df7ed12c8b7005e7b0e1387c9db41d000000000000000000000000000000000afc403b008b70e19f17b1ef37c9c396577a585b6c34b23d09621b891efc00ef9460c3f4b5f3e851ef63620dc06c824300000000000000000000000000000000024e06f3f3b18c026a166c41f75d7bcf699480f5b6811463c27606c9ec1232fd249a46235b7f5b5a2ed3b53231b333150563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d,0000000000000000000000000000000004f2480d0b7e84a996965b76055f6879aab5bab26656e4e5ca7220adc17f14b5946047484327bbc5952d9f2ffa5f92af0000000000000000000000000000000002f7260c55c500b54df4391a55eb4adefa7d19bcbec82a107fc0d222c898b97360a679a02ab3023ce2ebdcffd125ddf30000000000000000000000000000000002cddfa94c8f6b3f29c2fe018b1f8679d0e98c8c2656f86f327b9cbcba574cc52643ab423b458994a03347460deef6570000000000000000000000000000000014eb4c84f71ef5935e707a11a92ba34780677ac7eb198e160585ad92aa5c1ea21e3a77be940fe344e7e170680e2a8d53 +000000000000000000000000000000000c2c4c039047d297049afd0e8f0375bd4294d628d3a22078d93b684b737e8c4b6ad3ee544ecbeaad6b3c75d8d217f3a20000000000000000000000000000000004c77a2c0943c6f997ce2e785461f8ec253c47273ded4e1af43ae882766ef8c168e66d831abc2b3b3a0849bbc210cbd40000000000000000000000000000000004456a6c267a5cc6b7d9a9f573270855186a1b621cfdc465fe71ddb4d614565d9d36b13985b31396587919226843c6230000000000000000000000000000000009487cdd8a0cf7f40e9087fd3121cb480730f4302339d25fa12128033239662ed65579a59b837bf1bc5fa87db15b15335b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e990000000000000000000000000000000000ba43205e8392168824f77bac344d60c1a9a0b14ab55538c3bfea4a64984cf381a2f61c64f1ba1bcfd8a7973e43f6e80000000000000000000000000000000000e95e5ac415c3e3e7c9feb6e7a2af3e8189afca06ae1fe54bbeb31783810860921ab3c76a475fb227b3c8299e3f1caf00000000000000000000000000000000001e3cb2106a23e77a126013087751c4d2a419a51beedc3a33faa6c933bedb3d34ee9c6450c583642426edb352e04da98000000000000000000000000000000000ab5af4c98aca1fc3fa55355351d12f3bc639662bf8b5b772152988d676b00ef39f767237a2fa3be936e83d1dd77da86a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000012f3d927316ba638bd6294f7dd2f3f166d20285ee1662ae4dc145835704a17127078343d26042a5c397bfef31754186200000000000000000000000000000000162893d6252361c340057bcac31986458b8b55a8a4283f5a06ce1730098f9838dad1bca264374e7261bb9d08c177c1820000000000000000000000000000000017264aead0ec41a079827296f3d32c12adfca7cb6c674070d54087438d57b6ccca4822b2337263e60075d469b4ce0ccc000000000000000000000000000000000480cae035bd3bf1b4a4a766bcd5f188833e9587e1aff0e1f10e36ebbf2f3ae76bc0946e7c336efc3ee00bf42e7efbb9677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb607191000000000000000000000000000000000d13375356b1518e37a13b43b7d192eb74bd69636f91c570c41a741a8763c03caf8d13c7364f57c867a4a3983e88060f000000000000000000000000000000000f6f78dffb404faab88ac7373e0c765209c0af80514d438e18393bfcfeb60d9a5e13158d399f43162033571ee4a75dbc0000000000000000000000000000000010c379860638ccf3b6cb8479aa38881b0004197e3e367a1d5ef7c7fcf075689d283b87022e2825b5c789ac6a448467320000000000000000000000000000000002dc392872cf2fcd8e196f10c1ded175300070e4e38aa58c89c81e1aa5faa08d770a5ad90a8295a890551f9329a13cee53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac0000000000000000000000000000000010383a21acda7c8f3f3be980bff2d57fa0a5b2dc424164dd2ce53c0b20ca399d6227913b7b550462180b01c231e4813200000000000000000000000000000000078aec90354721f0a31e1377b3652bcb1f388ab36f1866c955f3ea8dfe6ac2c25bc4cea14f54aee71595c2c1bd2dc4910000000000000000000000000000000007dfeec77213d952c183452b98ad936e8854608d950c0c1451262cdc7d6de5aed0db07a8d74b3e8f674967cb4839c4d00000000000000000000000000000000015c09e4ed2ea76d10d196f7a733ccc272b94dc436d6bb5fafad2fae4a96372c2c6f1325d1554746814ae292d8e6b1e3634c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d500000000000000000000000000000000138656fa091cc6613b1fcff04a3efb4f9c393985b2c78fa838eecbbbb8b6dafd88d9c72441f9bc735649480b5187acac000000000000000000000000000000000a35cec4819ca3321917cea5aa589db8cf61882fd1135031dc41a8207a8e71d326312799291b160a646148c382ed086b0000000000000000000000000000000005b6e4c02c9c54630c96271073513cac3a42d47a7272f62a21c7ad4c85c19b60b70d04719626cf4273f6c5691719931700000000000000000000000000000000166a20da734a47d7e28cce8f0c2d679fa6c738a7a1ca9089dc67ba2b1c92a83b024b8991f131e7e8802a617153de4554bebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000a4432a544deda931b1f62759320ada2963062e722bc1b748c9bd0d026ffae10f228be36ea0ab076358924f4c06b6feb0000000000000000000000000000000000e955b1b1b28d2044b6be371c58bc85097c77145b239e913bb0729757518c465d9e69338066f7496aa6a2038ea604f900000000000000000000000000000000017ca2a7d52c3a82ab8abf9fc1bc187389b6e4904e161541008e5b3ba0981870e01060d1272a6d59bfcfb294c942403f000000000000000000000000000000001870649a50e0978185551f213eefd9305d33e92b3f8c39752b6ebe18ae86ad97f92acef05971dceee3b3729becea18168b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c95960000000000000000000000000000000013713effa20d5039ced751ebafe1516f062f11ee05ffad37281cfee9d7a49ab14c065709832f6674bfbf2c9f379bc9c9000000000000000000000000000000000295f7ef148430209b48c292b024474f05036edfdee082c56aea05a62f1fba3ee7a540955423f78614c8385da8ef60040000000000000000000000000000000007408c97321b6d7c27e5e442a9e35b054e743c34d845874aeb1ccf4e903ca7803ed7fb1288327865f9e0ff0a388e92b400000000000000000000000000000000081808d03722a2d48846a693059c2662dee614f181dc406825544d30a6adc0f9d84a712eff80bddd4a27a036e4bf7359d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca000000000000000000000000000000000880b646a674723c15b240ff56d2031e5db724251b1402a68df8b26261ffc9fb60a81abf165c6832137dc7a7293142d200000000000000000000000000000000172354b62bfb8d388b5a984411414738302725a508e8abeacdcb46454371d5e9cf762028fb65921d5c3dd8c67d42a981000000000000000000000000000000000a1af459bc3122dcef78359e468f4094d609ae3da09ca5aa6efb71a7494dafa2373a3906bac1f324d98b3eaa982a27d500000000000000000000000000000000092ac3b47253c7f090df076914cdc08a715faf153e8e365392b4859fca1db14d3f7fb998c97de9ad99b7d0b357252f086330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b80000000000000000000000000000000003b23eff722c078a781771d8b75d519e7a062ca3e4252ecca877845920158fb20d79a9ce449d9087426b113da0091826000000000000000000000000000000000c9026e8d3fee6282492393db504a2c41db19d8fbb83260624b05ba4107d6cb2c90d645a3c16862b27cc3fcce9bf89840000000000000000000000000000000018b8648d0a42285d474f809519696df9e1ad5c35d8e848ad74fbee37425aee8844a8be8cb4d3331670ee294ddb9a290200000000000000000000000000000000068cad37ee8578f4b502ac2ef4371a10e5432e57fe20d0cb074dc427831872113d3514a0b199d813b796b8357fa2a3dbc2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe3740000000000000000000000000000000013200f0aea4c60937be47213b6149b0ae76767f3559e0519f774af4a5d9431e2dd7ea74b42cc3ceb28ccf0d2f01116f30000000000000000000000000000000001c5bff08fd16ecb68f21289a3e7b9a2ec5da1357d604710a18e78ab780f8ef0343d5d9ee7f7988a009329b17e498beb00000000000000000000000000000000125453772eb9d1335ce4dbcc8f2ab8426fe89a0e49fec51d4e96718a38570aa82dbef452368141be2df260fb131c50b2000000000000000000000000000000000432cdd445519775b9914a986a0941cc829b4a15cd361df9ae7129547b24f7a6a15cd8fe9393fa1551db2d761a208b8ec9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000396cb6d7b44f92b716ed02985d351b4e8cd1bbb95f239e4f29d7379428470be395e2faeb8e3a910007aaa490d3c336d0000000000000000000000000000000000ad0c0623fdf50c2b504777554dbab3cde1b9705e976561873d7c22b81f49c7654a7c76e558fad1518ed73a0d3c3570000000000000000000000000000000001241d5bed68e02a2ddeb3ccbe109a161abe81edd7affb72182c5163851211c4763e6aecf766053b61ce575de893985f800000000000000000000000000000000183696d2a48feef6088f4e9f75a5055e8c54b3813658b593958490ddd4245ac495a8ff966861b20f26047f07fa8609a0c2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a000000000000000000000000000000000fe0ce41aa9e7cb2bcb4e01721b7b1d99fca4e9b7c4df09bec00bd346fc57c25118ba70d5333b7f3eef2659c64520a470000000000000000000000000000000005c932e09c62b7ddaf3f5c420c60740befa7cdff5bb812e0f089c45098d71b57004b7a207f0cdd34daaa3282cf6e9f7e000000000000000000000000000000001874200ead9776c1ecd6a54a57e5d0f9577910a4b3afb9b051622f658fe3ef6cc5070af60e7ef910562720e9716158d6000000000000000000000000000000000c2c657e58e400a67e59deee8c28234ff4688e781a2f6f2f0d0b186a5e4012695a522dfa0770cfd543f55939a05e20b09fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000f0b561e5860321249b9ff434c604d26c3275824fc4ab9c1ce5c5858605ddaafae83ae27e523bf6006932f6c7f33d0a7000000000000000000000000000000000b47aab85bbd909599aa85c5eda363b67790ac6729fd8b1f4f53f66dd796cf2fa3496407b1bfaea4dc8eae53519054e70000000000000000000000000000000000cab1ebd23bc05c53bc9e8481c469eac3ee1b140af545bebed10a8fe50698d2ed883219881929207c0addf2f687198d0000000000000000000000000000000007742de55b799950e6f786f4eef45d0fb67e0475272ad68a183135b70047abab6c2ed51ede16c39be7b986df334e9e75d4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000923861332988bc843a65ec5dd4637f9dca8a15e71b82c780fe60d768213d118d8948ab554e30bb9253e900a9b7d87f200000000000000000000000000000000132b1faef49e7966a05783ba526e71134bfb577b13116548352da37e91e617d7c72ed2645e672ebbc517e079247dfb0e00000000000000000000000000000000000a46a8893a194ebfe077afd05fb25d4680f1e4991a3ec29475fa5651d086d20b38136155a65a4c70af31de5a78af59000000000000000000000000000000001344eb957594028b4228cbdb8efb03cc7cf49ec43b2ca5481eae1df6f2df3d5be9a7c4e4e78f8c39be546e29a83c92f49644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000012d7111303563a6358e5ce9155d7a153b5781062c2f6b919efc67ddfb4c61ef03be8828ca6339397b84763a5f8a7e8330000000000000000000000000000000010a2a0ea9973728d3fb1b5906ee84b2635c687c11398ebf605cad30216df3b7b4e3ee1653d4b323a690e6ba614ebec30000000000000000000000000000000000b93d5de37b892d4de9407a820c73ecfd6cd9fa565db82e7e8c14c8406823f705ff0adf6bd6add5ddc5f72c91e52e840000000000000000000000000000000000dcb320ceba5436df8f099c5a77f34376c96d830f5e8ab80667d156d89f6bf8998c148ef9a53847ed395871ab86f6d280df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d,000000000000000000000000000000000ff3e299e6b9fc488d6db991cf9d226d330846e87c4a5cd3e5d4ac955bc2c3c2d1ee5ff230098b48f594d256495f489800000000000000000000000000000000097fdb8fc95d64c7070d8c71e0fd2a933d1e1ad3fefa230f079bc5743828317cd1b0c6d4253ba9c3c6ec6b12d53afa700000000000000000000000000000000002448bbb179d82eaa453cd5452752b9a083b52694cc65c5d9b47f72eff118d7aa06b0fba18eafe93d2937d7399c001af0000000000000000000000000000000009dec4c0aff2a46b07855c87e08832811633709ddfbbc30dbb7e78b3de43bd95e383baa6ff4c58188f0c7643e0ea5a40 +0000000000000000000000000000000008afabec8a9985cbbc6246825785654c1d2eb7da5a01f76c4af4d0096b9baed3c33dbe492d14a6f9e762f06eb3d198f800000000000000000000000000000000027c592315dee4bcc892acc6f41a6eff5219c308253f7cd715d0e4a32c03c6d0d0e8568e146e9e799ac3025486c77fc30000000000000000000000000000000015b4ee27a3aa518a1ec1b447bb8f9128301c85b7176296d68dad3339b1dee78715b2f031a7fb6ba376145c97ceafeef60000000000000000000000000000000004b7e30ec7cc024ced863ce511cef3cabe954a4e5843dd636d776645a44225a36ed7e153ab5bf5d18f23c6444751875c8a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a83300000000000000000000000000000000119949d36d8d8e2bc1c26ded5f5fb01225a980a28b934ed3862480dc9297a3758e0f08ccaab3a09b5e5c0e4215e3246c0000000000000000000000000000000004a82dc22316ee6af39d937b662d1f1f2dc855c2ca8f33ec3274d833e87d594633fc7fab247911e0f46564397910d6ce00000000000000000000000000000000196900a09d8504ed960d41f4a8a2cde2e5dac61b008d3f6eb47e86d7b2ce6fcdc0f85157e3ab1571094d9fdaa75d0d500000000000000000000000000000000010c52ef9407eb4ec57844aebbcc3ea5000b1940d035dcc2a873327affaaabdd79e3560cbd29c63ce04f6279056d6eed1bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000000e43655ae05dc6cfa93113dc26cea895d1c5bc73f20454c7b441dbc5ac80035b290514b13b31b41931ea5336d8d9a6a7000000000000000000000000000000001199a873958c63147e6b82625dfea15ce90dd41ceb4e315f67221eb874ef32c6a2953412e7e981659c72239a7a72bfe6000000000000000000000000000000001845af5936b4d7487ffe59137ba2f86daea3770cf37fd560969ee48243389941a1072205e049ddaa06c0ac56b7edc8930000000000000000000000000000000003cc831177f24614f93a118b896434105f05a277051a852fb9973a775fc54f779c2a1f3d64c457e5231dc22d6aef606b58b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe400000000000000000000000000000000005e63584bc85ba58615985f6a466afe05268e545e0062cd7214e0b6fc8b87537c745b754cd9a1144948bc88b3c43acd000000000000000000000000000000000635b6a49090ccede3ed2ef203f0ed164783e3df4d9a7d93319515cb9230bd841b61a097f39e30175793b3e934d8e426000000000000000000000000000000001861e65f47a9da1584c45bc79a66045d86bc1709c2d1cf6cd2930a9fcc8c4efaa6536b5015be8d54789e8f574f93f9f70000000000000000000000000000000009290ce63d55eb436794acf11be9d896f03e7608a1bc8528f61ec9473f054bc9fbbda1072440e58e2f6ba080a01180173b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe0000000000000000000000000000000018feed9500bff884d2bb58554da2180c68267b6d3a45c2c7cee4c3f8524252d3faaa5eff971bf40123587e669fe66bbb000000000000000000000000000000001441bd3b58b4a4a87c2459f873c0692f5977b775af984bab46dd76cb9f775d2faebcb77b2854c9f1faa33f6c5de61c6a00000000000000000000000000000000123a890c3362c77e5b5cf9846d9c9e43fb3242d5a831e640ad080993fa0547854c8d11cc22f7f7b426528bf1154d2300000000000000000000000000000000000ff4a59ea98d13cfd353ae61e18d3c7018688f755561e6a1da5f09acc4277e8d49645087115acc64f992ea778a11f39bb049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc0000000000000000000000000000000006b019d005141e82393a2ca04469d1f6fd7b9456001ffef4c34eff6b2e91df58e99fd07944f52b108bd41ab6c4d6bbf200000000000000000000000000000000109ae87042029856befff0c916db5437e1e058a96f2970d8816b3becc93a1a50d6d336d5451303715f3e272147a36caa0000000000000000000000000000000000fc381b8dc9dc02d34db13e34732a10d0dfcf676c224a05a3bffd888b0af7c415b38af0b6afe6b464ffca42947c6ee5000000000000000000000000000000000087040d09c39ccd06c9ecc360fa02147a32e8036ad6e4b6bdf5b3883722a4e5a887dd022d53706d2585fe558696be6656197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d970330000000000000000000000000000000000e35c27b29df0fa9298bb9ab6a38b3450782223e2115d79152f9baa924d762d583b3ebe88e42f33028814ec78e5b319d00000000000000000000000000000000190c65667627a16f0af0ac7f23af0803bca810f3986b906b7b4f126d98473d52badf45e90e2e45bb390242fa8c40135100000000000000000000000000000000103f0283a5673c16bcc0f74f259c2eb077061947da04e467dfebf62aa005491e32b85cb73418b624a30dbaa01672921e000000000000000000000000000000000465466955c908607191faf15f0768dce42488c488eb4a065977f21ac7484766bc0abf23961ea2ba46dcc04956abf6c7721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000082346e352e845a54cd4267f93b85b2c8623d4650e00c1c56082b73ee31f63588d2c117d3cdecc0378fbbf8956b082040000000000000000000000000000000001a7f43c2bb19cb32345c43c950536f8e85815b86364f278f6ec8169eca80917c2b8fc08d59b20cf55f25dc468e7bd7f00000000000000000000000000000000085a5cb020df10f9b4c7afc01b1d11700579dec1e85e766507def2e6cf5b714174f7be9cce3b18533a5ebfeec2b4e481000000000000000000000000000000001836d7506d1cc984fb777b8ee935d6f5b110644f59e96ff44d8329336d59a3e1d2b53a05d35e97f634baa4fdc11a6cd8461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e000000000000000000000000000000000c1c59828ec6257a02679cff0bee0d665d449d2a158bc6d877e84cc0fe2161c297dde09b778d5e1249c515833e483004000000000000000000000000000000000f5e82589bfb7781e4110f1486752b00cbdf96cdf4191d75053c6d6d646e1c989add011361031a11559e156d64139fbf0000000000000000000000000000000015053afa7fb2b4e4b70f3c8a570fef8288fdc22dd951b6ba8a40b6087b9ab04ede21f0ddfa84d6d18914041bcf244c110000000000000000000000000000000003f399800cba51ab35624d866831ab6506392cb3acf549787153ffaf08cc451acea46c7a612821dd96c45f8b75133d88569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d000000000000000000000000000000000e26242c8f73116079369ef4265f624abd4377e4e3485c28197663de9de9f5618c3b6ee602ff6bebd1c242aef7295b2200000000000000000000000000000000066ceb3ea6067220bd28fa1164237782859d27c1d3087a42b4d09bcc343611e4ed2be014a27f5b394c67643dc00f57cf00000000000000000000000000000000157f9d30de52110ea7a2a35ddfe67d9fad7223c5e3307e797dd0df3621520a421958a2835205e3c4777923f47d47e5310000000000000000000000000000000016ebb41beb85b9489a6d5482f8a3330a5c5c5e5718e8efb8b67362f9d8e9c313e9e563275ba38c207c5bf3d89c406ea62061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000005d1c9109b5b7409f94ae3f7dd9e8ae4908a9b378fea4ea284cbd33d1e59b605577b63892aaa8ec14d415f34e22fec520000000000000000000000000000000005afed05e62599f20f7eca019f41d770c630cf6359cb5601464be821691fba5205c16e7b580e6881047214f938e5104b00000000000000000000000000000000105637a2aa4725d8e080dec3b731a111ea4c94b79f898dfd51f645501ef0c8d68ea8e80fde28ff96e927e44306ebbb1d00000000000000000000000000000000080cfeea754474ceb37973234d5dc3269f8ca99bd862d4d2d1a602321fc709945a3209e5ff2cc962cfa6d03017c9a1354129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000137762ea5c80033aaf17570451b15a062feedde810f11ebdbe9a79a3275dc12613e0505835c122bd5f9afea7dba84203000000000000000000000000000000000d89c04e45e60769a63fcd73df2a138c457bb549195f2c4eebb3be1ea46149f286756795be8328b5b886f497d8167b34000000000000000000000000000000000be43d515083c8c10f467618685a43d4d5f6457204bacd278445943a9f44f7189b561a0e1bc59d2757fcfab2e3f93a4a0000000000000000000000000000000011a52583227c6dcdc1784d3633fd584612a9f3bbc1922477396dcd5af84413e5e9382a34a71b3a72491ea09fab2fc6bf366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e8346729000000000000000000000000000000000073acefe33525dd2d5204cce72371ed82c7e4b58d1b4e7f4b4994f9c58b02d9d6206fefb3552446b6b355e860ace43c0000000000000000000000000000000007344eaeaae71e17930e769e02bcb4f44ddf3d040ffa0b081f25901cc125a37a58a6a5d13e7b0ba493802ccdaa054e29000000000000000000000000000000000a65fec6ad29ec3eee9ddc7ded2297f49d03ff18a255f1e6d29d2a67c20713f319d79d513af0c58ae3cddfd1f6240ff50000000000000000000000000000000019d5f00d9e2b271f4e9ac779a096386f08ae124f77fb8183405d48ea7f16e685805442dc67a392aefc643ea95b4f1fcfd997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f50000000000000000000000000000000018230bf1a873aa04855af1426da30f1b3ef4b64eec613b9f660222e3827b325c318baea031b463c7e9f775165d22ec8f00000000000000000000000000000000017faafa1294fac53e1de8cae9601acc62d76a5f01a39ce49d65f3f5d2cd5cca33eb90bb4116b3ea36f912ae2b81b6cf000000000000000000000000000000000fc3ef5ea59849a87fcd45500989f1744cb5570ee88e34a952cec32cea2eb5900b64d8d0d04ef5c51e8fdcccd46412490000000000000000000000000000000001c53aa8aaae8422fa4fddc86cacdefa89c37592c8e67e472a23627514623a90901a619af79e93561a0dc65215837274e881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a3371000000000000000000000000000000000e3db6885c2db9244548e11b8c49b73f85e4104b413f54308497262fdff1957495859830114528a22c45d39a554ba82700000000000000000000000000000000181b1bfe2d9a1c563e73356d73f4ed3e7061a79c610bc97c911ab1a0213d123c9f83ed6706e862087a796ce14c5cf53d0000000000000000000000000000000013f5fdceddce771588869b945bd6025e5ce485fe78a362356720b474b83998f27e535cfd8d33ee51cfc68e5d514f915c0000000000000000000000000000000007e8fd7ba457a3cefd50c641847425cf2262deb1d6945a0bd740eadf38dcaa616edc48c3912508d663349f089b8b56fadcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e650000000000000000000000000000000004b20b0408da7b704694b47607928a655077015f2174fe01bac9a0b3a61dae087b0b593f58d2947d8d84f75bbfb327c900000000000000000000000000000000106d623b2007c5d7128e03e540325ba763e992a651e2e5c78936f82ee2ff72d89a1a914345486cd0a04440c75beb190b000000000000000000000000000000001847348e5ef429cfdf1ba4d265d8c5ebcbec3d5dd4611ba36e2754fbd3d327273bf2eb7b7ba4b3888d059dc87f034739000000000000000000000000000000000bcb0a9dfe5189bc965e9721407b4cb3ed4171510aa4d4e5d5f0823a1c2827643e1278f9c0ee960c54ef8f6c208eee7b334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638,000000000000000000000000000000001205b70b29ee04212589f8a70a71e004f517d3354e714c1b4fe42cf93faf1a8ed40dbc1b5089ddb53bb052c9cb74c0e8000000000000000000000000000000000f619082734dd9de653b61cf2fb927199f228637db70797bd2a21fdd48b6ecd4c4f712097037534024880a436fdd63680000000000000000000000000000000000592eca560be6ae256abe1796f7ec394a8085c127437f6590c8d41501a482c61456392cb320b9e801044dcba7802df9000000000000000000000000000000000a6d20b8009708ca01a274aed6dece732c5eed5aae5e4c2f3793b5fa1f8cb8c95037ce387bda2e7476e9c493507c7fbc +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/multiexp_g1_error.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/multiexp_g1_error.csv new file mode 100644 index 00000000000..9b9bcf14002 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/multiexp_g1_error.csv @@ -0,0 +1,101 @@ +input,result +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fefb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed34d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f2770973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e54c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d48964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f91787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1caaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442bdac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc796bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a191fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac944b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870f3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276cdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876827010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76994c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931eb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d607f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debfbb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb7e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154cd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4b06bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac7192a2a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760dead0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff337064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f3686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c33176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd2d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfd9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da15061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e054f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ff0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc9915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff18d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d40c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9b0f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c4346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8dc39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218092c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e1c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dccd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6441776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18033fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f1e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5fc26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d7abba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e821705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81159f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00cce807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfeaa7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193bb473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173823a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119c0f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b39431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0c2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751bb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9278176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c059c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29472ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3185fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae938a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b171d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91eddbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c6807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f67a830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f8a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d345111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c855320c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787a29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12b63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567481b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc4ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8b0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa4ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c98586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2126e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470b85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3c5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0e535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d516e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad52a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9252bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f7a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886333e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae9c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f5a4228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c830a417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab546561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c3cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a7f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951f40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefcae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35861268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728cf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed66070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d23d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5486041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" +000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b67cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79" diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/multiexp_g2_error.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/multiexp_g2_error.csv new file mode 100644 index 00000000000..27efc34b2ac --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/multiexp_g2_error.csv @@ -0,0 +1,101 @@ +input,result +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef577000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cda00000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e81000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877e0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad51a000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d32000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f400000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d20000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c79a000000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4100000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c100000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b670000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4ce0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5c000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9400000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24e00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0e00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647deb00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718b000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd560000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95102000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89fa000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c50000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2a00000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686250000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29ea000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c500000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e280000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595c000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb410000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154e0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25b0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10f0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98700000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52200000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde900000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc57040000000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776d000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a4000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066d00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01c0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180541000000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a775000000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65b0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489600000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf3000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24620000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade350000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86c000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032f000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563e000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26330000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88b00000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864140000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5c00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154695000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50c0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c55000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a2000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4800000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4400000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571f0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b400000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351027000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e040000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedcb000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e674050000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a20000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2d00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c6143a000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e300000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760237000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5b0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61e000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4f0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272c000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74286000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2b000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652f0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614aa000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110d0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dd000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48b0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7460000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c1000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892910000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ac0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fd0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3740000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb1000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bd0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad2000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a100000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c90000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135c0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2b0000000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a9000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5caa000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148517000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" +0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f030000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,"invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176" diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/pairing.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/pairing.csv new file mode 100644 index 00000000000..522fd202ab9 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/pairing.csv @@ -0,0 +1,97 @@ +input,result +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed200000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f9000000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac94300000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e0000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f900000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a6700000000000000000000000000000000018c690fc5571f69793ec3c82915ac9513726ec891312f4f11dd3ba0ee95cc98b50d925099b0642e58a106e8456bbcbed0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae70000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e88000000000000000000000000000000001129c94e0c06f51f1f808a62e42a5449dd159289c14a09c4cc382c91bcfe878b6f3555767c310de1b1c275eb3d17bcf5000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a79157800000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000f46a7dee0cb471ddef3a50670a5bfc443b8455877f1ff602b21faff1eff07fc6bf27930ca2159f01479359548339560000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b090000000000000000000000000000000011dbce34af6cb14d3e4d49f2482e1b058609f25dca79e2ca48e289ace9d1c8db177b7bc35daad79aa5fdac77728bd5fc0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd6700000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000108248cdc6f503c7bad30eac875d92a75feccbdbe7b5394f8557a92bb12a796430a2c60ca23c6ecd259e5b01e53891820000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed30250000000000000000000000000000000006f1fe4d98675ffc62d4d5dd0af9968faca4d0ef425d56fa31b80aea892820e270f6da17aec9eb83c6cc9f84289ecbff000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff32000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e705800000000000000000000000000000000091ce9e6c4bab3ad3d275cf5888387646c3d9bb53ace7bd0c118fce3e44fcd96241b58e5af53978272f56d04b7d7ab79000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000b4322c2fb5d5dd2c65b45f74ca75002c97444d8d4e5680d0369d32d180c93b294e30ef42f21ac274f77ad89633ab1b9000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c200000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b100000000000000000000000000000000081162fe2e65a642993ba283df9bc8d60bfe495b2dc5623f0467c87bc7570980be2654c8cc8838fb362c677f3a3cb1e900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e272839000000000000000000000000000000001324e51f295ea95adedc9730dac2e1ab6d85838840e3955103d76677ce9a7359215f8d954286950bed1be3bbfebabeda0000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b00000000000000000000000000000000132d85236d655190323279a01acc8cbc6fb736d951e3999589f0559cb61ea93e2e1c526ed08ef08046c3d7a40d7cfdaf00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da0000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a60000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000a4ec4acf91be994fe45f08dbeb2bbd053275861d11a486693fd6e5bfa3736134396f6b1c3ad146642f2e972ba609d0b000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b592947030000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe9760000000000000000000000000000000011dc30871a7a9b33e2882f6b24ccd192aad215edfc6c6bd9acd064dff4a43f41b4c212068875e896daf127547bbeca58000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000dd1137e592119c134103b9e29e4f14b48387a767d4effae44f807e5b579e7f9c2f60105495f070d0a6ab2410f62844d00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad62420000000000000000000000000000000016ad3b981f689f51f46e3e5e166986e4e71655dcc1e928327751ffdcfff8934caec5cc37327b3320202c4efbcfda6ae3000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000cbcd06a1c576af16d0d77ff8bcc3669a486d044cc7b85db03661a92f4c5c44a28d028521dfcfc292d8ecd05aed6ab940000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f00000000000000000000000000000000153ba4ab4fecc724c843b8f78db2db1943e91051b8cb9be2eb7e610a570f1f5925b7981334951b505cce1a3992ff05c9000000000000000000000000000000000c1e79925e9ebfd99e5d11489c56a994e0f855a759f0652cc9bb5151877cfea5c37896f56b949167b9cd2226f14333dd,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c81292925400000000000000000000000000000000065d3d4be1589a6f00c85c208c44916a35349a096b09219704b4c31861ef58e6b4ea5be57a175b429e482b1038718d6c000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000b8bf51e6509e81b70ff44d6e0df8f9f7bc8e33126e9f1b5a8419414878a2209c05df56cf590c8e33f484587f4a1f6950000000000000000000000000000000001c07a85ece4a1c5072fe722fb264bd1d3aaabf9db9809d5a6cb3fe17157d8fd1bfa730a26e34c87279ea81abe141fe6,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda00000000000000000000000000000000055caff20f9f3f2ea27c64caeb6bb1880f326c64eb6ed6ee7d15da7bfeb16518f76a75f061cd347f7322e0cec634f0fc000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000000197b17c5b695db49c7c8b6fd6f314da7cfdfc4dbe61c76475839c89064870fad5104234c09aee7bafc1660263b6959e0000000000000000000000000000000019e514b65a358640ea90cd3cf547d591f5ff1a13ad99c568ef70c558cbec26dd1e582cc92d849fcfce9749068d361372,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000003c0c619be1382199bee84862a0ac6bf4c891d22f722b6af5bfef0edd1ed8c7e9af5efb5d3fc546801f3e019329ae4e80000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa5200000000000000000000000000000000173bdb0d0a79f1f2607867ddbf2581b4c1cc20fc0bced60ed8756aa4e79cb87c47fa722b1c8fdbe99a8208fc532327b600000000000000000000000000000000172f37eac49dd7f09adf602ebe562e5d0bd52ee2419fc08dc7fda241c0319b3f43b3ec79ab5aac246a783f13e268d4bb,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a030000000000000000000000000000000014c22dcacf2e21ff447c94d81067c626b1217e58b7dc98aacab2ea3fc00b1c5e66f660d19f1c69b16571e49d13c8e1d0000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec35000000000000000000000000000000000125ffac343f97afc413ae80d40a309dfe461fe6b20eb84f8eec3a2718ec2c9f1273bcba2fa1ca59fdc924e471173d68000000000000000000000000000000000ebfaf78e267fd93f0e35e0d19feae9db125660a3dde99b028be77c6fac0116a4808aab02a29063c3c0bc4476924d04f,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000010338047b7c67c122ffb13466935623ef2338b32bbf5452f78f7abe9a13a16824c11f5520c9dac256b9d257da88d92a30000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000a96371995c5333949b5e9869599fcb67222c2e44447d133e9b3e18a9e9e14a92ee03dcba86832cde7d35b35a88bcd240000000000000000000000000000000003a912710b425cc477c43ff93684249649732b1e99270bba725e990559169b505e8411fba174b6a15f90d5b3962f5399,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000008418a39124b40643dddcd6afe1dbdf930303bca65226c2fee1b95de6e080e25451f8b4f2b2b7c4633e1de6a5a7d47cb000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c000000000000000000000000000000001535bce9acb48b6ebc4d3dbb7c236d7cc57d656210e42e9379d4f528fc0ba59bf868503d3bb8e5cd3dafdd881ec56d5c00000000000000000000000000000000037033062d13644c88c317f1c58c18e0d9b4facbbe0701ac8bbdf3c7f0c37b14034c7882d5112a94bea39dfdbfc518e7,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000ada13f88a645bc6082c6321e0cf2b7ac45c633fe2f0cb36aeb187fe2e50e7510df2a86b98979e8551636e94488c8ce000000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000002f98cb2305518737e92ee72e2c48d0dbdbbb1f2dc63b9d02d1a8c27dd1ba5a071dc72a8dcc7813b5757bc244357f6630000000000000000000000000000000017775ae72405e39e2db32d5b9db6637b7bbb9799e614bd783f0b785c3f2ee815367e67b15cc037fec8252735f36c28cf,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a3700000000000000000000000000000000162e49ebd1b50c7837336509e48ace0e7856f00ec45a76b96d1dd88eea300a8118357cafabf32ee2d06b601def523e4800000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b6000000000000000000000000000000001575d39e959d14b96f261265417ca949a248c3d46e2abb093541c103dadf58cd5b21e28c79f17b376070799492457358000000000000000000000000000000001240585d5f4c28467bccb5193e4aad78ea3b1d8dfb4716a3310fb5215a478aac3f05a8ed478486c9e703a59b9c32bfe5,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000efe47bf2b11dd10608a309c8aaefdbcbc2bb5e6adceef375371cface8f79668e2b7c2ce9990063a3b53e419e80e2a79000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba00000000000000000000000000000000088ea99f17bb06ba20c5c67aeb8fbbd19b2270986ee7c6517209679e6f84f5d43fa22daf6264c993f1d048d016e3b39e0000000000000000000000000000000008af6330f5638c0a9f339f4e8d841b955e322766457112039b2a852b37d3bc45efb67aeb216a09a8940f5e4e1a771da8,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c1872290000000000000000000000000000000001099fe889d8f5ddcad09328997c7c3098ef4b4d74ab1d9f6fcbc33a03cafb59c7b28931da67950d1389fbcedca3fb5bb00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e00000000000000000000000000000000006b5813a1c1f934e8e564a7cad93dc7f57de7a9592aedeb116fa4ed6bc6452bbc0da23be10adfea4ad4fa82969e7a150000000000000000000000000000000008e760ad89fd250a9d5041ec81e51b8b66f5265037e7237f7c4a08bb83e7799f352c54c37cf70a6c61bb95bfbf8a616e,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a9000000000000000000000000000000001750d2e78525453f113b76f18abf2334de9755c03786fbc9233cda2364d57ed493f4fe6c2b565f4d82ff8113e9b63c4d0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000018446c6ba126e030ed071f87189cbd618627419c82065d26044759f6e4c7257f45021dfad1dcb34dd06b4e391329a61f00000000000000000000000000000000136b60cd7658a5d135d4bc38edff042570c7824245ed9f7a6414e9e7ab3840a99700fb620e809891b66003340db29e64,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb0000000000000000000000000000000001a28b7856a22db6e79ac4165e60addbb7dfe1f19d815032bc68fea905bd0d7709c2dafc65fe51493c964de678a30d32000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb877660000000000000000000000000000000006a5d436046e0ac1975e4d24bb3e3f35c1ba3801f37705505cdeb6a86c58bf8068b43462a55155799fe2d2cc60c398b900000000000000000000000000000000191fde77c7c2b397a950f0542d2edd183a5e9404e516146697a755561ab2a9705f970b491e4c0003657d864258f391ec,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e270000000000000000000000000000000007c17aaf82c2aa6bf01695157bcd0c2b34734dfbd572b0abe79c8dd3eef7ce6eb9c5e7de55b36ddf87f05e55ba9ac28b00000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df700000000000000000000000000000000073b61e6c2de0969138ce3671582c9b58305c5abefb54cfe13eb3284c2be04d26c906c717fd29aaf60b485e18de8e4fb0000000000000000000000000000000006297267dd3b6f3de2329e837302427ab6235b3ad4a9f8c6bb45795852d3c3c61fe75747bbc78043102fc3f646f5d1f9,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000017147eda83f35d0b6c8894317da5b2e991818479674d7dd1aef6bfaebacbb61ad4b2a17ce7e799939f8c2004af4799530000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c74390000000000000000000000000000000009e2fdaeb5f35c5aeb9aaca231439c45ac022875d55575cbf25c15cb6177c6b67416ad22fa7e7cb1924d4c2501f98bd80000000000000000000000000000000012dcaeaa2e415f46b579d9e325d7d7c3cd94083d25fe38c872f1907bbb741aff660d28bb663edd502444e11d2d60d8f0,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f10000000000000000000000000000000016c1c9ca735535f801c58a9e35a80ce122d20abb327b44db4dea31b899982c4e136a2430c51cf3a31adc5611621f9dde000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000b49e02d9fb238a258f3a4307b6a2f64912b7fa91712b5639de24e90c09f9797654e0f7e2d31e968c040b867de03cd370000000000000000000000000000000005c56a16431ba175ad81260faeac87d8238f86b2828b0e74dbb0b296b34745ac17e6b684a25a16240183619c96b986bd,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf20000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf2,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e500000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e5,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae850000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae85,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c50000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c5,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e790000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e79,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa4820000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa482,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3,0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c,0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88,0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb963,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a,0000000000000000000000000000000000000000000000000000000000000001 +00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e6,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb,0000000000000000000000000000000000000000000000000000000000000001 +0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a7,0000000000000000000000000000000000000000000000000000000000000001 +000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5,0000000000000000000000000000000000000000000000000000000000000001 diff --git a/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/pairing_error.csv b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/pairing_error.csv new file mode 100644 index 00000000000..63ecf6a2996 --- /dev/null +++ b/runtime/near-vm-runner/src/logic/tests/bls12381_test_vectors/pairing_error.csv @@ -0,0 +1,101 @@ +input,result +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" diff --git a/runtime/near-vm-runner/src/logic/tests/gas_counter.rs b/runtime/near-vm-runner/src/logic/tests/gas_counter.rs index 220063e52b4..dd4dabec7e8 100644 --- a/runtime/near-vm-runner/src/logic/tests/gas_counter.rs +++ b/runtime/near-vm-runner/src/logic/tests/gas_counter.rs @@ -274,7 +274,7 @@ fn check_action_gas_exceeds_limit( /// the arguments. /// /// This case is more interesting because the burnt gas can be below used gas, -/// when the prepaid gas was exceeded by burnt burnt + promised gas but not by +/// when the prepaid gas was exceeded by burnt + promised gas but not by /// burnt gas alone. /// /// Consequently, `num_action_paid` here is even more important to calculate diff --git a/runtime/near-vm-runner/src/logic/tests/mod.rs b/runtime/near-vm-runner/src/logic/tests/mod.rs index 4c1f3fd4ccf..21468f7a517 100644 --- a/runtime/near-vm-runner/src/logic/tests/mod.rs +++ b/runtime/near-vm-runner/src/logic/tests/mod.rs @@ -1,4 +1,6 @@ mod alt_bn128; +#[cfg(feature = "protocol_feature_bls12381")] +mod bls12381; mod context; mod ed25519_verify; mod gas_counter; diff --git a/runtime/near-vm-runner/src/logic/tests/promises.rs b/runtime/near-vm-runner/src/logic/tests/promises.rs index 1dc0d2540c0..ee8eaca5a13 100644 --- a/runtime/near-vm-runner/src/logic/tests/promises.rs +++ b/runtime/near-vm-runner/src/logic/tests/promises.rs @@ -12,14 +12,14 @@ fn vm_receipts<'a>(ext: &'a MockedExternal) -> Vec { #[test] fn test_promise_results() { - let promise_results = vec![ + let promise_results = [ PromiseResult::Successful(b"test".to_vec()), PromiseResult::Failed, PromiseResult::NotReady, ]; let mut logic_builder = VMLogicBuilder::default(); - logic_builder.promise_results = promise_results; + logic_builder.context.promise_results = promise_results.into(); let mut logic = logic_builder.build(); assert_eq!(logic.promise_results_count(), Ok(3), "Total count of registers must be 3"); diff --git a/runtime/near-vm-runner/src/logic/tests/vm_logic_builder.rs b/runtime/near-vm-runner/src/logic/tests/vm_logic_builder.rs index b3358b1fa6b..23dfd2055c4 100644 --- a/runtime/near-vm-runner/src/logic/tests/vm_logic_builder.rs +++ b/runtime/near-vm-runner/src/logic/tests/vm_logic_builder.rs @@ -1,15 +1,14 @@ use crate::logic::mocks::mock_external::MockedExternal; use crate::logic::mocks::mock_memory::MockedMemory; -use crate::logic::types::PromiseResult; -use crate::logic::{Config, MemSlice, VMContext, VMLogic}; +use crate::logic::{Config, ExecutionResultState, MemSlice, VMContext, VMLogic}; use crate::tests::test_vm_config; use near_parameters::RuntimeFeesConfig; +use std::sync::Arc; pub(super) struct VMLogicBuilder { pub ext: MockedExternal, pub config: Config, pub fees_config: RuntimeFeesConfig, - pub promise_results: Vec, pub memory: MockedMemory, pub context: VMContext, } @@ -21,7 +20,6 @@ impl Default for VMLogicBuilder { fees_config: RuntimeFeesConfig::test(), ext: MockedExternal::default(), memory: MockedMemory::default(), - promise_results: vec![], context: get_context(), } } @@ -37,13 +35,13 @@ impl VMLogicBuilder { } pub fn build(&mut self) -> TestVMLogic<'_> { + let result_state = ExecutionResultState::new(&self.context, Arc::new(self.config.clone())); TestVMLogic::from(VMLogic::new( &mut self.ext, &self.context, - &self.config, - &self.fees_config, - &self.promise_results, - &mut self.memory, + Arc::new(self.fees_config.clone()), + result_state, + self.memory.clone(), )) } @@ -57,7 +55,6 @@ impl VMLogicBuilder { fees_config: RuntimeFeesConfig::free(), ext: MockedExternal::default(), memory: MockedMemory::default(), - promise_results: vec![], context: get_context(), } } @@ -69,7 +66,9 @@ fn get_context() -> VMContext { signer_account_id: "bob.near".parse().unwrap(), signer_account_pk: vec![0, 1, 2, 3, 4], predecessor_account_id: "carol.near".parse().unwrap(), + method: "VMLogicBuilder::method_not_specified".into(), input: vec![0, 1, 2, 3, 4], + promise_results: vec![].into(), block_height: 10, block_timestamp: 42, epoch_height: 1, @@ -153,6 +152,6 @@ impl TestVMLogic<'_> { } pub fn compute_outcome(self) -> crate::logic::VMOutcome { - self.logic.compute_outcome() + self.logic.result_state.compute_outcome() } } diff --git a/runtime/near-vm-runner/src/logic/vmstate.rs b/runtime/near-vm-runner/src/logic/vmstate.rs index a1ac607f697..bd72ff0f7f7 100644 --- a/runtime/near-vm-runner/src/logic/vmstate.rs +++ b/runtime/near-vm-runner/src/logic/vmstate.rs @@ -19,7 +19,7 @@ type Result = ::std::result::Result; /// the compiler can deconstruct the access to each field of [`VMLogic`] and do /// more granular lifetime analysis. In particular, this design is what allows /// us to forgo copying register value in [`VMLogic::read_register`]. -pub(super) struct Memory<'a>(&'a mut dyn MemoryLike); +pub(super) struct Memory(Box); macro_rules! memory_get { ($_type:ty, $name:ident) => { @@ -48,9 +48,9 @@ macro_rules! memory_set { }; } -impl<'a> Memory<'a> { - pub(super) fn new(mem: &'a mut dyn MemoryLike) -> Self { - Self(mem) +impl Memory { + pub(super) fn new(mem: impl MemoryLike + 'static) -> Self { + Self(Box::new(mem)) } /// Returns view of the guest memory. @@ -237,13 +237,13 @@ impl Registers { /// of gas counter, memory and registers separately. This allows `VMLogic` to /// borrow value from a register and then continue constructing mutable /// references to other fields in the structure.. -pub(super) fn get_memory_or_register<'a, 'b>( +pub(super) fn get_memory_or_register<'a>( gas_counter: &mut GasCounter, - memory: &'b Memory<'a>, - registers: &'b Registers, + memory: &'a Memory, + registers: &'a Registers, ptr: u64, len: u64, -) -> Result> { +) -> Result> { if len == u64::MAX { registers.get(gas_counter, ptr).map(Cow::Borrowed) } else { diff --git a/runtime/near-vm-runner/src/memory.rs b/runtime/near-vm-runner/src/memory.rs deleted file mode 100644 index 3b80b2316b0..00000000000 --- a/runtime/near-vm-runner/src/memory.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::logic::{MemSlice, MemoryLike}; - -use std::borrow::Cow; - -use wasmer_runtime::units::Pages; -use wasmer_runtime::wasm::MemoryDescriptor; -use wasmer_runtime::Memory; - -pub struct WasmerMemory(Memory); - -impl WasmerMemory { - pub fn new(initial_memory_pages: u32, max_memory_pages: u32) -> Self { - WasmerMemory( - Memory::new( - MemoryDescriptor::new( - Pages(initial_memory_pages), - Some(Pages(max_memory_pages)), - false, - ) - .unwrap(), - ) - .expect("TODO creating memory cannot fail"), - ) - } - - pub fn clone(&self) -> Memory { - self.0.clone() - } -} - -impl WasmerMemory { - fn with_memory(&self, offset: u64, len: usize, func: F) -> Result - where - F: FnOnce(core::slice::Iter<'_, std::cell::Cell>) -> T, - { - let start = usize::try_from(offset).map_err(|_| ())?; - let end = start.checked_add(len).ok_or(())?; - self.0.view().get(start..end).map(|mem| func(mem.iter())).ok_or(()) - } -} - -impl MemoryLike for WasmerMemory { - fn fits_memory(&self, slice: MemSlice) -> Result<(), ()> { - self.with_memory(slice.ptr, slice.len()?, |_| ()) - } - - fn view_memory(&self, slice: MemSlice) -> Result, ()> { - self.with_memory(slice.ptr, slice.len()?, |mem| { - Cow::Owned(mem.map(core::cell::Cell::get).collect()) - }) - } - - fn read_memory(&self, offset: u64, buffer: &mut [u8]) -> Result<(), ()> { - self.with_memory(offset, buffer.len(), |mem| { - buffer.iter_mut().zip(mem).for_each(|(dst, src)| *dst = src.get()); - }) - } - - fn write_memory(&mut self, offset: u64, buffer: &[u8]) -> Result<(), ()> { - self.with_memory(offset, buffer.len(), |mem| { - mem.zip(buffer.iter()).for_each(|(dst, src)| dst.set(*src)); - }) - } -} - -#[test] -fn test_memory_like() { - crate::logic::test_utils::test_memory_like(|| Box::new(WasmerMemory::new(1, 1))); -} diff --git a/runtime/near-vm-runner/src/near_vm_runner/runner.rs b/runtime/near-vm-runner/src/near_vm_runner/runner.rs index 4cdbce60f19..aa8738ae292 100644 --- a/runtime/near-vm-runner/src/near_vm_runner/runner.rs +++ b/runtime/near-vm-runner/src/near_vm_runner/runner.rs @@ -1,13 +1,11 @@ use super::{NearVmMemory, VM_CONFIG}; use crate::cache::CompiledContractInfo; use crate::errors::ContractPrecompilatonResult; -use crate::imports::near_vm::NearVmImports; use crate::logic::errors::{ CacheError, CompilationError, FunctionCallError, MethodResolveError, VMRunnerError, WasmTrap, }; use crate::logic::gas_counter::FastGasCounter; -use crate::logic::types::PromiseResult; -use crate::logic::{Config, External, VMContext, VMLogic, VMOutcome}; +use crate::logic::{Config, ExecutionResultState, External, VMContext, VMLogic, VMOutcome}; use crate::near_vm_runner::{NearVmCompiler, NearVmEngine}; use crate::runner::VMResult; use crate::{ @@ -17,7 +15,6 @@ use crate::{prepare, NoContractRuntimeCache}; use memoffset::offset_of; use near_parameters::vm::VMKind; use near_parameters::RuntimeFeesConfig; -use near_primitives_core::hash::CryptoHash; use near_vm_compiler_singlepass::Singlepass; use near_vm_engine::universal::{ MemoryPool, Universal, UniversalArtifact, UniversalEngine, UniversalExecutable, @@ -25,7 +22,8 @@ use near_vm_engine::universal::{ }; use near_vm_types::{FunctionIndex, InstanceConfig, MemoryType, Pages, WASM_PAGE_SIZE}; use near_vm_vm::{ - Artifact, Instantiatable, LinearMemory, LinearTable, MemoryStyle, TrapCode, VMMemory, + Artifact, ExportFunction, ExportFunctionMetadata, Instantiatable, LinearMemory, LinearTable, + MemoryStyle, Resolver, TrapCode, VMFunction, VMFunctionKind, VMMemory, }; use std::mem::size_of; use std::sync::{Arc, OnceLock}; @@ -94,12 +92,12 @@ fn translate_runtime_error( } pub(crate) struct NearVM { - pub(crate) config: Config, + pub(crate) config: Arc, pub(crate) engine: UniversalEngine, } impl NearVM { - pub(crate) fn new_for_target(config: Config, target: near_vm_compiler::Target) -> Self { + pub(crate) fn new_for_target(config: Arc, target: near_vm_compiler::Target) -> Self { // We only support singlepass compiler at the moment. assert_eq!(VM_CONFIG.compiler, NearVmCompiler::Singlepass); let mut compiler = Singlepass::new(); @@ -138,7 +136,7 @@ impl NearVM { } } - pub(crate) fn new(config: Config) -> Self { + pub(crate) fn new(config: Arc) -> Self { use near_vm_compiler::{CpuFeature, Target, Triple}; let target_features = if cfg!(feature = "no_cpu_compatibility_checks") { let mut fs = CpuFeature::set(); @@ -174,7 +172,7 @@ impl NearVM { .engine .compile_universal(&prepared_code, &self) .map_err(|err| { - tracing::error!(?err, "near_vm failed to compile the prepared code (this is defense-in-depth, the error was recovered from but should be reported to pagoda)"); + tracing::error!(?err, "near_vm failed to compile the prepared code (this is defense-in-depth, the error was recovered from but should be reported to the developers)"); CompilationError::WasmerCompileError { msg: err.to_string() } })?; crate::metrics::compilation_duration(VMKind::NearVm, start.elapsed()); @@ -211,81 +209,41 @@ impl NearVM { skip_all )] fn with_compiled_and_loaded( - &self, - code_hash: CryptoHash, - code: Option<&ContractCode>, + self: Box, cache: &dyn ContractRuntimeCache, - ext: &mut dyn External, + ext: &dyn External, context: &VMContext, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], - method_name: &str, - closure: impl FnOnce(VMMemory, VMLogic<'_>, &VMArtifact) -> Result, - ) -> VMResult { + closure: impl FnOnce(ExecutionResultState, &VMArtifact, Box) -> VMResult, + ) -> VMResult { // (wasm code size, compilation result) type MemoryCacheType = (u64, Result); let to_any = |v: MemoryCacheType| -> Box { Box::new(v) }; // To identify a cache hit from either in-memory and on-disk cache correctly, we first assume that we have a cache hit here, // and then we set it to false when we fail to find any entry and decide to compile (by calling compile_and_cache below). let mut is_cache_hit = true; + let code_hash = ext.code_hash(); let (wasm_bytes, artifact_result) = cache.memory_cache().try_lookup( code_hash, - || match code { - None => { - // `cache` stores compiled machine code in the database - // - // Caches also cache _compilation_ errors, so that we don't have to - // re-parse invalid code (invalid code, in a sense, is a normal - // outcome). And `cache`, being a database, can fail with an `io::Error`. - let _span = - tracing::debug_span!(target: "vm", "NearVM::fetch_from_cache").entered(); - let key = get_contract_cache_key(code_hash, &self.config); - let cache_record = cache.get(&key).map_err(CacheError::ReadError)?; - let Some(code) = cache_record else { - return Err(VMRunnerError::CacheError(CacheError::ReadError( - std::io::Error::from(std::io::ErrorKind::NotFound), - ))); + || { + // `cache` stores compiled machine code in the database + // + // Caches also cache _compilation_ errors, so that we don't have to + // re-parse invalid code (invalid code, in a sense, is a normal + // outcome). And `cache`, being a database, can fail with an `io::Error`. + let _span = + tracing::debug_span!(target: "vm", "NearVM::fetch_from_cache").entered(); + let key = get_contract_cache_key(code_hash, &self.config); + let cache_record = cache.get(&key).map_err(CacheError::ReadError)?; + let Some(compiled_contract_info) = cache_record else { + let Some(code) = ext.get_contract() else { + return Err(VMRunnerError::ContractCodeNotPresent); }; - - match &code.compiled { - CompiledContract::CompileModuleError(err) => { - Ok::<_, VMRunnerError>(to_any((code.wasm_bytes, Err(err.clone())))) - } - CompiledContract::Code(serialized_module) => { - let _span = - tracing::debug_span!(target: "vm", "NearVM::load_from_fs_cache") - .entered(); - unsafe { - // (UN-)SAFETY: the `serialized_module` must have been produced by - // a prior call to `serialize`. - // - // In practice this is not necessarily true. One could have - // forgotten to change the cache key when upgrading the version of - // the near_vm library or the database could have had its data - // corrupted while at rest. - // - // There should definitely be some validation in near_vm to ensure - // we load what we think we load. - let executable = - UniversalExecutableRef::deserialize(&serialized_module) - .map_err(|_| CacheError::DeserializationError)?; - let artifact = self - .engine - .load_universal_executable_ref(&executable) - .map(Arc::new) - .map_err(|err| VMRunnerError::LoadingError(err.to_string()))?; - Ok(to_any((code.wasm_bytes, Ok(artifact)))) - } - } - } - } - Some(code) => { let _span = tracing::debug_span!(target: "vm", "NearVM::build_from_source").entered(); is_cache_hit = false; - Ok(to_any(( + return Ok(to_any(( code.code().len() as u64, - match self.compile_and_cache(code, cache)? { + match self.compile_and_cache(&code, cache)? { Ok(executable) => Ok(self .engine .load_universal_executable(&executable) @@ -293,7 +251,40 @@ impl NearVM { .map_err(|err| VMRunnerError::LoadingError(err.to_string()))?), Err(err) => Err(err), }, - ))) + ))); + }; + + match &compiled_contract_info.compiled { + CompiledContract::CompileModuleError(err) => Ok::<_, VMRunnerError>(to_any(( + compiled_contract_info.wasm_bytes, + Err(err.clone()), + ))), + CompiledContract::Code(serialized_module) => { + let _span = + tracing::debug_span!(target: "vm", "NearVM::load_from_fs_cache") + .entered(); + unsafe { + // (UN-)SAFETY: the `serialized_module` must have been produced by + // a prior call to `serialize`. + // + // In practice this is not necessarily true. One could have + // forgotten to change the cache key when upgrading the version of + // the near_vm library or the database could have had its data + // corrupted while at rest. + // + // There should definitely be some validation in near_vm to ensure + // we load what we think we load. + let executable = + UniversalExecutableRef::deserialize(&serialized_module) + .map_err(|_| CacheError::DeserializationError)?; + let artifact = self + .engine + .load_universal_executable_ref(&executable) + .map(Arc::new) + .map_err(|err| VMRunnerError::LoadingError(err.to_string()))?; + Ok(to_any((compiled_contract_info.wasm_bytes, Ok(artifact)))) + } + } } }, move |value| { @@ -309,30 +300,23 @@ impl NearVM { crate::metrics::record_compiled_contract_cache_lookup(is_cache_hit); - let mut memory = NearVmMemory::new( - self.config.limit_config.initial_memory_pages, - self.config.limit_config.max_memory_pages, - ) - .expect("Cannot create memory for a contract call"); - // FIXME: this mostly duplicates the `run_module` method. - // Note that we don't clone the actual backing memory, just increase the RC. - let vmmemory = memory.vm(); - let mut logic = - VMLogic::new(ext, context, &self.config, fees_config, promise_results, &mut memory); - - let result = logic.before_loading_executable(method_name, wasm_bytes); + let mut result_state = ExecutionResultState::new(&context, Arc::clone(&self.config)); + let result = result_state.before_loading_executable(&context.method, wasm_bytes); if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); + return Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e))); } match artifact_result { Ok(artifact) => { - let result = logic.after_loading_executable(wasm_bytes); + let result = result_state.after_loading_executable(wasm_bytes); if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); + return Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e))); } - closure(vmmemory, logic, &artifact) + closure(result_state, &artifact, self) } - Err(e) => Ok(VMOutcome::abort(logic, FunctionCallError::CompilationError(e))), + Err(e) => Ok(PreparedContract::Outcome(VMOutcome::abort( + result_state, + FunctionCallError::CompilationError(e), + ))), } } @@ -588,41 +572,37 @@ impl<'a> finite_wasm::wasmparser::VisitOperator<'a> for GasCostCfg { } impl crate::runner::VM for NearVM { - fn run( - &self, - code_hash: CryptoHash, - code: Option<&ContractCode>, - method_name: &str, - ext: &mut dyn External, + fn prepare( + self: Box, + ext: &dyn External, context: &VMContext, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], cache: Option<&dyn ContractRuntimeCache>, - ) -> Result { + ) -> Box { let cache = cache.unwrap_or(&NoContractRuntimeCache); - self.with_compiled_and_loaded( - code_hash, - code, - cache, - ext, - context, - fees_config, - promise_results, - method_name, - |vmmemory, mut logic, artifact| { - let import = imports::near_vm::build(vmmemory, &mut logic, artifact.engine()); - let entrypoint = match get_entrypoint_index(&*artifact, method_name) { + let prepd = + self.with_compiled_and_loaded(cache, ext, context, |result_state, artifact, vm| { + let memory = NearVmMemory::new( + vm.config.limit_config.initial_memory_pages, + vm.config.limit_config.max_memory_pages, + ) + .expect("Cannot create memory for a contract call"); + let entrypoint = match get_entrypoint_index(&*artifact, &context.method) { Ok(index) => index, Err(e) => { - return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol(logic, e)) + return Ok(PreparedContract::Outcome( + VMOutcome::abort_but_nop_outcome_in_old_protocol(result_state, e), + )) } }; - match self.run_method(&artifact, import, entrypoint)? { - Ok(()) => Ok(VMOutcome::ok(logic)), - Err(err) => Ok(VMOutcome::abort(logic, err)), - } - }, - ) + Ok(PreparedContract::Ready(ReadyContract { + memory, + result_state, + entrypoint, + artifact: Arc::clone(artifact), + vm, + })) + }); + Box::new(prepd) } fn precompile( @@ -639,6 +619,180 @@ impl crate::runner::VM for NearVM { } } +struct ReadyContract { + memory: NearVmMemory, + result_state: ExecutionResultState, + entrypoint: FunctionIndex, + artifact: VMArtifact, + vm: Box, +} + +#[allow(clippy::large_enum_variant)] +enum PreparedContract { + Outcome(VMOutcome), + Ready(ReadyContract), +} + +impl crate::PreparedContract for VMResult { + fn run( + self: Box, + ext: &mut dyn External, + context: &VMContext, + fees_config: Arc, + ) -> VMResult { + let ReadyContract { memory, result_state, entrypoint, artifact, vm } = match (*self)? { + PreparedContract::Outcome(outcome) => return Ok(outcome), + PreparedContract::Ready(r) => r, + }; + let config = Arc::clone(&result_state.config); + let vmmemory = memory.vm(); + let mut logic = VMLogic::new(ext, context, fees_config, result_state, memory); + let import = build_imports(vmmemory, &mut logic, config, artifact.engine()); + match vm.run_method(&artifact, import, entrypoint)? { + Ok(()) => Ok(VMOutcome::ok(logic.result_state)), + Err(err) => Ok(VMOutcome::abort(logic.result_state, err)), + } + } +} + +pub(crate) struct NearVmImports<'engine, 'vmlogic, 'vmlogic_refs> { + pub(crate) memory: VMMemory, + config: Arc, + // Note: this same object is also referenced by the `metadata` field! + pub(crate) vmlogic: &'vmlogic mut VMLogic<'vmlogic_refs>, + pub(crate) metadata: Arc, + pub(crate) engine: &'engine UniversalEngine, +} + +trait NearVmType { + type NearVm; + fn to_near_vm(self) -> Self::NearVm; + fn ty() -> near_vm_types::Type; +} +macro_rules! near_vm_types { + ($($native:ty as $near_vm:ty => $type_expr:expr;)*) => { + $(impl NearVmType for $native { + type NearVm = $near_vm; + fn to_near_vm(self) -> $near_vm { + self as _ + } + fn ty() -> near_vm_types::Type { + $type_expr + } + })* + } + } +near_vm_types! { + u32 as i32 => near_vm_types::Type::I32; + u64 as i64 => near_vm_types::Type::I64; +} + +macro_rules! return_ty { + ($return_type: ident = [ ]) => { + type $return_type = (); + fn make_ret() -> () {} + }; + ($return_type: ident = [ $($returns: ident),* ]) => { + #[repr(C)] + struct $return_type($(<$returns as NearVmType>::NearVm),*); + fn make_ret($($returns: $returns),*) -> Ret { Ret($($returns.to_near_vm()),*) } + } + } + +impl<'e, 'l, 'lr> Resolver for NearVmImports<'e, 'l, 'lr> { + fn resolve(&self, _index: u32, module: &str, field: &str) -> Option { + if module == "env" && field == "memory" { + return Some(near_vm_vm::Export::Memory(self.memory.clone())); + } + + macro_rules! add_import { + ( + $mod:ident / $name:ident : $func:ident < + [ $( $arg_name:ident : $arg_type:ident ),* ] + -> [ $( $returns:ident ),* ] + > + ) => { + return_ty!(Ret = [ $($returns),* ]); + + extern "C" fn $name(env: *mut VMLogic<'_>, $( $arg_name: $arg_type ),* ) + -> Ret { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + const TRACE: bool = $crate::imports::should_trace_host_function(stringify!($name)); + let _span = TRACE.then(|| { + tracing::trace_span!(target: "vm::host_function", stringify!($name)).entered() + }); + + // SAFETY: This code should only be executable within `'vmlogic` + // lifetime and so it is safe to dereference the `env` pointer which is + // known to be derived from a valid `&'vmlogic mut VMLogic<'_>` in the + // first place. + unsafe { (*env).$func( $( $arg_name, )* ) } + })); + // We want to ensure that the only kind of error that host function calls + // return are VMLogicError. This is important because we later attempt to + // downcast the `RuntimeError`s into `VMLogicError`. + let result: Result, _> = result; + #[allow(unused_parens)] + match result { + Ok(Ok(($($returns),*))) => make_ret($($returns),*), + Ok(Err(trap)) => unsafe { + // SAFETY: this can only be called by a WASM contract, so all the + // necessary hooks are known to be in place. + near_vm_vm::raise_user_trap(Box::new(trap)) + }, + Err(e) => unsafe { + // SAFETY: this can only be called by a WASM contract, so all the + // necessary hooks are known to be in place. + near_vm_vm::resume_panic(e) + }, + } + } + // TODO: a phf hashmap would probably work better here. + if module == stringify!($mod) && field == stringify!($name) { + let args = [$(<$arg_type as NearVmType>::ty()),*]; + let rets = [$(<$returns as NearVmType>::ty()),*]; + let signature = near_vm_types::FunctionType::new(&args[..], &rets[..]); + let signature = self.engine.register_signature(signature); + return Some(near_vm_vm::Export::Function(ExportFunction { + vm_function: VMFunction { + address: $name as *const _, + // SAFETY: here we erase the lifetime of the `vmlogic` reference, + // but we believe that the lifetimes on `NearVmImports` enforce + // sufficiently that it isn't possible to call this exported + // function when vmlogic is no loger live. + vmctx: near_vm_vm::VMFunctionEnvironment { + host_env: self.vmlogic as *const _ as *mut _ + }, + signature, + kind: VMFunctionKind::Static, + call_trampoline: None, + instance_ref: None, + }, + metadata: Some(Arc::clone(&self.metadata)), + })); + } + }; + } + imports::for_each_available_import!(self.config, add_import); + return None; + } +} + +pub(crate) fn build_imports<'e, 'a, 'b>( + memory: VMMemory, + logic: &'a mut VMLogic<'b>, + config: Arc, + engine: &'e UniversalEngine, +) -> NearVmImports<'e, 'a, 'b> { + let metadata = unsafe { + // SAFETY: the functions here are thread-safe. We ensure that the lifetime of `VMLogic` + // is sufficiently long by tying the lifetime of VMLogic to the return type which + // contains this metadata. + ExportFunctionMetadata::new(logic as *mut _ as *mut _, None, |ptr| ptr, |_| {}) + }; + NearVmImports { memory, config, vmlogic: logic, metadata: Arc::new(metadata), engine } +} + #[cfg(test)] mod tests { #[test] diff --git a/runtime/near-vm-runner/src/prepare/prepare_v2.rs b/runtime/near-vm-runner/src/prepare/prepare_v2.rs index 894dea6fd73..e0f63b20600 100644 --- a/runtime/near-vm-runner/src/prepare/prepare_v2.rs +++ b/runtime/near-vm-runner/src/prepare/prepare_v2.rs @@ -173,8 +173,10 @@ impl<'a> PrepareContext<'a> { self.func_validator_allocations = func_validator.into_allocations(); } wp::Payload::CustomSection(reader) => { - self.ensure_import_section(); - self.copy_section(SectionId::Custom, reader.range())?; + if !self.config.discard_custom_sections { + self.ensure_import_section(); + self.copy_section(SectionId::Custom, reader.range())?; + } } // Extensions not supported. diff --git a/runtime/near-vm-runner/src/profile.rs b/runtime/near-vm-runner/src/profile.rs index e3cdb8dc8d1..270c367d2eb 100644 --- a/runtime/near-vm-runner/src/profile.rs +++ b/runtime/near-vm-runner/src/profile.rs @@ -9,11 +9,11 @@ use strum::IntoEnumIterator; #[derive(Clone, PartialEq, Eq)] pub struct ProfileDataV3 { /// Gas spent on sending or executing actions. - actions_profile: EnumMap, + pub actions_profile: EnumMap, /// Non-action gas spent outside the WASM VM while executing a contract. - wasm_ext_profile: EnumMap, + pub wasm_ext_profile: EnumMap, /// Gas spent on execution inside the WASM VM. - wasm_gas: Gas, + pub wasm_gas: Gas, } impl Default for ProfileDataV3 { diff --git a/runtime/near-vm-runner/src/runner.rs b/runtime/near-vm-runner/src/runner.rs index c71013faad8..3f264658677 100644 --- a/runtime/near-vm-runner/src/runner.rs +++ b/runtime/near-vm-runner/src/runner.rs @@ -1,12 +1,10 @@ use crate::errors::ContractPrecompilatonResult; use crate::logic::errors::{CacheError, CompilationError, VMRunnerError}; -use crate::logic::types::PromiseResult; use crate::logic::{External, VMContext, VMOutcome}; use crate::{ContractCode, ContractRuntimeCache}; use near_parameters::vm::{Config, VMKind}; use near_parameters::RuntimeFeesConfig; -use near_primitives_core::account::Account; -use near_primitives_core::hash::CryptoHash; +use std::sync::Arc; /// Returned by VM::run method. /// @@ -25,6 +23,26 @@ use near_primitives_core::hash::CryptoHash; /// validators, even when a guest error occurs, or else their state will diverge. pub(crate) type VMResult = Result; +#[tracing::instrument(target = "vm", level = "debug", "prepare", skip_all, fields( + code.hash = %ext.code_hash(), + method_name, + vm_kind = ?wasm_config.vm_kind, + burnt_gas = tracing::field::Empty, + compute_usage = tracing::field::Empty, +))] +pub fn prepare( + ext: &(dyn External + Send), + context: &VMContext, + wasm_config: Arc, + cache: Option<&dyn ContractRuntimeCache>, +) -> Box { + let vm_kind = wasm_config.vm_kind; + let runtime = vm_kind + .runtime(wasm_config) + .unwrap_or_else(|| panic!("the {vm_kind:?} runtime has not been enabled at compile time")); + runtime.prepare(ext, context, cache) +} + /// Validate and run the specified contract. /// /// This is the entry point for executing a NEAR protocol contract. Before the @@ -42,38 +60,22 @@ pub(crate) type VMResult = Result; /// The gas cost for contract preparation will be subtracted by the VM /// implementation. #[tracing::instrument(target = "vm", level = "debug", "run", skip_all, fields( - code.hash = %account.code_hash(), + code.hash = %ext.code_hash(), method_name, vm_kind = ?wasm_config.vm_kind, burnt_gas = tracing::field::Empty, compute_usage = tracing::field::Empty, ))] pub fn run( - account: &Account, - code: Option<&ContractCode>, - method_name: &str, - ext: &mut dyn External, + ext: &mut (dyn External + Send), context: &VMContext, - wasm_config: &Config, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], + wasm_config: Arc, + fees_config: Arc, cache: Option<&dyn ContractRuntimeCache>, ) -> VMResult { let span = tracing::Span::current(); - let vm_kind = wasm_config.vm_kind; - let runtime = vm_kind - .runtime(wasm_config.clone()) - .unwrap_or_else(|| panic!("the {vm_kind:?} runtime has not been enabled at compile time")); - let outcome = runtime.run( - account.code_hash(), - code, - method_name, - ext, - context, - fees_config, - promise_results, - cache, - ); + let prepared = prepare(ext, context, wasm_config, cache); + let outcome = prepared.run(ext, context, fees_config); let outcome = match outcome { Ok(o) => o, e @ Err(_) => return e, @@ -84,32 +86,38 @@ pub fn run( Ok(outcome) } -pub trait VM { - /// Validate and run the specified contract. +pub trait PreparedContract: Send { + /// Run the prepared contract. /// - /// This is the entry point for executing a NEAR protocol contract. Before - /// the entry point (as specified by the `method_name` argument) of the - /// contract code is executed, the contract will be validated (see - /// [`crate::prepare::prepare_contract`]), instrumented (e.g. for gas - /// accounting), and linked with the externs specified via the `ext` - /// argument. + /// This is the entry point for executing a NEAR protocol contract. The entry point (as + /// specified by the `VMContext::method` argument) of the contract code is executed. /// - /// [`VMContext::input`] will be passed to the contract entrypoint as an - /// argument. - /// - /// The gas cost for contract preparation will be subtracted by the VM - /// implementation. + /// [`VMContext::input`] will be made available to the contract. fn run( - &self, - code_hash: CryptoHash, - code: Option<&ContractCode>, - method_name: &str, + self: Box, ext: &mut dyn External, context: &VMContext, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], - cache: Option<&dyn ContractRuntimeCache>, + fees_config: Arc, ) -> VMResult; +} + +pub trait VM { + /// Prepare a contract for execution. + /// + /// Work that goes into the preparation is runtime implementation specific, and depending on + /// the runtime may not do anything at all (and instead prepare everything when the contract is + /// `run`.) + /// + /// ## Return + /// + /// This method does not report any errors. If the contract is invalid in any way, the errors + /// will be reported when the returned value is `run`. + fn prepare( + self: Box, + ext: &dyn External, + context: &VMContext, + cache: Option<&dyn ContractRuntimeCache>, + ) -> Box; /// Precompile a WASM contract to a VM specific format and store the result /// into the `cache`. @@ -130,7 +138,7 @@ pub trait VMKindExt { /// /// This is not intended to be used by code other than internal tools like /// the estimator. - fn runtime(&self, config: Config) -> Option>; + fn runtime(&self, config: std::sync::Arc) -> Option>; } impl VMKindExt for VMKind { @@ -142,7 +150,7 @@ impl VMKindExt for VMKind { Self::NearVm => cfg!(all(feature = "near_vm", target_arch = "x86_64")), } } - fn runtime(&self, config: Config) -> Option> { + fn runtime(&self, config: std::sync::Arc) -> Option> { match self { #[cfg(all(feature = "wasmer0_vm", target_arch = "x86_64"))] Self::Wasmer0 => Some(Box::new(crate::wasmer_runner::Wasmer0VM::new(config))), diff --git a/runtime/near-vm-runner/src/tests.rs b/runtime/near-vm-runner/src/tests.rs index bacca8c2c10..a6f5835a718 100644 --- a/runtime/near-vm-runner/src/tests.rs +++ b/runtime/near-vm-runner/src/tests.rs @@ -23,7 +23,7 @@ pub(crate) fn test_vm_config() -> near_parameters::vm::Config { let config = store.get_config(PROTOCOL_VERSION).wasm_config.clone(); near_parameters::vm::Config { vm_kind: config.vm_kind.replace_with_wasmtime_if_unsupported(), - ..config + ..near_parameters::vm::Config::clone(&config) } } @@ -52,13 +52,15 @@ pub(crate) fn with_vm_variants( } } -fn create_context(input: Vec) -> VMContext { +fn create_context(method: &str, input: Vec) -> VMContext { VMContext { current_account_id: CURRENT_ACCOUNT_ID.parse().unwrap(), signer_account_id: SIGNER_ACCOUNT_ID.parse().unwrap(), signer_account_pk: Vec::from(&SIGNER_ACCOUNT_PK[..]), predecessor_account_id: PREDECESSOR_ACCOUNT_ID.parse().unwrap(), + method: method.into(), input, + promise_results: Vec::new().into(), block_height: 10, block_timestamp: 42, epoch_height: 1, diff --git a/runtime/near-vm-runner/src/tests/cache.rs b/runtime/near-vm-runner/src/tests/cache.rs index b233fbed07c..1192ffa5315 100644 --- a/runtime/near-vm-runner/src/tests/cache.rs +++ b/runtime/near-vm-runner/src/tests/cache.rs @@ -19,7 +19,7 @@ use std::sync::Arc; #[test] fn test_caches_compilation_error() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { // The cache is currently properly implemented only for NearVM match vm_kind { @@ -33,7 +33,7 @@ fn test_caches_compilation_error() { let terragas = 1000000000000u64; assert_eq!(cache.len(), 0); let outcome1 = make_cached_contract_call_vm( - &config, + Arc::clone(&config), &cache, code_hash, Some(&code), @@ -45,7 +45,7 @@ fn test_caches_compilation_error() { println!("{:?}", cache); assert_eq!(cache.len(), 1); let outcome2 = make_cached_contract_call_vm( - &config, + Arc::clone(&config), &cache, code_hash, None, @@ -60,11 +60,11 @@ fn test_caches_compilation_error() { #[test] fn test_does_not_cache_io_error() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { match vm_kind { - VMKind::Wasmer0 | VMKind::Wasmer2 | VMKind::NearVm => {} - VMKind::Wasmtime => return, + VMKind::NearVm => {} + VMKind::Wasmer0 | VMKind::Wasmer2 | VMKind::Wasmtime => return, } let code = near_test_contracts::trivial_contract(); @@ -75,7 +75,7 @@ fn test_does_not_cache_io_error() { cache.set_read_fault(true); let result = make_cached_contract_call_vm( - &config, + Arc::clone(&config), &cache, code_hash, None, @@ -91,7 +91,7 @@ fn test_does_not_cache_io_error() { cache.set_write_fault(true); let result = make_cached_contract_call_vm( - &config, + Arc::clone(&config), &cache, code_hash, Some(&code), @@ -108,7 +108,7 @@ fn test_does_not_cache_io_error() { } fn make_cached_contract_call_vm( - config: &Config, + config: Arc, cache: &dyn ContractRuntimeCache, code_hash: CryptoHash, code: Option<&ContractCode>, @@ -116,22 +116,17 @@ fn make_cached_contract_call_vm( prepaid_gas: u64, vm_kind: VMKind, ) -> VMResult { - let mut fake_external = MockedExternal::new(); - let mut context = create_context(vec![]); - let fees = RuntimeFeesConfig::test(); - let promise_results = vec![]; + let mut fake_external = if let Some(code) = code { + MockedExternal::with_code_and_hash(code_hash, code.clone_for_tests()) + } else { + MockedExternal::new() + }; + fake_external.code_hash = code_hash; + let mut context = create_context(method_name, vec![]); + let fees = Arc::new(RuntimeFeesConfig::test()); context.prepaid_gas = prepaid_gas; - let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); - runtime.run( - code_hash, - code, - method_name, - &mut fake_external, - &context, - &fees, - &promise_results, - Some(cache), - ) + let runtime = vm_kind.runtime(config).expect("runtime has not been compiled"); + runtime.prepare(&fake_external, &context, Some(cache)).run(&mut fake_external, &context, fees) } #[test] @@ -170,7 +165,7 @@ fn test_wasmer2_artifact_output_stability() { for seed in seeds { let contract = ContractCode::new(near_test_contracts::arbitrary_contract(seed), None); - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); let prepared_code = prepare::prepare_contract(contract.code(), &config, VMKind::Wasmer2).unwrap(); got_prepared_hashes.push(crate::utils::stable_hash((&contract.code(), &prepared_code))); @@ -245,7 +240,7 @@ fn test_near_vm_artifact_output_stability() { for seed in seeds { let contract = ContractCode::new(near_test_contracts::arbitrary_contract(seed), None); - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); let prepared_code = prepare::prepare_contract(contract.code(), &config, VMKind::NearVm).unwrap(); got_prepared_hashes.push(crate::utils::stable_hash((&contract.code(), &prepared_code))); diff --git a/runtime/near-vm-runner/src/tests/fuzzers.rs b/runtime/near-vm-runner/src/tests/fuzzers.rs index 5d0ce4af948..95c43e23343 100644 --- a/runtime/near-vm-runner/src/tests/fuzzers.rs +++ b/runtime/near-vm-runner/src/tests/fuzzers.rs @@ -10,6 +10,7 @@ use arbitrary::Arbitrary; use core::fmt; use near_parameters::vm::{ContractPrepareVersion, VMKind}; use near_parameters::RuntimeFeesConfig; +use std::sync::Arc; /// Finds a no-parameter exported function, something like `(func (export "entry-point"))`. pub fn find_entry_point(contract: &ContractCode) -> Option { @@ -38,13 +39,15 @@ pub fn find_entry_point(contract: &ContractCode) -> Option { None } -pub fn create_context(input: Vec) -> VMContext { +pub fn create_context(method: &str, input: Vec) -> VMContext { VMContext { current_account_id: "alice".parse().unwrap(), signer_account_id: "bob".parse().unwrap(), signer_account_pk: vec![0, 1, 2, 3, 4], predecessor_account_id: "carol".parse().unwrap(), + method: method.into(), input, + promise_results: Vec::new().into(), block_height: 10, block_timestamp: 42, epoch_height: 1, @@ -106,30 +109,21 @@ impl fmt::Debug for ArbitraryModule { } fn run_fuzz(code: &ContractCode, vm_kind: VMKind) -> VMResult { - let mut fake_external = MockedExternal::new(); - - let mut context = create_context(vec![]); + let mut fake_external = MockedExternal::with_code(code.clone_for_tests()); + let method_name = find_entry_point(code).unwrap_or_else(|| "main".to_string()); + let mut context = create_context(&method_name, vec![]); context.prepaid_gas = 10u64.pow(14); let mut config = test_vm_config(); config.limit_config.wasmer2_stack_limit = i32::MAX; // If we can crash wasmer2 even without the secondary stack limit it's still good to know config.limit_config.contract_prepare_version = ContractPrepareVersion::V2; - let fees = RuntimeFeesConfig::test(); - - let promise_results = vec![]; - - let method_name = find_entry_point(code).unwrap_or_else(|| "main".to_string()); - let mut res = vm_kind.runtime(config).unwrap().run( - *code.hash(), - Some(code), - &method_name, - &mut fake_external, - &context, - &fees, - &promise_results, - None, - ); + let fees = Arc::new(RuntimeFeesConfig::test()); + let mut res = vm_kind + .runtime(config.into()) + .unwrap() + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, Arc::clone(&fees)); // Remove the VMError message details as they can differ between runtimes // TODO: maybe there's actually things we could check for equality here too? @@ -178,7 +172,7 @@ fn near_vm_is_reproducible_fuzzer() { bolero::check!().with_arbitrary::().for_each(|module: &ArbitraryModule| { let code = ContractCode::new(module.0.module.to_bytes(), None); - let config = test_vm_config(); + let config = std::sync::Arc::new(test_vm_config()); let mut first_hash = None; for _ in 0..3 { let vm = NearVM::new(config.clone()); diff --git a/runtime/near-vm-runner/src/tests/rs_contract.rs b/runtime/near-vm-runner/src/tests/rs_contract.rs index 2a9c031d5ff..6129c45d950 100644 --- a/runtime/near-vm-runner/src/tests/rs_contract.rs +++ b/runtime/near-vm-runner/src/tests/rs_contract.rs @@ -7,6 +7,7 @@ use crate::ContractCode; use near_parameters::RuntimeFeesConfig; use near_primitives_core::types::Balance; use std::mem::size_of; +use std::sync::Arc; use super::test_vm_config; use crate::runner::VMResult; @@ -49,38 +50,27 @@ fn assert_run_result(result: VMResult, expected_value: u64) { #[test] pub fn test_read_write() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); + let fees = Arc::new(RuntimeFeesConfig::test()); with_vm_variants(&config, |vm_kind: VMKind| { let code = test_contract(vm_kind); - let mut fake_external = MockedExternal::new(); + let mut fake_external = MockedExternal::with_code(code); + let context = create_context("write_key_value", encode(&[10u64, 20u64])); - let context = create_context(encode(&[10u64, 20u64])); - let fees = RuntimeFeesConfig::test(); - - let promise_results = vec![]; let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); - let result = runtime.run( - *code.hash(), - Some(&code), - "write_key_value", + let result = runtime.prepare(&fake_external, &context, None).run( &mut fake_external, &context, - &fees, - &promise_results, - None, + Arc::clone(&fees), ); assert_run_result(result, 0); - let context = create_context(encode(&[10u64])); - let result = runtime.run( - *code.hash(), - Some(&code), - "read_value", + let context = create_context("read_value", encode(&[10u64])); + let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); + let result = runtime.prepare(&fake_external, &context, None).run( &mut fake_external, &context, - &fees, - &promise_results, - None, + Arc::clone(&fees), ); assert_run_result(result, 20); }); @@ -90,34 +80,34 @@ macro_rules! def_test_ext { ($name:ident, $method:expr, $expected:expr, $input:expr, $validator:expr) => { #[test] pub fn $name() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { - run_test_ext(&config, $method, $expected, $input, $validator, vm_kind) + run_test_ext(Arc::clone(&config), $method, $expected, $input, $validator, vm_kind) }); } }; ($name:ident, $method:expr, $expected:expr, $input:expr) => { #[test] pub fn $name() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { - run_test_ext(&config, $method, $expected, $input, vec![], vm_kind) + run_test_ext(Arc::clone(&config), $method, $expected, $input, vec![], vm_kind) }); } }; ($name:ident, $method:expr, $expected:expr) => { #[test] pub fn $name() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { - run_test_ext(&config, $method, $expected, &[], vec![], vm_kind) + run_test_ext(Arc::clone(&config), $method, $expected, &[], vec![], vm_kind) }) } }; } fn run_test_ext( - config: &Config, + config: Arc, method: &str, expected: &[u8], input: &[u8], @@ -125,15 +115,16 @@ fn run_test_ext( vm_kind: VMKind, ) { let code = test_contract(vm_kind); - let mut fake_external = MockedExternal::new(); + let mut fake_external = MockedExternal::with_code(code); fake_external.validators = validators.into_iter().map(|(s, b)| (s.parse().unwrap(), b)).collect(); - let fees = RuntimeFeesConfig::test(); - let context = create_context(input.to_vec()); - let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); + let fees = Arc::new(RuntimeFeesConfig::test()); + let context = create_context(method, input.to_vec()); + let runtime = vm_kind.runtime(config).expect("runtime has not been compiled"); let outcome = runtime - .run(*code.hash(), Some(&code), method, &mut fake_external, &context, &fees, &[], None) + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, Arc::clone(&fees)) .unwrap_or_else(|err| panic!("Failed execution: {:?}", err)); assert_eq!(outcome.profile.action_gas(), 0); @@ -164,7 +155,7 @@ def_test_ext!(ext_storage_usage, "ext_storage_usage", &12u64.to_le_bytes()); #[test] pub fn ext_used_gas() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { // Note, the used_gas is not a global used_gas at the beginning of method, but instead a // diff in used_gas for computing fib(30) in a loop @@ -173,7 +164,7 @@ pub fn ext_used_gas() { crate::logic::ContractPrepareVersion::V1 => [111, 10, 200, 15, 0, 0, 0, 0], crate::logic::ContractPrepareVersion::V2 => [27, 180, 237, 15, 0, 0, 0, 0], }; - run_test_ext(&config, "ext_used_gas", &expected, &[], vec![], vm_kind) + run_test_ext(Arc::clone(&config), "ext_used_gas", &expected, &[], vec![], vm_kind) }) } @@ -224,6 +215,7 @@ def_test_ext!( pub fn test_out_of_memory() { let mut config = test_vm_config(); config.make_free(); + let config = Arc::new(config); with_vm_variants(&config, |vm_kind: VMKind| { // TODO: currently we only run this test on Wasmer. match vm_kind { @@ -232,24 +224,13 @@ pub fn test_out_of_memory() { } let code = test_contract(vm_kind); - let mut fake_external = MockedExternal::new(); - - let context = create_context(Vec::new()); - let fees = RuntimeFeesConfig::free(); + let mut fake_external = MockedExternal::with_code(code); + let context = create_context("out_of_memory", Vec::new()); + let fees = Arc::new(RuntimeFeesConfig::free()); let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); - - let promise_results = vec![]; let result = runtime - .run( - *code.hash(), - Some(&code), - "out_of_memory", - &mut fake_external, - &context, - &fees, - &promise_results, - None, - ) + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, fees) .expect("execution failed"); assert_eq!( result.aborted, @@ -268,29 +249,22 @@ fn function_call_weight_contract() -> ContractCode { #[test] fn attach_unspent_gas_but_use_all_gas() { - let mut context = create_context(vec![]); + let mut context = create_context("attach_unspent_gas_but_use_all_gas", vec![]); context.prepaid_gas = 100 * 10u64.pow(12); let mut config = test_vm_config(); config.limit_config.max_gas_burnt = context.prepaid_gas / 3; + let config = Arc::new(config); with_vm_variants(&config, |vm_kind: VMKind| { let code = function_call_weight_contract(); - let mut external = MockedExternal::new(); - let fees = RuntimeFeesConfig::test(); + let mut external = MockedExternal::with_code(code); + let fees = Arc::new(RuntimeFeesConfig::test()); let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); let outcome = runtime - .run( - *code.hash(), - Some(&code), - "attach_unspent_gas_but_use_all_gas", - &mut external, - &context, - &fees, - &[], - None, - ) + .prepare(&external, &context, None) + .run(&mut external, &context, fees) .unwrap_or_else(|err| panic!("Failed execution: {:?}", err)); let err = outcome.aborted.as_ref().unwrap(); diff --git a/runtime/near-vm-runner/src/tests/test_builder.rs b/runtime/near-vm-runner/src/tests/test_builder.rs index b765f89876d..dbd9312b818 100644 --- a/runtime/near-vm-runner/src/tests/test_builder.rs +++ b/runtime/near-vm-runner/src/tests/test_builder.rs @@ -16,6 +16,8 @@ pub(crate) fn test_builder() -> TestBuilder { signer_account_pk: vec![0, 1, 2], predecessor_account_id: "carol".parse().unwrap(), input: Vec::new(), + promise_results: Vec::new().into(), + method: "main".into(), block_height: 10, block_timestamp: 42, epoch_height: 1, @@ -37,7 +39,6 @@ pub(crate) fn test_builder() -> TestBuilder { TestBuilder { code: ContractCode::new(Vec::new(), None), context, - method: "main".to_string(), protocol_versions: vec![u32::MAX], skip, opaque_error: false, @@ -49,7 +50,6 @@ pub(crate) struct TestBuilder { code: ContractCode, context: VMContext, protocol_versions: Vec, - method: String, skip: HashSet, opaque_error: bool, opaque_outcome: bool, @@ -74,7 +74,7 @@ impl TestBuilder { } pub(crate) fn method(mut self, method: &str) -> Self { - self.method = method.to_string(); + self.context.method = method.to_string(); self } @@ -212,28 +212,18 @@ impl TestBuilder { continue; } - let mut fake_external = MockedExternal::new(); + let mut fake_external = MockedExternal::with_code(self.code.clone_for_tests()); let config = runtime_config.wasm_config.clone(); - let fees = RuntimeFeesConfig::test(); + let fees = Arc::new(RuntimeFeesConfig::test()); let context = self.context.clone(); - let promise_results = vec![]; - let Some(runtime) = vm_kind.runtime(config) else { panic!("runtime for {:?} has not been compiled", vm_kind); }; println!("Running {:?} for protocol version {}", vm_kind, protocol_version); let outcome = runtime - .run( - *self.code.hash(), - Some(&self.code), - &self.method, - &mut fake_external, - &context, - &fees, - &promise_results, - None, - ) + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, fees) .expect("execution failed"); let mut got = String::new(); diff --git a/runtime/near-vm-runner/src/tests/ts_contract.rs b/runtime/near-vm-runner/src/tests/ts_contract.rs index 588dfe6eb77..067a74537de 100644 --- a/runtime/near-vm-runner/src/tests/ts_contract.rs +++ b/runtime/near-vm-runner/src/tests/ts_contract.rs @@ -8,30 +8,23 @@ use crate::tests::{create_context, with_vm_variants}; use crate::ContractCode; use near_parameters::vm::VMKind; use near_parameters::RuntimeFeesConfig; +use std::sync::Arc; #[test] pub fn test_ts_contract() { - let config = test_vm_config(); + let config = Arc::new(test_vm_config()); with_vm_variants(&config, |vm_kind: VMKind| { let code = ContractCode::new(near_test_contracts::ts_contract().to_vec(), None); - let code_hash = code.hash(); - let mut fake_external = MockedExternal::new(); - - let context = create_context(Vec::new()); - let fees = RuntimeFeesConfig::test(); + let mut fake_external = MockedExternal::with_code(code); + let context = create_context("try_panic", Vec::new()); + let fees = Arc::new(RuntimeFeesConfig::test()); // Call method that panics. - let promise_results = vec![]; let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); - let result = runtime.run( - *code_hash, - Some(&code), - "try_panic", + let result = runtime.prepare(&fake_external, &context, None).run( &mut fake_external, &context, - &fees, - &promise_results, - None, + Arc::clone(&fees), ); let outcome = result.expect("execution failed"); assert_eq!( @@ -42,18 +35,11 @@ pub fn test_ts_contract() { ); // Call method that writes something into storage. - let context = create_context(b"foo bar".to_vec()); + let context = create_context("try_storage_write", b"foo bar".to_vec()); + let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); runtime - .run( - *code_hash, - Some(&code), - "try_storage_write", - &mut fake_external, - &context, - &fees, - &promise_results, - None, - ) + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, Arc::clone(&fees)) .expect("bad failure"); // Verify by looking directly into the storage of the host. { @@ -65,18 +51,11 @@ pub fn test_ts_contract() { } // Call method that reads the value from storage using registers. - let context = create_context(b"foo".to_vec()); + let context = create_context("try_storage_read", b"foo".to_vec()); + let runtime = vm_kind.runtime(config.clone()).expect("runtime has not been compiled"); let outcome = runtime - .run( - *code_hash, - Some(&code), - "try_storage_read", - &mut fake_external, - &context, - &fees, - &promise_results, - None, - ) + .prepare(&fake_external, &context, None) + .run(&mut fake_external, &context, Arc::clone(&fees)) .expect("execution failed"); if let ReturnData::Value(value) = outcome.return_data { diff --git a/runtime/near-vm-runner/src/wasmer2_runner.rs b/runtime/near-vm-runner/src/wasmer2_runner.rs index 5b38051147b..bfef3fb8a6d 100644 --- a/runtime/near-vm-runner/src/wasmer2_runner.rs +++ b/runtime/near-vm-runner/src/wasmer2_runner.rs @@ -1,19 +1,18 @@ use crate::cache::{CompiledContract, CompiledContractInfo, ContractRuntimeCache}; use crate::errors::ContractPrecompilatonResult; -use crate::imports::wasmer2::Wasmer2Imports; use crate::logic::errors::{ CacheError, CompilationError, FunctionCallError, MethodResolveError, VMRunnerError, WasmTrap, }; use crate::logic::gas_counter::FastGasCounter; -use crate::logic::types::PromiseResult; -use crate::logic::{Config, External, MemSlice, MemoryLike, VMContext, VMLogic, VMOutcome}; +use crate::logic::{ + Config, ExecutionResultState, External, MemSlice, MemoryLike, VMContext, VMLogic, VMOutcome, +}; use crate::prepare; use crate::runner::VMResult; use crate::{get_contract_cache_key, imports, ContractCode}; use memoffset::offset_of; use near_parameters::vm::VMKind; use near_parameters::RuntimeFeesConfig; -use near_primitives_core::hash::CryptoHash; use std::borrow::Cow; use std::hash::Hash; use std::mem::size_of; @@ -25,7 +24,8 @@ use wasmer_engine_universal::{ }; use wasmer_types::{FunctionIndex, InstanceConfig, MemoryType, Pages, WASM_PAGE_SIZE}; use wasmer_vm::{ - Artifact, Instantiatable, LinearMemory, LinearTable, Memory, MemoryStyle, TrapCode, VMMemory, + Artifact, ExportFunction, ExportFunctionMetadata, Instantiatable, LinearMemory, LinearTable, + Memory, MemoryStyle, Resolver, TrapCode, VMFunction, VMFunctionKind, VMMemory, }; #[derive(Clone)] @@ -229,12 +229,12 @@ pub(crate) fn wasmer2_vm_hash() -> u64 { pub(crate) type VMArtifact = Arc; pub(crate) struct Wasmer2VM { - pub(crate) config: Config, + pub(crate) config: Arc, pub(crate) engine: UniversalEngine, } impl Wasmer2VM { - pub(crate) fn new_for_target(config: Config, target: wasmer_compiler::Target) -> Self { + pub(crate) fn new_for_target(config: Arc, target: wasmer_compiler::Target) -> Self { // We only support singlepass compiler at the moment. assert_eq!(WASMER2_CONFIG.compiler, WasmerCompiler::Singlepass); let compiler = Singlepass::new(); @@ -248,7 +248,7 @@ impl Wasmer2VM { } } - pub(crate) fn new(config: Config) -> Self { + pub(crate) fn new(config: Arc) -> Self { use wasmer_compiler::{CpuFeature, Target, Triple}; let target_features = if cfg!(feature = "no_cpu_compatibility_checks") { let mut fs = CpuFeature::set(); @@ -563,77 +563,256 @@ impl wasmer_vm::Tunables for &Wasmer2VM { } impl crate::runner::VM for Wasmer2VM { - fn run( + fn precompile( &self, - _code_hash: CryptoHash, - code: Option<&ContractCode>, - method_name: &str, - ext: &mut dyn External, + code: &ContractCode, + cache: &dyn ContractRuntimeCache, + ) -> Result< + Result, + crate::logic::errors::CacheError, + > { + Ok(self + .compile_and_cache(code, Some(cache))? + .map(|_| ContractPrecompilatonResult::ContractCompiled)) + } + + fn prepare( + self: Box, + ext: &dyn External, context: &VMContext, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], cache: Option<&dyn ContractRuntimeCache>, - ) -> Result { - let Some(code) = code else { - return Err(VMRunnerError::CacheError(CacheError::ReadError(std::io::Error::from( - std::io::ErrorKind::NotFound, - )))); + ) -> Box { + type Result = VMResult; + let Some(code) = ext.get_contract() else { + return Box::new(Result::Err(VMRunnerError::ContractCodeNotPresent)); }; - let mut memory = Wasmer2Memory::new( + let mut result_state = ExecutionResultState::new(&context, Arc::clone(&self.config)); + let result = + result_state.before_loading_executable(&context.method, code.code().len() as u64); + if let Err(e) = result { + return Box::new(Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e)))); + } + let artifact = match self.compile_and_load(&code, cache) { + Ok(Ok(it)) => it, + Ok(Err(err)) => { + return Box::new(Ok(PreparedContract::Outcome(VMOutcome::abort( + result_state, + FunctionCallError::CompilationError(err), + )))); + } + Err(err) => { + return Box::new(Result::Err(err)); + } + }; + let result = result_state.after_loading_executable(code.code().len() as u64); + if let Err(e) = result { + return Box::new(Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e)))); + } + let entrypoint = match get_entrypoint_index(&*artifact, &context.method) { + Ok(index) => index, + Err(e) => { + return Box::new(Ok(PreparedContract::Outcome( + VMOutcome::abort_but_nop_outcome_in_old_protocol(result_state, e), + ))) + } + }; + + let memory = Wasmer2Memory::new( self.config.limit_config.initial_memory_pages, self.config.limit_config.max_memory_pages, ) .expect("Cannot create memory for a contract call"); + Box::new(Ok(PreparedContract::Ready(ReadyContract { + vm: self, + memory, + result_state, + entrypoint, + artifact, + }))) + } +} +struct ReadyContract { + vm: Box, + memory: Wasmer2Memory, + result_state: ExecutionResultState, + entrypoint: FunctionIndex, + artifact: VMArtifact, +} + +#[allow(clippy::large_enum_variant)] +enum PreparedContract { + Outcome(VMOutcome), + Ready(ReadyContract), +} + +impl crate::PreparedContract for VMResult { + fn run( + self: Box, + ext: &mut dyn External, + context: &VMContext, + fees_config: Arc, + ) -> VMResult { + let ReadyContract { vm, memory, result_state, entrypoint, artifact } = match (*self)? { + PreparedContract::Outcome(outcome) => return Ok(outcome), + PreparedContract::Ready(r) => r, + }; // FIXME: this mostly duplicates the `run_module` method. // Note that we don't clone the actual backing memory, just increase the RC. let vmmemory = memory.vm(); - let mut logic = - VMLogic::new(ext, context, &self.config, fees_config, promise_results, &mut memory); - - let result = logic.before_loading_executable(method_name, code.code().len() as u64); - if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); + let mut logic = VMLogic::new(ext, context, fees_config, result_state, memory); + let import = build_imports(vmmemory, &mut logic, Arc::clone(&vm.config), artifact.engine()); + match vm.run_method(&artifact, import, entrypoint)? { + Ok(()) => Ok(VMOutcome::ok(logic.result_state)), + Err(err) => Ok(VMOutcome::abort(logic.result_state, err)), } + } +} - let artifact = self.compile_and_load(code, cache)?; - let artifact = match artifact { - Ok(it) => it, - Err(err) => { - return Ok(VMOutcome::abort(logic, FunctionCallError::CompilationError(err))); +pub(crate) struct Wasmer2Imports<'engine, 'vmlogic, 'vmlogic_refs> { + pub(crate) memory: VMMemory, + config: Arc, + // Note: this same object is also referenced by the `metadata` field! + pub(crate) vmlogic: &'vmlogic mut VMLogic<'vmlogic_refs>, + pub(crate) metadata: Arc, + pub(crate) engine: &'engine UniversalEngine, +} + +trait Wasmer2Type { + type Wasmer; + fn to_wasmer(self) -> Self::Wasmer; + fn ty() -> wasmer_types::Type; +} +macro_rules! wasmer_types { + ($($native:ty as $wasmer:ty => $type_expr:expr;)*) => { + $(impl Wasmer2Type for $native { + type Wasmer = $wasmer; + fn to_wasmer(self) -> $wasmer { + self as _ } - }; + fn ty() -> wasmer_types::Type { + $type_expr + } + })* + } +} +wasmer_types! { + u32 as i32 => wasmer_types::Type::I32; + u64 as i64 => wasmer_types::Type::I64; +} - let result = logic.after_loading_executable(code.code().len() as u64); - if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); +macro_rules! return_ty { + ($return_type: ident = [ ]) => { + type $return_type = (); + fn make_ret() -> () {} + }; + ($return_type: ident = [ $($returns: ident),* ]) => { + #[repr(C)] + struct $return_type($(<$returns as Wasmer2Type>::Wasmer),*); + fn make_ret($($returns: $returns),*) -> Ret { Ret($($returns.to_wasmer()),*) } + } +} + +impl<'e, 'l, 'lr> Resolver for Wasmer2Imports<'e, 'l, 'lr> { + fn resolve(&self, _index: u32, module: &str, field: &str) -> Option { + if module == "env" && field == "memory" { + return Some(wasmer_vm::Export::Memory(self.memory.clone())); } - let import = imports::wasmer2::build(vmmemory, &mut logic, artifact.engine()); - let entrypoint = match get_entrypoint_index(&*artifact, method_name) { - Ok(index) => index, - Err(e) => return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol(logic, e)), - }; - match self.run_method(&artifact, import, entrypoint)? { - Ok(()) => Ok(VMOutcome::ok(logic)), - Err(err) => Ok(VMOutcome::abort(logic, err)), + + macro_rules! add_import { + ( + $mod:ident / $name:ident : $func:ident < + [ $( $arg_name:ident : $arg_type:ident ),* ] + -> [ $( $returns:ident ),* ] + > + ) => { + return_ty!(Ret = [ $($returns),* ]); + + extern "C" fn $name(env: *mut VMLogic<'_>, $( $arg_name: $arg_type ),* ) + -> Ret { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + const TRACE: bool = $crate::imports::should_trace_host_function(stringify!($name)); + let _span = TRACE.then(|| { + tracing::trace_span!(target: "vm::host_function", stringify!($name)).entered() + }); + + // SAFETY: This code should only be executable within `'vmlogic` + // lifetime and so it is safe to dereference the `env` pointer which is + // known to be derived from a valid `&'vmlogic mut VMLogic<'_>` in the + // first place. + unsafe { (*env).$func( $( $arg_name, )* ) } + })); + // We want to ensure that the only kind of error that host function calls + // return are VMLogicError. This is important because we later attempt to + // downcast the `RuntimeError`s into `VMLogicError`. + let result: Result, _> = result; + #[allow(unused_parens)] + match result { + Ok(Ok(($($returns),*))) => make_ret($($returns),*), + Ok(Err(trap)) => unsafe { + // SAFETY: this can only be called by a WASM contract, so all the + // necessary hooks are known to be in place. + wasmer_vm::raise_user_trap(Box::new(trap)) + }, + Err(e) => unsafe { + // SAFETY: this can only be called by a WASM contract, so all the + // necessary hooks are known to be in place. + wasmer_vm::resume_panic(e) + }, + } + } + // TODO: a phf hashmap would probably work better here. + if module == stringify!($mod) && field == stringify!($name) { + let args = [$(<$arg_type as Wasmer2Type>::ty()),*]; + let rets = [$(<$returns as Wasmer2Type>::ty()),*]; + let signature = wasmer_types::FunctionTypeRef::new(&args[..], &rets[..]); + let signature = self.engine.register_signature(signature); + return Some(wasmer_vm::Export::Function(ExportFunction { + vm_function: VMFunction { + address: $name as *const _, + // SAFETY: here we erase the lifetime of the `vmlogic` reference, + // but we believe that the lifetimes on `Wasmer2Imports` enforce + // sufficiently that it isn't possible to call this exported + // function when vmlogic is no loger live. + vmctx: wasmer_vm::VMFunctionEnvironment { + host_env: self.vmlogic as *const _ as *mut _ + }, + signature, + kind: VMFunctionKind::Static, + call_trampoline: None, + instance_ref: None, + }, + metadata: Some(Arc::clone(&self.metadata)), + })); + } + }; } + imports::for_each_available_import!(self.config, add_import); + return None; } +} - fn precompile( - &self, - code: &ContractCode, - cache: &dyn ContractRuntimeCache, - ) -> Result< - Result, - crate::logic::errors::CacheError, - > { - Ok(self - .compile_and_cache(code, Some(cache))? - .map(|_| ContractPrecompilatonResult::ContractCompiled)) - } +pub(crate) fn build_imports<'e, 'a, 'b>( + memory: VMMemory, + logic: &'a mut VMLogic<'b>, + config: Arc, + engine: &'e UniversalEngine, +) -> Wasmer2Imports<'e, 'a, 'b> { + let metadata = unsafe { + // SAFETY: the functions here are thread-safe. We ensure that the lifetime of `VMLogic` + // is sufficiently long by tying the lifetime of VMLogic to the return type which + // contains this metadata. + ExportFunctionMetadata::new(logic as *mut _ as *mut _, None, |ptr| ptr, |_| {}) + }; + Wasmer2Imports { memory, config, vmlogic: logic, metadata: Arc::new(metadata), engine } } -#[test] -fn test_memory_like() { - crate::logic::test_utils::test_memory_like(|| Box::new(Wasmer2Memory::new(1, 1).unwrap())); +#[cfg(test)] +mod tests { + #[test] + fn test_memory_like() { + crate::logic::test_utils::test_memory_like(|| { + Box::new(super::Wasmer2Memory::new(1, 1).unwrap()) + }); + } } diff --git a/runtime/near-vm-runner/src/wasmer_runner.rs b/runtime/near-vm-runner/src/wasmer_runner.rs index 0c620f51f54..ef666cf6917 100644 --- a/runtime/near-vm-runner/src/wasmer_runner.rs +++ b/runtime/near-vm-runner/src/wasmer_runner.rs @@ -3,15 +3,19 @@ use crate::errors::ContractPrecompilatonResult; use crate::logic::errors::{ CacheError, CompilationError, FunctionCallError, MethodResolveError, VMRunnerError, WasmTrap, }; -use crate::logic::types::PromiseResult; -use crate::logic::{External, VMContext, VMLogic, VMLogicError, VMOutcome}; -use crate::memory::WasmerMemory; +use crate::logic::{ExecutionResultState, External, VMContext, VMLogic, VMLogicError, VMOutcome}; +use crate::logic::{MemSlice, MemoryLike}; use crate::prepare; use crate::runner::VMResult; use crate::{get_contract_cache_key, imports, ContractCode}; use near_parameters::vm::{Config, VMKind}; use near_parameters::RuntimeFeesConfig; -use near_primitives_core::hash::CryptoHash; +use std::borrow::Cow; +use std::ffi::c_void; +use std::sync::Arc; +use wasmer_runtime::units::Pages; +use wasmer_runtime::wasm::MemoryDescriptor; +use wasmer_runtime::Memory; use wasmer_runtime::{ImportObject, Module}; fn check_method(module: &Module, method_name: &str) -> Result<(), FunctionCallError> { @@ -194,6 +198,63 @@ impl IntoVMError for wasmer_runtime::error::RuntimeError { } } +pub struct WasmerMemory(Memory); + +impl WasmerMemory { + pub fn new(initial_memory_pages: u32, max_memory_pages: u32) -> Self { + WasmerMemory( + Memory::new( + MemoryDescriptor::new( + Pages(initial_memory_pages), + Some(Pages(max_memory_pages)), + false, + ) + .unwrap(), + ) + .expect("TODO creating memory cannot fail"), + ) + } + + pub fn clone(&self) -> Memory { + self.0.clone() + } +} + +impl WasmerMemory { + fn with_memory(&self, offset: u64, len: usize, func: F) -> Result + where + F: FnOnce(core::slice::Iter<'_, std::cell::Cell>) -> T, + { + let start = usize::try_from(offset).map_err(|_| ())?; + let end = start.checked_add(len).ok_or(())?; + self.0.view().get(start..end).map(|mem| func(mem.iter())).ok_or(()) + } +} + +impl MemoryLike for WasmerMemory { + fn fits_memory(&self, slice: MemSlice) -> Result<(), ()> { + self.with_memory(slice.ptr, slice.len()?, |_| ()) + } + + fn view_memory(&self, slice: MemSlice) -> Result, ()> { + self.with_memory(slice.ptr, slice.len()?, |mem| { + Cow::Owned(mem.map(core::cell::Cell::get).collect()) + }) + } + + fn read_memory(&self, offset: u64, buffer: &mut [u8]) -> Result<(), ()> { + self.with_memory(offset, buffer.len(), |mem| { + buffer.iter_mut().zip(mem).for_each(|(dst, src)| *dst = src.get()); + }) + } + + fn write_memory(&mut self, offset: u64, buffer: &[u8]) -> Result<(), ()> { + self.with_memory(offset, buffer.len(), |mem| { + mem.zip(buffer.iter()).for_each(|(dst, src)| dst.set(*src)); + }) + } +} + fn run_method( module: &Module, import: &ImportObject, @@ -234,11 +295,11 @@ pub(crate) fn wasmer0_vm_hash() -> u64 { } pub(crate) struct Wasmer0VM { - config: Config, + config: Arc, } impl Wasmer0VM { - pub(crate) fn new(config: Config) -> Self { + pub(crate) fn new(config: Arc) -> Self { Self { config } } @@ -353,21 +414,28 @@ impl Wasmer0VM { } impl crate::runner::VM for Wasmer0VM { - fn run( + fn precompile( &self, - _code_hash: CryptoHash, - code: Option<&ContractCode>, - method_name: &str, - ext: &mut dyn External, + code: &ContractCode, + cache: &dyn ContractRuntimeCache, + ) -> Result< + Result, + crate::logic::errors::CacheError, + > { + Ok(self + .compile_and_cache(code, Some(cache))? + .map(|_| ContractPrecompilatonResult::ContractCompiled)) + } + + fn prepare( + self: Box, + ext: &dyn External, context: &VMContext, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], cache: Option<&dyn ContractRuntimeCache>, - ) -> Result { - let Some(code) = code else { - return Err(VMRunnerError::CacheError(CacheError::ReadError(std::io::Error::from( - std::io::ErrorKind::NotFound, - )))); + ) -> Box { + type Result = VMResult; + let Some(code) = ext.get_contract() else { + return Box::new(Result::Err(VMRunnerError::ContractCodeNotPresent)); }; if !cfg!(target_arch = "x86") && !cfg!(target_arch = "x86_64") { // TODO(#1940): Remove once NaN is standardized by the VM. @@ -381,25 +449,16 @@ impl crate::runner::VM for Wasmer0VM { panic!("AVX support is required in order to run Wasmer VM Singlepass backend."); } - let mut memory = WasmerMemory::new( - self.config.limit_config.initial_memory_pages, - self.config.limit_config.max_memory_pages, - ); - // Note that we don't clone the actual backing memory, just increase the RC. - let memory_copy = memory.clone(); - - let mut logic = - VMLogic::new(ext, context, &self.config, fees_config, promise_results, &mut memory); - - let result = logic.before_loading_executable(method_name, code.code().len() as u64); + let mut result_state = ExecutionResultState::new(&context, Arc::clone(&self.config)); + let result = + result_state.before_loading_executable(&context.method, code.code().len() as u64); if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); + return Box::new(Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e)))); } // TODO: consider using get_module() here, once we'll go via deployment path. - let module = self.compile_and_load(code, cache)?; - let module = match module { - Ok(x) => x, + let module = match self.compile_and_load(&code, cache) { + Ok(Ok(x)) => x, // Note on backwards-compatibility: This error used to be an error // without result, later refactored to NOP outcome. Now this returns // an actual outcome, including gas costs that occurred before this @@ -407,38 +466,123 @@ impl crate::runner::VM for Wasmer0VM { // version do not have gas costs before reaching this code. (Also // see `test_old_fn_loading_behavior_preserved` for a test that // verifies future changes do not counteract this assumption.) - Err(err) => { - return Ok(VMOutcome::abort(logic, FunctionCallError::CompilationError(err))) + Ok(Err(err)) => { + return Box::new(Ok(PreparedContract::Outcome(VMOutcome::abort( + result_state, + FunctionCallError::CompilationError(err), + )))) } + Err(err) => return Box::new(Result::Err(err)), }; - let result = logic.after_loading_executable(code.code().len() as u64); + let result = result_state.after_loading_executable(code.code().len() as u64); if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); + return Box::new(Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e)))); + } + if let Err(e) = check_method(&module, &context.method) { + return Box::new(Ok(PreparedContract::Outcome( + VMOutcome::abort_but_nop_outcome_in_old_protocol(result_state, e), + ))); } - let import_object = imports::wasmer::build(memory_copy, &mut logic); + let memory = WasmerMemory::new( + self.config.limit_config.initial_memory_pages, + self.config.limit_config.max_memory_pages, + ); + Box::new(Ok(PreparedContract::Ready(ReadyContract { + vm: self, + memory, + result_state, + module, + }))) + } +} - if let Err(e) = check_method(&module, method_name) { - return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol(logic, e)); - } +struct ReadyContract { + vm: Box, + memory: WasmerMemory, + result_state: ExecutionResultState, + module: Module, +} - match run_method(&module, &import_object, method_name)? { - Ok(()) => Ok(VMOutcome::ok(logic)), - Err(err) => Ok(VMOutcome::abort(logic, err)), +#[allow(clippy::large_enum_variant)] +enum PreparedContract { + Outcome(VMOutcome), + Ready(ReadyContract), +} + +impl crate::PreparedContract for VMResult { + fn run( + self: Box, + ext: &mut dyn External, + context: &VMContext, + fees_config: Arc, + ) -> Result { + let ReadyContract { vm, memory, result_state, module } = match (*self)? { + PreparedContract::Outcome(outcome) => return Ok(outcome), + PreparedContract::Ready(r) => r, + }; + // Note that we don't clone the actual backing memory, just increase the RC. + let memory_copy = memory.clone(); + let mut logic = VMLogic::new(ext, context, fees_config, result_state, memory); + let import_object = build_imports(memory_copy, &vm.config, &mut logic); + match run_method(&module, &import_object, &context.method)? { + Ok(()) => Ok(VMOutcome::ok(logic.result_state)), + Err(err) => Ok(VMOutcome::abort(logic.result_state, err)), } } +} - fn precompile( - &self, - code: &ContractCode, - cache: &dyn ContractRuntimeCache, - ) -> Result< - Result, - crate::logic::errors::CacheError, - > { - Ok(self - .compile_and_cache(code, Some(cache))? - .map(|_| ContractPrecompilatonResult::ContractCompiled)) - } +#[derive(Clone, Copy)] +struct ImportReference(pub *mut c_void); +unsafe impl Send for ImportReference {} +unsafe impl Sync for ImportReference {} + +pub(crate) fn build_imports( + memory: wasmer_runtime::memory::Memory, + config: &Config, + logic: &mut VMLogic<'_>, +) -> wasmer_runtime::ImportObject { + let raw_ptr = logic as *mut _ as *mut c_void; + let import_reference = ImportReference(raw_ptr); + let mut import_object = wasmer_runtime::ImportObject::new_with_data(move || { + let dtor = (|_: *mut c_void| {}) as fn(*mut c_void); + ({ import_reference }.0, dtor) + }); + + let mut ns_internal = wasmer_runtime_core::import::Namespace::new(); + let mut ns_env = wasmer_runtime_core::import::Namespace::new(); + ns_env.insert("memory", memory); + + macro_rules! add_import { + ( + $mod:ident / $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > + ) => { + #[allow(unused_parens)] + fn $name( ctx: &mut wasmer_runtime::Ctx, $( $arg_name: $arg_type ),* ) -> Result<($( $returns ),*), VMLogicError> { + const TRACE: bool = $crate::imports::should_trace_host_function(stringify!($name)); + let _span = TRACE.then(|| { + tracing::trace_span!(target: "vm::host_function", stringify!($name)).entered() + }); + let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) }; + logic.$func( $( $arg_name, )* ) + } + + match stringify!($mod) { + "env" => ns_env.insert(stringify!($name), wasmer_runtime::func!($name)), + "internal" => ns_internal.insert(stringify!($name), wasmer_runtime::func!($name)), + _ => unimplemented!(), + } + }; + } + imports::for_each_available_import!(config, add_import); + + import_object.register("env", ns_env); + import_object.register("internal", ns_internal); + import_object +} + +#[test] +fn test_memory_like() { + crate::logic::test_utils::test_memory_like(|| Box::new(WasmerMemory::new(1, 1))); } diff --git a/runtime/near-vm-runner/src/wasmtime_runner.rs b/runtime/near-vm-runner/src/wasmtime_runner.rs index 8814eab0c7b..ef79ed89558 100644 --- a/runtime/near-vm-runner/src/wasmtime_runner.rs +++ b/runtime/near-vm-runner/src/wasmtime_runner.rs @@ -3,15 +3,19 @@ use crate::logic::errors::{ CacheError, CompilationError, FunctionCallError, MethodResolveError, PrepareError, VMLogicError, VMRunnerError, WasmTrap, }; -use crate::logic::types::PromiseResult; -use crate::logic::Config; +use crate::logic::{Config, ExecutionResultState}; use crate::logic::{External, MemSlice, MemoryLike, VMContext, VMLogic, VMOutcome}; -use crate::{imports, prepare, ContractCode, ContractRuntimeCache}; +use crate::runner::VMResult; +use crate::{ + get_contract_cache_key, imports, prepare, CompiledContract, CompiledContractInfo, ContractCode, + ContractRuntimeCache, NoContractRuntimeCache, +}; use near_parameters::vm::VMKind; use near_parameters::RuntimeFeesConfig; -use near_primitives_core::hash::CryptoHash; use std::borrow::Cow; -use std::cell::RefCell; +use std::cell::{RefCell, UnsafeCell}; +use std::ffi::c_void; +use std::sync::Arc; use wasmtime::ExternType::Func; use wasmtime::{Engine, Linker, Memory, MemoryType, Module, Store}; @@ -83,7 +87,7 @@ trait IntoVMError { impl IntoVMError for anyhow::Error { fn into_vm_error(self) -> Result { let cause = self.root_cause(); - if let Some(container) = cause.downcast_ref::() { + if let Some(container) = cause.downcast_ref::() { use {VMLogicError as LE, VMRunnerError as RE}; return match container.take() { Some(LE::HostError(h)) => Ok(FunctionCallError::HostError(h)), @@ -120,15 +124,17 @@ impl IntoVMError for anyhow::Error { } } -#[cfg(not(feature = "lightbeam"))] #[allow(clippy::needless_pass_by_ref_mut)] -pub fn get_engine(config: &mut wasmtime::Config) -> Engine { +pub fn get_engine(config: &wasmtime::Config) -> Engine { Engine::new(config).unwrap() } -#[cfg(feature = "lightbeam")] -pub fn get_engine(config: &mut wasmtime::Config) -> Engine { - Engine::new(config.strategy(wasmtime::Strategy::Lightbeam).unwrap()).unwrap() +pub(crate) fn default_wasmtime_config(config: &Config) -> wasmtime::Config { + let features = + crate::features::WasmFeatures::from(config.limit_config.contract_prepare_version); + let mut config = wasmtime::Config::from(features); + config.max_wasm_stack(1024 * 1024 * 1024); // wasm stack metering is implemented by instrumentation, we don't want wasmtime to trap before that + config } pub(crate) fn wasmtime_vm_hash() -> u64 { @@ -137,129 +143,324 @@ pub(crate) fn wasmtime_vm_hash() -> u64 { } pub(crate) struct WasmtimeVM { - config: Config, + config: Arc, + engine: wasmtime::Engine, } impl WasmtimeVM { - pub(crate) fn new(config: Config) -> Self { - Self { config } + pub(crate) fn new(config: Arc) -> Self { + Self { engine: get_engine(&default_wasmtime_config(&config)), config } } - pub(crate) fn default_wasmtime_config(&self) -> wasmtime::Config { - let features = - crate::features::WasmFeatures::from(self.config.limit_config.contract_prepare_version); - let mut config = wasmtime::Config::from(features); - config.max_wasm_stack(1024 * 1024 * 1024); // wasm stack metering is implemented by instrumentation, we don't want wasmtime to trap before that - config + #[tracing::instrument(target = "vm", level = "debug", "WasmtimeVM::compile_uncached", skip_all)] + fn compile_uncached(&self, code: &ContractCode) -> Result, CompilationError> { + let start = std::time::Instant::now(); + let prepared_code = prepare::prepare_contract(code.code(), &self.config, VMKind::Wasmtime) + .map_err(CompilationError::PrepareError)?; + let serialized = self.engine.precompile_module(&prepared_code).map_err(|err| { + tracing::error!(?err, "wasmtime failed to compile the prepared code (this is defense-in-depth, the error was recovered from but should be reported to the developers)"); + CompilationError::WasmtimeCompileError { msg: err.to_string() } + }); + crate::metrics::compilation_duration(VMKind::Wasmtime, start.elapsed()); + serialized } -} -impl crate::runner::VM for WasmtimeVM { - fn run( + fn compile_and_cache( &self, - _code_hash: CryptoHash, - code: Option<&ContractCode>, - method_name: &str, - ext: &mut dyn External, - context: &VMContext, - fees_config: &RuntimeFeesConfig, - promise_results: &[PromiseResult], - _cache: Option<&dyn ContractRuntimeCache>, - ) -> Result { - let Some(code) = code else { - return Err(VMRunnerError::CacheError(CacheError::ReadError(std::io::Error::from( - std::io::ErrorKind::NotFound, - )))); + code: &ContractCode, + cache: &dyn ContractRuntimeCache, + ) -> Result, CompilationError>, CacheError> { + let serialized_or_error = self.compile_uncached(code); + let key = get_contract_cache_key(*code.hash(), &self.config); + let record = CompiledContractInfo { + wasm_bytes: code.code().len() as u64, + compiled: match &serialized_or_error { + Ok(serialized) => CompiledContract::Code(serialized.clone()), + Err(err) => CompiledContract::CompileModuleError(err.clone()), + }, }; - let mut config = self.default_wasmtime_config(); - let engine = get_engine(&mut config); - let mut store = Store::new(&engine, ()); - let mut memory = WasmtimeMemory::new( - &mut store, - self.config.limit_config.initial_memory_pages, - self.config.limit_config.max_memory_pages, - ) - .unwrap(); - let memory_copy = memory.0; - let mut logic = - VMLogic::new(ext, context, &self.config, fees_config, promise_results, &mut memory); + cache.put(&key, record).map_err(CacheError::WriteError)?; + Ok(serialized_or_error) + } - let result = logic.before_loading_executable(method_name, code.code().len() as u64); - if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); - } + fn with_compiled_and_loaded( + &self, + cache: &dyn ContractRuntimeCache, + ext: &dyn External, + context: &VMContext, + closure: impl FnOnce(ExecutionResultState, Module) -> VMResult, + ) -> VMResult { + let code_hash = ext.code_hash(); + type MemoryCacheType = (u64, Result); + let to_any = |v: MemoryCacheType| -> Box { Box::new(v) }; + let (wasm_bytes, module_result) = cache.memory_cache().try_lookup( + code_hash, + || { + let key = get_contract_cache_key(code_hash, &self.config); + let cache_record = cache.get(&key).map_err(CacheError::ReadError)?; + let Some(compiled_contract_info) = cache_record else { + let Some(code) = ext.get_contract() else { + return Err(VMRunnerError::ContractCodeNotPresent); + }; + return Ok(to_any(( + code.code().len() as u64, + match self.compile_and_cache(&code, cache)? { + Ok(serialized_module) => Ok(unsafe { + Module::deserialize(&self.engine, serialized_module) + .map_err(|err| VMRunnerError::LoadingError(err.to_string()))? + }), + Err(err) => Err(err), + }, + ))); + }; + match &compiled_contract_info.compiled { + CompiledContract::CompileModuleError(err) => Ok::<_, VMRunnerError>(to_any(( + compiled_contract_info.wasm_bytes, + Err(err.clone()), + ))), + CompiledContract::Code(serialized_module) => { + unsafe { + // (UN-)SAFETY: the `serialized_module` must have been produced by + // a prior call to `serialize`. + // + // In practice this is not necessarily true. One could have + // forgotten to change the cache key when upgrading the version of + // the near_vm library or the database could have had its data + // corrupted while at rest. + // + // There should definitely be some validation in near_vm to ensure + // we load what we think we load. + let module = Module::deserialize(&self.engine, &serialized_module) + .map_err(|err| VMRunnerError::LoadingError(err.to_string()))?; + Ok(to_any((compiled_contract_info.wasm_bytes, Ok(module)))) + } + } + } + }, + move |value| { + let &(wasm_bytes, ref downcast) = value + .downcast_ref::() + .expect("downcast should always succeed"); - let prepared_code = - match prepare::prepare_contract(code.code(), &self.config, VMKind::Wasmtime) { - Ok(code) => code, - Err(err) => return Ok(VMOutcome::abort(logic, FunctionCallError::from(err))), - }; - let start = std::time::Instant::now(); - let module = match Module::new(&engine, prepared_code) { - Ok(module) => module, - Err(err) => return Ok(VMOutcome::abort(logic, err.into_vm_error()?)), - }; - crate::metrics::compilation_duration(VMKind::Wasmtime, start.elapsed()); - let mut linker = Linker::new(&engine); + (wasm_bytes, downcast.clone()) + }, + )?; - let result = logic.after_loading_executable(code.code().len() as u64); + let mut result_state = ExecutionResultState::new(&context, Arc::clone(&self.config)); + let result = result_state.before_loading_executable(&context.method, wasm_bytes); if let Err(e) = result { - return Ok(VMOutcome::abort(logic, e)); + return Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e))); + } + match module_result { + Ok(module) => { + let result = result_state.after_loading_executable(wasm_bytes); + if let Err(e) = result { + return Ok(PreparedContract::Outcome(VMOutcome::abort(result_state, e))); + } + closure(result_state, module) + } + Err(e) => Ok(PreparedContract::Outcome(VMOutcome::abort( + result_state, + FunctionCallError::CompilationError(e), + ))), } + } +} + +impl crate::runner::VM for WasmtimeVM { + fn precompile( + &self, + code: &ContractCode, + cache: &dyn ContractRuntimeCache, + ) -> Result< + Result, + crate::logic::errors::CacheError, + > { + Ok(self + .compile_and_cache(code, cache)? + .map(|_| ContractPrecompilatonResult::ContractCompiled)) + } - imports::wasmtime::link(&mut linker, memory_copy, &store, &mut logic); - match module.get_export(method_name) { - Some(export) => match export { - Func(func_type) => { - if func_type.params().len() != 0 || func_type.results().len() != 0 { - let err = FunctionCallError::MethodResolveError( - MethodResolveError::MethodInvalidSignature, - ); - return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol(logic, err)); + fn prepare( + self: Box, + ext: &dyn External, + context: &VMContext, + cache: Option<&dyn ContractRuntimeCache>, + ) -> Box { + let cache = cache.unwrap_or(&NoContractRuntimeCache); + let prepd = self.with_compiled_and_loaded(cache, ext, context, |result_state, module| { + match module.get_export(&context.method) { + Some(export) => match export { + Func(func_type) => { + if func_type.params().len() != 0 || func_type.results().len() != 0 { + let err = FunctionCallError::MethodResolveError( + MethodResolveError::MethodInvalidSignature, + ); + return Ok(PreparedContract::Outcome( + VMOutcome::abort_but_nop_outcome_in_old_protocol(result_state, err), + )); + } } - } - _ => { - return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol( - logic, - FunctionCallError::MethodResolveError(MethodResolveError::MethodNotFound), + _ => { + return Ok(PreparedContract::Outcome( + VMOutcome::abort_but_nop_outcome_in_old_protocol( + result_state, + FunctionCallError::MethodResolveError( + MethodResolveError::MethodNotFound, + ), + ), + )); + } + }, + None => { + return Ok(PreparedContract::Outcome( + VMOutcome::abort_but_nop_outcome_in_old_protocol( + result_state, + FunctionCallError::MethodResolveError( + MethodResolveError::MethodNotFound, + ), + ), )); } - }, - None => { - return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol( - logic, - FunctionCallError::MethodResolveError(MethodResolveError::MethodNotFound), - )); } - } + + let mut store = Store::new(&self.engine, ()); + let memory = WasmtimeMemory::new( + &mut store, + self.config.limit_config.initial_memory_pages, + self.config.limit_config.max_memory_pages, + ) + .unwrap(); + Ok(PreparedContract::Ready(ReadyContract { store, memory, module, result_state })) + }); + Box::new(prepd) + } +} + +struct ReadyContract { + store: Store<()>, + memory: WasmtimeMemory, + module: Module, + result_state: ExecutionResultState, +} + +#[allow(clippy::large_enum_variant)] +enum PreparedContract { + Outcome(VMOutcome), + Ready(ReadyContract), +} + +impl crate::PreparedContract for VMResult { + fn run( + self: Box, + ext: &mut dyn External, + context: &VMContext, + fees_config: Arc, + ) -> VMResult { + let ReadyContract { mut store, memory, module, result_state } = match (*self)? { + PreparedContract::Outcome(outcome) => return Ok(outcome), + PreparedContract::Ready(r) => r, + }; + let memory_copy = memory.0; + let config = Arc::clone(&result_state.config); + let mut logic = VMLogic::new(ext, context, fees_config, result_state, memory); + let engine = store.engine(); + let mut linker = Linker::new(engine); + // TODO: config could be accessed through `logic.result_state`, without this code having to + // figure it out... + link(&mut linker, memory_copy, &store, &config, &mut logic); match linker.instantiate(&mut store, &module) { - Ok(instance) => match instance.get_func(&mut store, method_name) { + Ok(instance) => match instance.get_func(&mut store, &context.method) { Some(func) => match func.typed::<(), ()>(&mut store) { Ok(run) => match run.call(&mut store, ()) { - Ok(_) => Ok(VMOutcome::ok(logic)), - Err(err) => Ok(VMOutcome::abort(logic, err.into_vm_error()?)), + Ok(_) => Ok(VMOutcome::ok(logic.result_state)), + Err(err) => Ok(VMOutcome::abort(logic.result_state, err.into_vm_error()?)), }, - Err(err) => Ok(VMOutcome::abort(logic, err.into_vm_error()?)), + Err(err) => Ok(VMOutcome::abort(logic.result_state, err.into_vm_error()?)), }, None => { return Ok(VMOutcome::abort_but_nop_outcome_in_old_protocol( - logic, + logic.result_state, FunctionCallError::MethodResolveError(MethodResolveError::MethodNotFound), )); } }, - Err(err) => Ok(VMOutcome::abort(logic, err.into_vm_error()?)), + Err(err) => Ok(VMOutcome::abort(logic.result_state, err.into_vm_error()?)), } } +} - fn precompile( - &self, - _code: &ContractCode, - _cache: &dyn ContractRuntimeCache, - ) -> Result< - Result, - crate::logic::errors::CacheError, - > { - Ok(Ok(ContractPrecompilatonResult::CacheNotAvailable)) +/// This is a container from which an error can be taken out by value. This is necessary as +/// `anyhow` does not really give any opportunity to grab causes by value and the VM Logic +/// errors end up a couple layers deep in a causal chain. +#[derive(Debug)] +pub(crate) struct ErrorContainer(std::sync::Mutex>); +impl ErrorContainer { + pub(crate) fn take(&self) -> Option { + let mut guard = self.0.lock().unwrap_or_else(|e| e.into_inner()); + guard.take() + } +} +impl std::error::Error for ErrorContainer {} +impl std::fmt::Display for ErrorContainer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("VMLogic error occurred and is now stored in an opaque storage container") + } +} + +thread_local! { + static CALLER_CONTEXT: UnsafeCell<*mut c_void> = const { UnsafeCell::new(core::ptr::null_mut()) }; +} + +fn link<'a, 'b>( + linker: &mut wasmtime::Linker<()>, + memory: wasmtime::Memory, + store: &wasmtime::Store<()>, + config: &Config, + logic: &'a mut VMLogic<'b>, +) { + // Unfortunately, due to the Wasmtime implementation we have to do tricks with the + // lifetimes of the logic instance and pass raw pointers here. + // FIXME(nagisa): I believe this is no longer required, we just need to look at this code + // again. + let raw_logic = logic as *mut _ as *mut c_void; + CALLER_CONTEXT.with(|caller_context| unsafe { *caller_context.get() = raw_logic }); + linker.define(store, "env", "memory", memory).expect("cannot define memory"); + + macro_rules! add_import { + ( + $mod:ident / $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > + ) => { + #[allow(unused_parens)] + fn $name(caller: wasmtime::Caller<'_, ()>, $( $arg_name: $arg_type ),* ) -> anyhow::Result<($( $returns ),*)> { + const TRACE: bool = imports::should_trace_host_function(stringify!($name)); + let _span = TRACE.then(|| { + tracing::trace_span!(target: "vm::host_function", stringify!($name)).entered() + }); + // the below is bad. don't do this at home. it probably works thanks to the exact way the system is setup. + // Thanksfully, this doesn't run in production, and hopefully should be possible to remove before we even + // consider doing so. + let data = CALLER_CONTEXT.with(|caller_context| { + unsafe { + *caller_context.get() + } + }); + unsafe { + // Transmute the lifetime of caller so it's possible to put it in a thread-local. + #[allow(clippy::missing_transmute_annotations)] + crate::wasmtime_runner::CALLER.with(|runner_caller| *runner_caller.borrow_mut() = std::mem::transmute(caller)); + } + let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) }; + match logic.$func( $( $arg_name as $arg_type, )* ) { + Ok(result) => Ok(result as ($( $returns ),* ) ), + Err(err) => { + Err(ErrorContainer(std::sync::Mutex::new(Some(err))).into()) + } + } + } + + linker.func_wrap(stringify!($mod), stringify!($name), $name).expect("cannot link external"); + }; } + imports::for_each_available_import!(config, add_import); } diff --git a/runtime/near-vm/compiler/README.md b/runtime/near-vm/compiler/README.md index c1cde73fd23..ad0d5bd2892 100644 --- a/runtime/near-vm/compiler/README.md +++ b/runtime/near-vm/compiler/README.md @@ -54,4 +54,4 @@ attributions of the project. [`cranelift-wasm`]: https://crates.io/crates/cranelift-wasm -[Wasmer `ATTRIBUTIONS`]: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +[Wasmer `ATTRIBUTIONS`]: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md diff --git a/runtime/near-vm/compiler/src/function.rs b/runtime/near-vm/compiler/src/function.rs index 7b99eb1fe32..c0b4728f01e 100644 --- a/runtime/near-vm/compiler/src/function.rs +++ b/runtime/near-vm/compiler/src/function.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! A `Compilation` contains the compiled function bodies for a WebAssembly //! module (`CompiledFunction`). diff --git a/runtime/near-vm/compiler/src/sourceloc.rs b/runtime/near-vm/compiler/src/sourceloc.rs index b2b091e5d95..bd57d27e0c7 100644 --- a/runtime/near-vm/compiler/src/sourceloc.rs +++ b/runtime/near-vm/compiler/src/sourceloc.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Source locations. //! diff --git a/runtime/near-vm/compiler/src/translator/environ.rs b/runtime/near-vm/compiler/src/translator/environ.rs index e10fb409429..d6e94455406 100644 --- a/runtime/near-vm/compiler/src/translator/environ.rs +++ b/runtime/near-vm/compiler/src/translator/environ.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md use super::state::ModuleTranslationState; use crate::lib::std::borrow::ToOwned; use crate::lib::std::string::ToString; @@ -60,7 +60,7 @@ impl<'data> ModuleEnvironment<'data> { /// Translate a wasm module using this environment. This consumes the /// `ModuleEnvironment` and produces a `ModuleInfoTranslation`. #[tracing::instrument(target = "near_vm", level = "trace", skip_all)] - pub fn translate(mut self, data: &'data [u8]) -> WasmResult> { + pub fn translate(mut self, data: &'data [u8]) -> WasmResult { assert!(self.module_translation_state.is_none()); let module_translation_state = translate_module(data, &mut self)?; self.module_translation_state = Some(module_translation_state); diff --git a/runtime/near-vm/compiler/src/translator/module.rs b/runtime/near-vm/compiler/src/translator/module.rs index 1bf29118f84..f25d94f702a 100644 --- a/runtime/near-vm/compiler/src/translator/module.rs +++ b/runtime/near-vm/compiler/src/translator/module.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Translation skeleton that traverses the whole WebAssembly module and call helper functions //! to deal with each part of it. diff --git a/runtime/near-vm/compiler/src/translator/sections.rs b/runtime/near-vm/compiler/src/translator/sections.rs index de50fc42fc2..577e65df316 100644 --- a/runtime/near-vm/compiler/src/translator/sections.rs +++ b/runtime/near-vm/compiler/src/translator/sections.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Helper functions to gather information for each of the non-function sections of a //! WebAssembly module. diff --git a/runtime/near-vm/compiler/src/translator/state.rs b/runtime/near-vm/compiler/src/translator/state.rs index e9b73ae33d6..e10a51845cb 100644 --- a/runtime/near-vm/compiler/src/translator/state.rs +++ b/runtime/near-vm/compiler/src/translator/state.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md use near_vm_types::entity::PrimaryMap; use near_vm_types::{FunctionIndex, ImportIndex, ModuleInfo, SignatureIndex}; diff --git a/runtime/near-vm/engine/README.md b/runtime/near-vm/engine/README.md index 90669f8ce11..1c98575d65c 100644 --- a/runtime/near-vm/engine/README.md +++ b/runtime/near-vm/engine/README.md @@ -19,4 +19,4 @@ Please check [Wasmer `ATTRIBUTIONS`] to further see licenses and other attributions of the project. [`wasmtime-api`]: https://crates.io/crates/wasmtime -[Wasmer `ATTRIBUTIONS`]: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +[Wasmer `ATTRIBUTIONS`]: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md diff --git a/runtime/near-vm/engine/src/universal/code_memory.rs b/runtime/near-vm/engine/src/universal/code_memory.rs index b88aa80c2a0..baed92f045a 100644 --- a/runtime/near-vm/engine/src/universal/code_memory.rs +++ b/runtime/near-vm/engine/src/universal/code_memory.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Memory management for executable code. use near_vm_compiler::CompileError; diff --git a/runtime/near-vm/engine/src/universal/engine.rs b/runtime/near-vm/engine/src/universal/engine.rs index 8f2aaa41782..bd77ff3f09f 100644 --- a/runtime/near-vm/engine/src/universal/engine.rs +++ b/runtime/near-vm/engine/src/universal/engine.rs @@ -622,7 +622,9 @@ impl UniversalEngineInner { // As lifetime annotations in Rust cannot influence the codegen, this is not a // source of undefined behaviour but we do lose static lifetime checks that Rust // enforces. - std::mem::transmute::<_, VMTrampoline>(code_memory.executable_address(offset)) + std::mem::transmute::<*const u8, VMTrampoline>( + code_memory.executable_address(offset), + ) }; allocated_function_call_trampolines.push(trampoline); } diff --git a/runtime/near-vm/engine/src/universal/executable.rs b/runtime/near-vm/engine/src/universal/executable.rs index 1eff75377c1..23717594e2b 100644 --- a/runtime/near-vm/engine/src/universal/executable.rs +++ b/runtime/near-vm/engine/src/universal/executable.rs @@ -58,9 +58,7 @@ impl<'a> UniversalExecutableRef<'a> { /// Right now we are not doing any extra work for validation, but /// `rkyv` has an option to do bytecheck on the serialized data before /// serializing (via `rkyv::check_archived_value`). - pub unsafe fn deserialize( - data: &'a [u8], - ) -> Result, DeserializeError> { + pub unsafe fn deserialize(data: &'a [u8]) -> Result { Self::verify_serialized(data).map_err(|e| DeserializeError::Incompatible(e.to_string()))?; let (archive, position) = data.split_at(data.len() - 8); let mut position_value = [0u8; 8]; diff --git a/runtime/near-vm/test-api/src/sys/externals/function.rs b/runtime/near-vm/test-api/src/sys/externals/function.rs index 95eeb9d6b6e..d9b37ef716b 100644 --- a/runtime/near-vm/test-api/src/sys/externals/function.rs +++ b/runtime/near-vm/test-api/src/sys/externals/function.rs @@ -116,6 +116,7 @@ fn build_export_function_metadata( where Env: Clone + Sized + 'static + Send + Sync, { + #[allow(clippy::missing_transmute_annotations)] let import_init_function_ptr = Some(unsafe { std::mem::transmute::<_, ImportInitializerFuncPtr>(import_init_function_ptr) }); @@ -1556,7 +1557,11 @@ mod inner { #[test] fn test_function_pointer() { let f = Function::new(func_i32__i32); - let function = unsafe { std::mem::transmute::<_, fn(usize, i32) -> i32>(f.address) }; + let function = unsafe { + std::mem::transmute::<*const near_vm_vm::VMFunctionBody, fn(usize, i32) -> i32>( + f.address, + ) + }; assert_eq!(function(0, 3), 6); } } diff --git a/runtime/near-vm/test-api/src/sys/native.rs b/runtime/near-vm/test-api/src/sys/native.rs index 0b2a2a37bcc..0a26ef1231a 100644 --- a/runtime/near-vm/test-api/src/sys/native.rs +++ b/runtime/near-vm/test-api/src/sys/native.rs @@ -171,6 +171,7 @@ macro_rules! impl_native_traits { match self.arg_kind() { VMFunctionKind::Static => { let results = catch_unwind(AssertUnwindSafe(|| unsafe { + #[allow(clippy::missing_transmute_annotations)] let f = std::mem::transmute::<_, unsafe extern "C" fn( VMFunctionEnvironment, $( $x, )*) -> Rets::CStruct>(self.address()); // We always pass the vmctx f( self.vmctx(), $( $x, )* ) diff --git a/runtime/near-vm/test-generator/src/lib.rs b/runtime/near-vm/test-generator/src/lib.rs index bf69fa6bf86..d6ee707e4bd 100644 --- a/runtime/near-vm/test-generator/src/lib.rs +++ b/runtime/near-vm/test-generator/src/lib.rs @@ -4,7 +4,7 @@ //! to automatically run the files in parallel. //! //! > This program is inspired/forked from: -//! > https://github.com/bytecodealliance/wasmtime/blob/master/build.rs +//! > mod processors; pub use crate::processors::{emscripten_processor, wasi_processor, wast_processor}; diff --git a/runtime/near-vm/types/README.md b/runtime/near-vm/types/README.md index 54608f280d2..1706b93d408 100644 --- a/runtime/near-vm/types/README.md +++ b/runtime/near-vm/types/README.md @@ -27,4 +27,4 @@ Among other things, it defines the following _types_: This project borrowed some of the code for the entity structure from [cranelift-entity](https://crates.io/crates/cranelift-entity). We decided to move it here to help on serialization/deserialization and also to ease the integration with other tools like `loupe`. -Please check [Wasmer ATTRIBUTIONS](https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md) to further see licenses and other attributions of the project. +Please check [Wasmer ATTRIBUTIONS](https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md) to further see licenses and other attributions of the project. diff --git a/runtime/near-vm/types/src/entity/boxed_slice.rs b/runtime/near-vm/types/src/entity/boxed_slice.rs index c801f134dc1..512262f722e 100644 --- a/runtime/near-vm/types/src/entity/boxed_slice.rs +++ b/runtime/near-vm/types/src/entity/boxed_slice.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Boxed slices for `PrimaryMap`. diff --git a/runtime/near-vm/types/src/entity/iter.rs b/runtime/near-vm/types/src/entity/iter.rs index 1e770325ca9..2d1cae605b9 100644 --- a/runtime/near-vm/types/src/entity/iter.rs +++ b/runtime/near-vm/types/src/entity/iter.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! A double-ended iterator over entity references and entities. diff --git a/runtime/near-vm/types/src/entity/keys.rs b/runtime/near-vm/types/src/entity/keys.rs index 9e15157fecd..d1a6c087035 100644 --- a/runtime/near-vm/types/src/entity/keys.rs +++ b/runtime/near-vm/types/src/entity/keys.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! A double-ended iterator over entity references. //! diff --git a/runtime/near-vm/types/src/entity/mod.rs b/runtime/near-vm/types/src/entity/mod.rs index 9412fa76c12..73226af7a70 100644 --- a/runtime/near-vm/types/src/entity/mod.rs +++ b/runtime/near-vm/types/src/entity/mod.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md /// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key /// of an `SecondaryMap` or `SparseMap`. diff --git a/runtime/near-vm/types/src/entity/packed_option.rs b/runtime/near-vm/types/src/entity/packed_option.rs index 44db4ea4d91..63321bcfb4a 100644 --- a/runtime/near-vm/types/src/entity/packed_option.rs +++ b/runtime/near-vm/types/src/entity/packed_option.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Compact representation of `Option` for types with a reserved value. //! diff --git a/runtime/near-vm/types/src/entity/primary_map.rs b/runtime/near-vm/types/src/entity/primary_map.rs index b617b3a2a43..26fc04ac5d1 100644 --- a/runtime/near-vm/types/src/entity/primary_map.rs +++ b/runtime/near-vm/types/src/entity/primary_map.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Densely numbered entity references as mapping keys. use rkyv::Archive; diff --git a/runtime/near-vm/types/src/entity/secondary_map.rs b/runtime/near-vm/types/src/entity/secondary_map.rs index 288a8534768..e664e831311 100644 --- a/runtime/near-vm/types/src/entity/secondary_map.rs +++ b/runtime/near-vm/types/src/entity/secondary_map.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Densely numbered entity references as mapping keys. diff --git a/runtime/near-vm/types/src/module.rs b/runtime/near-vm/types/src/module.rs index 785da0c2f8e..14bfc0de207 100644 --- a/runtime/near-vm/types/src/module.rs +++ b/runtime/near-vm/types/src/module.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Data structure for representing WebAssembly modules in a //! `wasmer::Module`. diff --git a/runtime/near-vm/vm/README.md b/runtime/near-vm/vm/README.md index 9ee5ac2a66f..b669f606839 100644 --- a/runtime/near-vm/vm/README.md +++ b/runtime/near-vm/vm/README.md @@ -25,4 +25,4 @@ directly. The `wasmer` crate provides types that embed types from This project borrowed some of the code for the VM structure and trapping from the [wasmtime-runtime](https://crates.io/crates/wasmtime-runtime). -Please check [Wasmer ATTRIBUTIONS](https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md) to further see licenses and other attributions of the project. +Please check [Wasmer ATTRIBUTIONS](https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md) to further see licenses and other attributions of the project. diff --git a/runtime/near-vm/vm/src/export/mod.rs b/runtime/near-vm/vm/src/export/mod.rs index 766b7d34c49..306dd6f9415 100644 --- a/runtime/near-vm/vm/src/export/mod.rs +++ b/runtime/near-vm/vm/src/export/mod.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md use crate::global::Global; use crate::instance::WeakOrStrongInstanceRef; diff --git a/runtime/near-vm/vm/src/imports.rs b/runtime/near-vm/vm/src/imports.rs index fdb36404cea..1d30a8b1eb1 100644 --- a/runtime/near-vm/vm/src/imports.rs +++ b/runtime/near-vm/vm/src/imports.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md use crate::instance::ImportFunctionEnv; use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport}; diff --git a/runtime/near-vm/vm/src/instance/mod.rs b/runtime/near-vm/vm/src/instance/mod.rs index 0722e6d27b9..df1fc1152ba 100644 --- a/runtime/near-vm/vm/src/instance/mod.rs +++ b/runtime/near-vm/vm/src/instance/mod.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! An `Instance` contains all the runtime state used by execution of //! a WebAssembly module (except its callstack and register state). An diff --git a/runtime/near-vm/vm/src/lib.rs b/runtime/near-vm/vm/src/lib.rs index fff47009e43..f45a96df667 100644 --- a/runtime/near-vm/vm/src/lib.rs +++ b/runtime/near-vm/vm/src/lib.rs @@ -1,7 +1,6 @@ //! Runtime library support for Wasmer. #![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] -#![deny(trivial_numeric_casts, unused_extern_crates)] #![warn(unused_import_braces)] #![allow(clippy::new_without_default)] #![warn( diff --git a/runtime/near-vm/vm/src/libcalls.rs b/runtime/near-vm/vm/src/libcalls.rs index 84576eba5db..7256c2b68c6 100644 --- a/runtime/near-vm/vm/src/libcalls.rs +++ b/runtime/near-vm/vm/src/libcalls.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Runtime library calls. //! diff --git a/runtime/near-vm/vm/src/memory/mod.rs b/runtime/near-vm/vm/src/memory/mod.rs index 0bf4f044a2b..0f8ccb9b9ae 100644 --- a/runtime/near-vm/vm/src/memory/mod.rs +++ b/runtime/near-vm/vm/src/memory/mod.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Memory management for linear memories. //! diff --git a/runtime/near-vm/vm/src/mmap.rs b/runtime/near-vm/vm/src/mmap.rs index f89681c1d5e..f301fe3e0ff 100644 --- a/runtime/near-vm/vm/src/mmap.rs +++ b/runtime/near-vm/vm/src/mmap.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Low-level abstraction for allocating and managing zero-filled pages //! of memory. diff --git a/runtime/near-vm/vm/src/probestack.rs b/runtime/near-vm/vm/src/probestack.rs index cba4389d322..96297c92309 100644 --- a/runtime/near-vm/vm/src/probestack.rs +++ b/runtime/near-vm/vm/src/probestack.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! This section defines the `PROBESTACK` intrinsic which is used in the //! implementation of "stack probes" on certain platforms. diff --git a/runtime/near-vm/vm/src/sig_registry.rs b/runtime/near-vm/vm/src/sig_registry.rs index 9e00d8a174f..e0f272aade6 100644 --- a/runtime/near-vm/vm/src/sig_registry.rs +++ b/runtime/near-vm/vm/src/sig_registry.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Implement a registry of function signatures, for fast indirect call //! signature checking. diff --git a/runtime/near-vm/vm/src/table.rs b/runtime/near-vm/vm/src/table.rs index ed58dcb915a..909e8a6a887 100644 --- a/runtime/near-vm/vm/src/table.rs +++ b/runtime/near-vm/vm/src/table.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Memory management for tables. //! diff --git a/runtime/near-vm/vm/src/trap/handlers.c b/runtime/near-vm/vm/src/trap/handlers.c index f9df9f321c4..6a74d8645e7 100644 --- a/runtime/near-vm/vm/src/trap/handlers.c +++ b/runtime/near-vm/vm/src/trap/handlers.c @@ -1,5 +1,5 @@ // This file contains partial code from other sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md #include #include diff --git a/runtime/near-vm/vm/src/trap/mod.rs b/runtime/near-vm/vm/src/trap/mod.rs index 43c09862dc5..075dd6d3bb1 100644 --- a/runtime/near-vm/vm/src/trap/mod.rs +++ b/runtime/near-vm/vm/src/trap/mod.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! This is the module that facilitates the usage of Traps //! in Wasmer Runtime diff --git a/runtime/near-vm/vm/src/trap/trapcode.rs b/runtime/near-vm/vm/src/trap/trapcode.rs index 5f929a14f90..6f6ba543a84 100644 --- a/runtime/near-vm/vm/src/trap/trapcode.rs +++ b/runtime/near-vm/vm/src/trap/trapcode.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Trap codes describing the reason for a trap. diff --git a/runtime/near-vm/vm/src/trap/traphandlers.rs b/runtime/near-vm/vm/src/trap/traphandlers.rs index 608c36aad90..246e0949038 100644 --- a/runtime/near-vm/vm/src/trap/traphandlers.rs +++ b/runtime/near-vm/vm/src/trap/traphandlers.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! WebAssembly trap handling, which is built on top of the lower-level //! signalhandling mechanisms. @@ -150,9 +150,10 @@ pub unsafe fn near_vm_call_trampoline( values_vec: *mut u8, ) -> Result<(), Trap> { catch_traps(|| { - mem::transmute::<_, extern "C" fn(VMFunctionEnvironment, *const VMFunctionBody, *mut u8)>( - trampoline, - )(callee_env, callee, values_vec); + mem::transmute::< + VMTrampoline, + extern "C" fn(VMFunctionEnvironment, *const VMFunctionBody, *mut u8), + >(trampoline)(callee_env, callee, values_vec); }) } diff --git a/runtime/near-vm/vm/src/vmcontext.rs b/runtime/near-vm/vm/src/vmcontext.rs index bfdeba5876e..e38d6f324cb 100644 --- a/runtime/near-vm/vm/src/vmcontext.rs +++ b/runtime/near-vm/vm/src/vmcontext.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! This file declares `VMContext` and several related structs which contain //! fields that compiled wasm code accesses directly. diff --git a/runtime/near-vm/vm/src/vmoffsets.rs b/runtime/near-vm/vm/src/vmoffsets.rs index be7bd6ed61f..cd8ae5be650 100644 --- a/runtime/near-vm/vm/src/vmoffsets.rs +++ b/runtime/near-vm/vm/src/vmoffsets.rs @@ -1,5 +1,5 @@ // This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md +// Attributions: https://github.com/wasmerio/wasmer/blob/2.3.0/ATTRIBUTIONS.md //! Offsets and sizes of various structs in near_vm-vm's vmcontext //! module. @@ -451,11 +451,11 @@ impl VMOffsets { /// Offsets for [`VMSharedSignatureIndex`]. /// -/// [`VMSharedSignatureIndex`]: crate::vmcontext::VMSharedSignatureIndex +/// [`VMSharedSignatureIndex`]: crate::sig_registry::VMSharedSignatureIndex impl VMOffsets { /// Return the size of [`VMSharedSignatureIndex`]. /// - /// [`VMSharedSignatureIndex`]: crate::vmcontext::VMSharedSignatureIndex + /// [`VMSharedSignatureIndex`]: crate::sig_registry::VMSharedSignatureIndex pub const fn size_of_vmshared_signature_index(&self) -> u8 { 4 } @@ -588,7 +588,7 @@ impl VMOffsets { /// Return the offset to [`VMSharedSignatureIndex`] index `index`. /// - /// [`VMSharedSignatureIndex`]: crate::vmcontext::VMSharedSignatureIndex + /// [`VMSharedSignatureIndex`]: crate::sig_registry::VMSharedSignatureIndex // Remember updating precompute upon changes pub fn vmctx_vmshared_signature_id(&self, index: SignatureIndex) -> u32 { assert_lt!(index.as_u32(), self.num_signature_ids); diff --git a/runtime/near-wallet-contract/implementation/Cargo.lock b/runtime/near-wallet-contract/implementation/Cargo.lock index 0ee31e8d452..f9df40ebb80 100644 --- a/runtime/near-wallet-contract/implementation/Cargo.lock +++ b/runtime/near-wallet-contract/implementation/Cargo.lock @@ -902,16 +902,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rand_core 0.6.4", "rustc_version", "subtle", @@ -1181,6 +1180,7 @@ dependencies = [ "base64 0.21.7", "ethabi", "hex", + "near-contract-standards", "near-crypto 0.21.2", "near-sdk", "near-workspaces", @@ -2242,6 +2242,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-contract-standards" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d60a794d819e96fb307eb9afe5dd5d5c5dc7a73ce46eb8a94a0f40c387a165b" +dependencies = [ + "near-sdk", +] + [[package]] name = "near-crypto" version = "0.20.1" @@ -3058,12 +3067,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "platforms" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" - [[package]] name = "powerfmt" version = "0.2.0" diff --git a/runtime/near-wallet-contract/implementation/Cargo.toml b/runtime/near-wallet-contract/implementation/Cargo.toml index 965ab3747d0..cfc629d78a8 100644 --- a/runtime/near-wallet-contract/implementation/Cargo.toml +++ b/runtime/near-wallet-contract/implementation/Cargo.toml @@ -12,6 +12,7 @@ aurora-engine-transactions = { version = "1.1", default-features = false, featur base64 = "0.21" ethabi = { version = "18", default-features = false } hex = "0.4" +near-contract-standards = "5.0" near-sdk = { version = "5.0" } once_cell = "1.18" serde = { version = "1", features = ["derive"] } diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/Cargo.toml b/runtime/near-wallet-contract/implementation/wallet-contract/Cargo.toml index fc8e36dbd84..56a637712d4 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/Cargo.toml +++ b/runtime/near-wallet-contract/implementation/wallet-contract/Cargo.toml @@ -14,6 +14,7 @@ aurora-engine-transactions.workspace = true base64.workspace = true ethabi.workspace = true hex.workspace = true +near-contract-standards.workspace = true near-sdk.workspace = true once_cell.workspace = true serde.workspace = true diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/eth_emulation.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/eth_emulation.rs index 4174e92acc3..008b6b2af7d 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/eth_emulation.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/eth_emulation.rs @@ -5,11 +5,11 @@ use crate::{ error::{Error, UserError}, ethabi_utils, - types::{Action, ExecutionContext}, + types::{Action, ExecutionContext, ParsableEthEmulationKind}, }; use aurora_engine_transactions::NormalizedEthTransaction; use ethabi::{Address, ParamType}; -use near_sdk::AccountId; +use near_sdk::{env, AccountId}; const FIVE_TERA_GAS: u64 = near_sdk::Gas::from_tgas(5).as_gas(); @@ -26,22 +26,12 @@ pub fn try_emulation( target: &AccountId, tx: &NormalizedEthTransaction, context: &ExecutionContext, -) -> Result { +) -> Result<(Action, ParsableEthEmulationKind), Error> { if tx.data.len() < 4 { return Err(Error::User(UserError::InvalidAbiEncodedData)); } - // In production eth-implicit accounts are top-level, so this suffix will - // always be empty. The purpose of finding a suffix is that it allows for - // testing environments where the wallet contract is deployed to an address - // that is a sub-account. For example, this allows testing on Near testnet - // before the eth-implicit accounts feature is stabilized. - // The suffix is only needed in testing. - let suffix = context - .current_account_id - .as_str() - .find('.') - .map(|index| &context.current_account_id.as_str()[index..]) - .unwrap_or(""); + + let suffix = context.current_account_suffix(); match &tx.data[0..4] { ERC20_BALANCE_OF_SELECTOR => { let (address,): (Address,) = @@ -52,32 +42,40 @@ pub fn try_emulation( // assumed to all be deployed to the same namespace so that they will all have the // same suffix. let args = format!(r#"{{"account_id": "0x{}{}"}}"#, hex::encode(address), suffix); - Ok(Action::FunctionCall { - receiver_id: target.to_string(), - method_name: "ft_balance_of".into(), - args: args.into_bytes(), - gas: FIVE_TERA_GAS, - yocto_near: 0, - }) + Ok(( + Action::FunctionCall { + receiver_id: target.to_string(), + method_name: "ft_balance_of".into(), + args: args.into_bytes(), + gas: FIVE_TERA_GAS, + yocto_near: 0, + }, + ParsableEthEmulationKind::ERC20Balance, + )) } ERC20_TRANSFER_SELECTOR => { // We intentionally map to `u128` instead of `U256` because the NEP-141 standard // is to use u128. let (to, value): (Address, u128) = ethabi_utils::abi_decode(&ERC20_TRANSFER_SIGNATURE, &tx.data[4..])?; + let receiver_id: AccountId = format!("0x{}{}", hex::encode(to), suffix) + .parse() + .unwrap_or_else(|_| env::panic_str("eth-implicit accounts are valid account ids")); let args = format!( - r#"{{"receiver_id": "0x{}{}", "amount": "{}", "memo": null}}"#, - hex::encode(to), - suffix, + r#"{{"receiver_id": "{}", "amount": "{}", "memo": null}}"#, + receiver_id.as_str(), value ); - Ok(Action::FunctionCall { - receiver_id: target.to_string(), - method_name: "ft_transfer".into(), - args: args.into_bytes(), - gas: 2 * FIVE_TERA_GAS, - yocto_near: 1, - }) + Ok(( + Action::FunctionCall { + receiver_id: target.to_string(), + method_name: "ft_transfer".into(), + args: args.into_bytes(), + gas: 2 * FIVE_TERA_GAS, + yocto_near: 1, + }, + ParsableEthEmulationKind::ERC20Transfer { receiver_id }, + )) } _ => Err(Error::User(UserError::UnknownFunctionSelector)), } diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/internal.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/internal.rs index 369d10da8a0..8d4332f92bb 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/internal.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/internal.rs @@ -2,9 +2,10 @@ use crate::{ error::{AccountIdError, CallerError, Error, RelayerError, UserError}, eth_emulation, ethabi_utils, near_action, types::{ - Action, ExecutionContext, TransactionValidationOutcome, ADD_KEY_SELECTOR, - ADD_KEY_SIGNATURE, DELETE_KEY_SELECTOR, DELETE_KEY_SIGNATURE, FUNCTION_CALL_SELECTOR, - FUNCTION_CALL_SIGNATURE, TRANSFER_SELECTOR, TRANSFER_SIGNATURE, + Action, EthEmulationKind, ExecutionContext, ParsableTransactionKind, TargetKind, + TransactionKind, ADD_KEY_SELECTOR, ADD_KEY_SIGNATURE, DELETE_KEY_SELECTOR, + DELETE_KEY_SIGNATURE, FUNCTION_CALL_SELECTOR, FUNCTION_CALL_SIGNATURE, TRANSFER_SELECTOR, + TRANSFER_SIGNATURE, }, }; use aurora_engine_transactions::{EthTransactionKind, NormalizedEthTransaction}; @@ -35,32 +36,97 @@ pub fn parse_rlp_tx_to_action( target: &AccountId, context: &ExecutionContext, expected_nonce: &mut u64, -) -> Result<(near_action::Action, TransactionValidationOutcome), Error> { +) -> Result<(near_action::Action, TransactionKind), Error> { let tx_bytes = decode_b64(tx_bytes_b64)?; let tx_kind: EthTransactionKind = tx_bytes.as_slice().try_into()?; let tx: NormalizedEthTransaction = tx_kind.try_into()?; - let validation_outcome = validate_tx_relayer_data(&tx, target, context, *expected_nonce)?; + let target_kind = validate_tx_relayer_data(&tx, target, context, *expected_nonce)?; // If the transaction is valid then increment the nonce to prevent replay *expected_nonce = expected_nonce.saturating_add(1); - let to = tx.to.ok_or(Error::User(UserError::EvmDeployDisallowed))?.raw(); - let action = if to != context.current_address - && extract_address(target).map(|a| a == to).unwrap_or(false) - { - // If target is another Ethereum implicit account then the action - // must be a transfer (because EOAs are not contracts on Ethereum). - Action::Transfer { receiver_id: target.to_string(), yocto_near: 0 } - } else { - parse_tx_data(target, &tx, context)? + // The way an honest relayer assigns `target` is as follows: + // 1. If the Ethereum transaction payload represents a Near action then use the receiver_id, + // 2. If the payload looks like a supported Ethereum emulation then use the address registrar: + // 2.a. if the tx.to address is registered then use the associated account id, + // 2.b. otherwise, tx.to == target + // 3. Otherwise, tx.to == target + // Given this algorithm, the only way to have `TargetKind::EthImplicit` is in the + // following cases: + // I) The Ethereum transaction payload is not parseable as a known action, + // II) The payload is parsable as a Near action and the receiver_id is an eth-implicit account + // III) The payload is parsable as a supported Ethereum emulation but the to address is + // not registered in the address registrar. + // Therefore, to determine if the relayer is honest we must always parse the payload and + // we only need to check the registrar if the payload is parseable as an Ethereum emulation. + // Note: the `TargetKind` is determined in `validate_tx_relayer_data` above, and that function + // also confirms that the `target` is compatible with the user's `tx.to`. + + let (action, transaction_kind) = match parse_tx_data(target, &tx, context) { + Ok((action, ParsableTransactionKind::NearNativeAction)) => { + (action, TransactionKind::NearNativeAction) + } + Ok((action, ParsableTransactionKind::SelfNearNativeAction)) => { + if let TargetKind::EthImplicit(_) = target_kind { + // The calldata was parseable as a Near native action where the target + // should be the current account, but the target is some other wallet contract. + // This is technically allowed under the Ethereum standard for base token transfers + // (where any calldata can be used when sending tokens to another EOA), so we + // assume such a transfer must have been the user's intent. No address check is + // required in this case because no Near account other than the current account + // can be the receiver of these actions. + ( + Action::Transfer { receiver_id: target.to_string(), yocto_near: 0 }, + TransactionKind::EthEmulation(EthEmulationKind::EOABaseTokenTransfer { + address_check: None, + }), + ) + } else { + (action, TransactionKind::NearNativeAction) + } + } + Ok((action, ParsableTransactionKind::EthEmulation(eth_emulation))) => { + if let TargetKind::EthImplicit(address) = target_kind { + // Even though the action was parsable, the target is another wallet contract, + // so the action _must_ still be a base token transfer, but we need + // to check if the target is not registered (otherwise the relayer is faulty). + ( + Action::Transfer { receiver_id: target.to_string(), yocto_near: 0 }, + TransactionKind::EthEmulation(EthEmulationKind::EOABaseTokenTransfer { + address_check: Some(address), + }), + ) + } else { + (action, TransactionKind::EthEmulation(eth_emulation.into())) + } + } + Err( + error @ (Error::User(UserError::InvalidAbiEncodedData) + | Error::User(UserError::UnknownFunctionSelector)), + ) => { + // Unparsable actions can still be base token transfers, but no + // registrar check is required. + if let TargetKind::EthImplicit(_) = target_kind { + ( + Action::Transfer { receiver_id: target.to_string(), yocto_near: 0 }, + TransactionKind::EthEmulation(EthEmulationKind::EOABaseTokenTransfer { + address_check: None, + }), + ) + } else { + return Err(error); + } + } + Err(other_err) => return Err(other_err), }; + validate_tx_value(&tx, context, &action)?; // Call to `low_u128` here is safe because of the validation done in `validate_tx_value` let near_action = action .try_into_near_action(tx.value.raw().low_u128().saturating_mul(MAX_YOCTO_NEAR.into()))?; - Ok((near_action, validation_outcome)) + Ok((near_action, transaction_kind)) } /// Extracts a 20-byte address from a Near account ID. @@ -116,11 +182,24 @@ pub fn keccak256(bytes: &[u8]) -> [u8; 32] { near_sdk::env::keccak256_array(bytes) } +fn parse_target(target: &AccountId, current_address: Address) -> TargetKind<'_> { + match extract_address(target) { + Ok(address) => { + if address == current_address { + TargetKind::CurrentAccount + } else { + TargetKind::EthImplicit(address) + } + } + Err(_) => TargetKind::OtherNearAccount(target), + } +} + fn parse_tx_data( target: &AccountId, tx: &NormalizedEthTransaction, context: &ExecutionContext, -) -> Result { +) -> Result<(Action, ParsableTransactionKind), Error> { if tx.data.len() < 4 { return Err(Error::User(UserError::InvalidAbiEncodedData)); } @@ -134,7 +213,10 @@ fn parse_tx_data( if yocto_near > MAX_YOCTO_NEAR { return Err(Error::User(UserError::ExcessYoctoNear)); } - Ok(Action::FunctionCall { receiver_id, method_name, args, gas, yocto_near }) + Ok(( + Action::FunctionCall { receiver_id, method_name, args, gas, yocto_near }, + ParsableTransactionKind::NearNativeAction, + )) } TRANSFER_SELECTOR => { let (receiver_id, yocto_near): (String, u32) = @@ -145,7 +227,10 @@ fn parse_tx_data( if yocto_near > MAX_YOCTO_NEAR { return Err(Error::User(UserError::ExcessYoctoNear)); } - Ok(Action::Transfer { receiver_id, yocto_near }) + Ok(( + Action::Transfer { receiver_id, yocto_near }, + ParsableTransactionKind::NearNativeAction, + )) } ADD_KEY_SELECTOR => { let ( @@ -158,23 +243,32 @@ fn parse_tx_data( receiver_id, method_names, ) = ethabi_utils::abi_decode(&ADD_KEY_SIGNATURE, &tx.data[4..])?; - Ok(Action::AddKey { - public_key_kind, - public_key, - nonce, - is_full_access, - is_limited_allowance, - allowance, - receiver_id, - method_names, - }) + Ok(( + Action::AddKey { + public_key_kind, + public_key, + nonce, + is_full_access, + is_limited_allowance, + allowance, + receiver_id, + method_names, + }, + ParsableTransactionKind::SelfNearNativeAction, + )) } DELETE_KEY_SELECTOR => { let (public_key_kind, public_key) = ethabi_utils::abi_decode(&DELETE_KEY_SIGNATURE, &tx.data[4..])?; - Ok(Action::DeleteKey { public_key_kind, public_key }) + Ok(( + Action::DeleteKey { public_key_kind, public_key }, + ParsableTransactionKind::SelfNearNativeAction, + )) + } + _ => { + let (action, emulation_kind) = eth_emulation::try_emulation(target, tx, context)?; + Ok((action, ParsableTransactionKind::EthEmulation(emulation_kind))) } - _ => eth_emulation::try_emulation(target, tx, context), } } @@ -184,12 +278,12 @@ fn parse_tx_data( /// - to address is present and matches the target address (or hash of target account ID) /// - nonce matches expected nonce /// If this validation fails then the relayer that sent it is faulty and should be banned. -fn validate_tx_relayer_data( +fn validate_tx_relayer_data<'a>( tx: &NormalizedEthTransaction, - target: &AccountId, + target: &'a AccountId, context: &ExecutionContext, expected_nonce: u64, -) -> Result { +) -> Result, Error> { if tx.address.raw() != context.current_address { return Err(Error::Relayer(RelayerError::InvalidSender)); } @@ -199,11 +293,22 @@ fn validate_tx_relayer_data( } let to = tx.to.ok_or(Error::User(UserError::EvmDeployDisallowed))?.raw(); - let target_as_address = extract_address(target).ok(); - let to_equals_target = target_as_address.map(|target| to == target).unwrap_or(false); - // Only valid targets satisfy `to == target` or `to == hash(target)` - if !to_equals_target && to != account_id_to_address(target) { + let target_kind = parse_target(target, context.current_address); + + // valid targets satisfy `to == target` or `to == hash(target)` + let is_valid_target = match target_kind { + TargetKind::CurrentAccount if to == context.current_address => { + target == &context.current_account_id + } + TargetKind::EthImplicit(address) if to == address => { + target.as_str() + == format!("0x{}{}", hex::encode(address), context.current_account_suffix()) + } + _ => to == account_id_to_address(target), + }; + + if !is_valid_target { return Err(Error::Relayer(RelayerError::InvalidTarget)); } @@ -216,15 +321,7 @@ fn validate_tx_relayer_data( return Err(Error::Relayer(RelayerError::InvalidNonce)); } - // If `to == target` and this is not a self-transaction then the address must not - // be registered in the address registry. The purpose of this check is to prevent - // lazy relayers from skipping this check themselves (relayers are supposed to use - // the address registry to fill in the `target`). - if to_equals_target && to != context.current_address { - Ok(TransactionValidationOutcome::AddressCheckRequired(to)) - } else { - Ok(TransactionValidationOutcome::Validated) - } + Ok(target_kind) } fn validate_tx_value( diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/lib.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/lib.rs index 5e81dde5cad..f9c7c4b7f3d 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/lib.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/lib.rs @@ -3,6 +3,7 @@ use crate::{ types::{ExecuteResponse, ExecutionContext}, }; use error::{UnsupportedAction, UserError}; +use near_contract_standards::storage_management::StorageBalance; use near_sdk::{ borsh::{BorshDeserialize, BorshSerialize}, env, @@ -10,7 +11,7 @@ use near_sdk::{ near_bindgen, AccountId, Allowance, Gas, GasWeight, NearToken, Promise, PromiseOrValue, PromiseResult, }; -use types::TransactionValidationOutcome; +use types::{EthEmulationKind, TransactionKind}; pub mod error; pub mod eth_emulation; @@ -22,7 +23,16 @@ pub mod types; #[cfg(test)] mod tests; +const MICRO_NEAR: u128 = 10_u128.pow(18); const ADDRESS_REGISTRAR_ACCOUNT_ID: &str = std::include_str!("ADDRESS_REGISTRAR_ACCOUNT_ID"); +/// This storage deposit value is the one used by the standard NEP-141 implementation, +/// which essentially all tokens use. Therefore we hard-code it here instead of doing +/// the extra on-chain call to `storage_balance_bounds`. This also prevents malicious +/// token contracts with very high `storage_balance_bounds` from taking lots of $NEAR +/// from eth-wallet-contract users. +const NEP_141_STORAGE_DEPOSIT_AMOUNT: NearToken = NearToken::from_yoctonear(1_250 * MICRO_NEAR); +const NEP_141_STORAGE_DEPOSIT_GAS: Gas = Gas::from_tgas(5); +const NEP_141_STORAGE_BALANCE_OF_GAS: Gas = Gas::from_tgas(5); #[near_bindgen] #[derive(Default, BorshDeserialize, BorshSerialize)] @@ -131,6 +141,73 @@ impl WalletContract { PromiseOrValue::Promise(promise) } + #[private] + pub fn nep_141_storage_balance_callback( + &mut self, + token_id: AccountId, + receiver_id: AccountId, + action: near_action::Action, + ) -> PromiseOrValue { + let maybe_storage_balance: Option = match env::promise_result(0) { + PromiseResult::Failed => { + return PromiseOrValue::Value(ExecuteResponse { + success: false, + success_value: None, + error: Some(format!("Call to NEP-141 {token_id}::storage_balance_of failed")), + }); + } + PromiseResult::Successful(value) => { + serde_json::from_slice(&value).unwrap_or_else(|_| { + env::panic_str("Unexpected response from NEP-141 storage_balance_of") + }) + } + }; + let current_account_id = env::current_account_id(); + let ext = WalletContract::ext(current_account_id).with_unused_gas_weight(1); + let promise = match maybe_storage_balance { + Some(_) => { + // receiver_id is registered so we can send the transfer + // without additional actions. Note: in the standard NEP-141 + // implementation it is impossible to have `Some` storage balance, + // but have it be insufficient to transact. + match action_to_promise(token_id, action) + .map(|p| p.then(ext.rlp_execute_callback())) + { + Ok(p) => p, + Err(e) => { + return PromiseOrValue::Value(e.into()); + } + } + } + None => { + // receiver_id is not registered so we must call `storage_deposit` first. + let storage_deposit_args = + format!(r#"{{"account_id": "{receiver_id}"}}"#).into_bytes(); + let transfer_function_call = match action { + near_action::Action::FunctionCall(x) => x, + _ => { + env::panic_str("Expected function call action to perform NEP-141 transfer") + } + }; + Promise::new(token_id) + .function_call( + "storage_deposit".into(), + storage_deposit_args, + NEP_141_STORAGE_DEPOSIT_AMOUNT, + NEP_141_STORAGE_DEPOSIT_GAS, + ) + .function_call( + transfer_function_call.method_name, + transfer_function_call.args, + transfer_function_call.deposit, + transfer_function_call.gas, + ) + .then(ext.rlp_execute_callback()) + } + }; + PromiseOrValue::Promise(promise) + } + #[private] pub fn rlp_execute_callback(&mut self) -> ExecuteResponse { let n = env::promise_results_count(); @@ -176,14 +253,12 @@ fn inner_rlp_execute( env::attached_deposit(), )?; - let (action, validation_outcome) = + let (action, transaction_kind) = internal::parse_rlp_tx_to_action(&tx_bytes_b64, &target, &context, nonce)?; - let promise = match validation_outcome { - TransactionValidationOutcome::Validated => { - let ext = WalletContract::ext(current_account_id).with_unused_gas_weight(1); - action_to_promise(target, action)?.then(ext.rlp_execute_callback()) - } - TransactionValidationOutcome::AddressCheckRequired(address) => { + let promise = match transaction_kind { + TransactionKind::EthEmulation(EthEmulationKind::EOABaseTokenTransfer { + address_check: Some(address), + }) => { let ext = WalletContract::ext(current_account_id).with_unused_gas_weight(1); let address_registrar = { let account_id = ADDRESS_REGISTRAR_ACCOUNT_ID @@ -195,6 +270,30 @@ fn inner_rlp_execute( let address = format!("0x{}", hex::encode(address)); address_registrar.lookup(address).then(ext.address_check_callback(target, action)) } + TransactionKind::EthEmulation(EthEmulationKind::ERC20Transfer { receiver_id }) => { + // In the case of the emulated ERC-20 transfer, the receiving account + // might not be registered with the NEP-141 contract (per the NEP-145) + // storage standard. Therefore we must create a multi-step promise where + // first we check if the receiver is registered and then if not call + // `storage_deposit` in addition to `ft_transfer`. + let token_id = target; + let ext: WalletContractExt = + WalletContract::ext(current_account_id).with_unused_gas_weight(1); + let storage_balance_args = + format!(r#"{{"account_id": "{}"}}"#, receiver_id.as_str()).into_bytes(); + Promise::new(token_id.clone()) + .function_call( + "storage_balance_of".into(), + storage_balance_args, + NearToken::from_yoctonear(0), + NEP_141_STORAGE_BALANCE_OF_GAS, + ) + .then(ext.nep_141_storage_balance_callback(token_id, receiver_id, action)) + } + _ => { + let ext = WalletContract::ext(current_account_id).with_unused_gas_weight(1); + action_to_promise(target, action)?.then(ext.rlp_execute_callback()) + } }; Ok(promise) } diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/emulation.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/emulation.rs index 4837564b252..531b3289f0a 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/emulation.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/emulation.rs @@ -1,10 +1,11 @@ use crate::{ internal::{account_id_to_address, CHAIN_ID, MAX_YOCTO_NEAR}, tests::utils::{crypto, nep141, test_context::TestContext}, + types::ExecuteResponse, }; use aurora_engine_types::types::{Address, Wei}; use near_sdk::json_types::U128; -use near_workspaces::types::NearToken; +use near_workspaces::{result::ValueOrReceiptId, types::NearToken}; // The Wallet Contract should understand that transactions to other Wallet // Contract instances are base token transactions. @@ -54,6 +55,45 @@ async fn test_base_token_transfer() -> anyhow::Result<()> { ); assert!(diff < NearToken::from_millinear(2)); + // If the relayer adds an account suffix different from the wallet contract, + // then the transaction is rejected. + let transaction = aurora_engine_transactions::eip_2930::Transaction2930 { + nonce: 1.into(), + gas_price: 0.into(), + gas_limit: 0.into(), + to: Some(Address::new(other_address)), + value: Wei::new_u128(TRANSFER_AMOUNT.as_yoctonear() / u128::from(MAX_YOCTO_NEAR)), + data: b"A message for the recipient".to_vec(), + chain_id: CHAIN_ID, + access_list: Vec::new(), + }; + let signed_transaction = crypto::sign_transaction(transaction, &wallet_sk); + + let target = format!("0x{}.wrong.suffix", hex::encode(other_address)); + let result = wallet_contract.rlp_execute_with_receipts(&target, &signed_transaction).await?; + + // Transaction is rejected for a wrong namespace so there is a faulty relayer error. + for r in result.receipt_outcomes() { + let response: ExecuteResponse = match r.clone().into_result().unwrap() { + ValueOrReceiptId::ReceiptId(_) => continue, + ValueOrReceiptId::Value(value) => match value.json() { + Err(_) => continue, + Ok(x) => x, + }, + }; + assert!(!response.success, "Expected failure"); + assert_eq!(response.error.as_deref(), Some("Error: faulty relayer")); + } + + let initial_wallet_balance = final_wallet_balance; + let final_wallet_balance = wallet_contract.inner.as_account().view_account().await?.balance; + + // Sender balance does not decrease (other than the gas spent to execute the transaction) + let diff = NearToken::from_yoctonear( + initial_wallet_balance.as_yoctonear() - final_wallet_balance.as_yoctonear(), + ); + assert!(diff < NearToken::from_millinear(2)); + Ok(()) } @@ -102,10 +142,11 @@ async fn test_erc20_emulation() -> anyhow::Result<()> { let balance: U128 = serde_json::from_slice(result.success_value.as_ref().unwrap())?; assert_eq!(balance.0, token_contract.ft_balance_of(wallet_contract.inner.id()).await?); - // Do a transfer to another account + // Do a transfer to another account. Note that the other account has never + // held this token before so it will require a storage deposit, but this is + // handled automatically by the wallet contract. let (other_wallet, other_address) = TestContext::deploy_wallet(&worker, &wallet_contract_bytes).await?; - token_contract.mint(other_wallet.inner.id(), MINT_AMOUNT.as_yoctonear()).await?; let transaction = aurora_engine_transactions::eip_2930::Transaction2930 { nonce: 1.into(), gas_price: 0.into(), @@ -137,9 +178,55 @@ async fn test_erc20_emulation() -> anyhow::Result<()> { token_contract.ft_balance_of(wallet_contract.inner.id()).await? ); assert_eq!( - MINT_AMOUNT.as_yoctonear() + TRANSFER_AMOUNT.as_yoctonear(), + TRANSFER_AMOUNT.as_yoctonear(), + token_contract.ft_balance_of(other_wallet.inner.id()).await? + ); + + // Now send a second transfer. This time the storage deposit is not needed + // and this case is still handled well by the wallet contract. + let transaction = aurora_engine_transactions::eip_2930::Transaction2930 { + nonce: 2.into(), + gas_price: 0.into(), + gas_limit: 0.into(), + to: Some(Address::new(account_id_to_address( + &token_contract.contract.id().as_str().parse().unwrap(), + ))), + value: Wei::zero(), + data: [ + crate::eth_emulation::ERC20_TRANSFER_SELECTOR.to_vec(), + ethabi::encode(&[ + ethabi::Token::Address(other_address), + ethabi::Token::Uint(TRANSFER_AMOUNT.as_yoctonear().into()), + ]), + ] + .concat(), + chain_id: CHAIN_ID, + access_list: Vec::new(), + }; + let signed_transaction = crypto::sign_transaction(transaction, &wallet_sk); + + let result = wallet_contract + .rlp_execute(token_contract.contract.id().as_str(), &signed_transaction) + .await?; + + assert!(result.success); + assert_eq!( + MINT_AMOUNT.as_yoctonear() - (2 * TRANSFER_AMOUNT.as_yoctonear()), + token_contract.ft_balance_of(wallet_contract.inner.id()).await? + ); + assert_eq!( + 2 * TRANSFER_AMOUNT.as_yoctonear(), token_contract.ft_balance_of(other_wallet.inner.id()).await? ); + assert_eq!( + nep141::STORAGE_DEPOSIT_AMOUNT, + token_contract + .storage_balance_of(other_wallet.inner.id()) + .await? + .unwrap() + .total + .as_yoctonear(), + ); Ok(()) } diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/nep141.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/nep141.rs index 1e100afe385..1dc92e91bb0 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/nep141.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/nep141.rs @@ -1,7 +1,8 @@ +use near_contract_standards::storage_management::StorageBalance; use near_sdk::json_types::U128; use near_workspaces::{network::Sandbox, types::NearToken, AccountId, Contract, Worker}; -const STORAGE_DEPOSIT_AMOUNT: u128 = 1_250_000_000_000_000_000_000; +pub const STORAGE_DEPOSIT_AMOUNT: u128 = crate::NEP_141_STORAGE_DEPOSIT_AMOUNT.as_yoctonear(); pub struct Nep141 { pub contract: Contract, @@ -64,4 +65,19 @@ impl Nep141 { .json()?; Ok(result.0) } + + pub async fn storage_balance_of( + &self, + account_id: &AccountId, + ) -> anyhow::Result> { + let result: Option = self + .contract + .view("storage_balance_of") + .args_json(serde_json::json!({ + "account_id": account_id.as_str(), + })) + .await? + .json()?; + Ok(result) + } } diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/test_context.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/test_context.rs index b20ac6eaa7b..9f8a09054af 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/test_context.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/tests/utils/test_context.rs @@ -11,6 +11,7 @@ use ethabi::Address; use near_sdk::json_types::U64; use near_workspaces::{ network::Sandbox, + result::ExecutionFinalResult, types::{KeyType, NearToken, PublicKey, SecretKey}, Account, Contract, Worker, }; @@ -30,12 +31,12 @@ pub struct WalletContract { } impl WalletContract { - pub async fn rlp_execute( + pub async fn rlp_execute_with_receipts( &self, target: &str, tx: &EthTransactionKind, - ) -> anyhow::Result { - let result: ExecuteResponse = self + ) -> anyhow::Result { + let result = self .inner .call(RLP_EXECUTE) .args_json(serde_json::json!({ @@ -44,9 +45,18 @@ impl WalletContract { })) .max_gas() .transact() - .await? - .into_result()? - .json()?; + .await?; + + Ok(result) + } + + pub async fn rlp_execute( + &self, + target: &str, + tx: &EthTransactionKind, + ) -> anyhow::Result { + let result: ExecuteResponse = + self.rlp_execute_with_receipts(target, tx).await?.into_result()?.json()?; Ok(result) } diff --git a/runtime/near-wallet-contract/implementation/wallet-contract/src/types.rs b/runtime/near-wallet-contract/implementation/wallet-contract/src/types.rs index 975e1304a25..130cf76da99 100644 --- a/runtime/near-wallet-contract/implementation/wallet-contract/src/types.rs +++ b/runtime/near-wallet-contract/implementation/wallet-contract/src/types.rs @@ -84,12 +84,84 @@ impl ExecutionContext { let current_address = crate::internal::extract_address(¤t_account_id)?; Ok(Self { current_address, attached_deposit, predecessor_account_id, current_account_id }) } + + /// In production eth-implicit accounts are top-level, so this suffix will + /// always be empty. The purpose of finding a suffix is that it allows for + /// testing environments where the wallet contract is deployed to an address + /// that is a sub-account. For example, this allows testing on Near testnet + /// before the eth-implicit accounts feature is stabilized. + /// The suffix is only needed in testing. + pub fn current_account_suffix(&self) -> &str { + self.current_account_id + .as_str() + .find('.') + .map(|index| &self.current_account_id.as_str()[index..]) + .unwrap_or("") + } +} + +/// The `target` of the transaction (set by the relayer) +/// is one of the following: the current account, another eth-implicit account +/// (i.e. another wallet contract) or some other Near account. This distinction +/// is important because the only kind of transaction that can be sent to another +/// eth-implicit account is a base token transfer (EOAs are not contracts on Ethereum). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[must_use] +pub enum TargetKind<'a> { + CurrentAccount, + EthImplicit(Address), + OtherNearAccount(&'a AccountId), } +/// A transaction can either contain an ABI-encoded Near action +/// or it can be a normal Ethereum transaction who's behaviour +/// we are trying to emulate. #[must_use] -pub enum TransactionValidationOutcome { - Validated, - AddressCheckRequired(Address), +pub enum TransactionKind { + NearNativeAction, + EthEmulation(EthEmulationKind), +} + +#[must_use] +pub enum EthEmulationKind { + EOABaseTokenTransfer { address_check: Option
}, + ERC20Balance, + ERC20Transfer { receiver_id: AccountId }, +} + +/// Describes a kind of transaction that is directly parsable +/// from an Ethereum-formatted transaction's calldata. Notably +/// `EthEmulationKind::EOABaseTokenTransfer` is missing because +/// on Ethereum base token transfers are inferred from the target +/// of the transaction, not its data. +#[must_use] +pub enum ParsableTransactionKind { + /// Near native actions with an explicit receiver + /// (i.e. `FunctionCall` and `Transfer`). + NearNativeAction, + /// Near native actions where the receiver should be equal + /// to the current account (i.e. `AddKey` and `DeleteKey`). + SelfNearNativeAction, + /// Emulated Ethereum standards + EthEmulation(ParsableEthEmulationKind), +} + +/// See docs for `ParsableTransactionKind`. +#[must_use] +pub enum ParsableEthEmulationKind { + ERC20Balance, + ERC20Transfer { receiver_id: AccountId }, +} + +impl From for EthEmulationKind { + fn from(value: ParsableEthEmulationKind) -> Self { + match value { + ParsableEthEmulationKind::ERC20Balance => Self::ERC20Balance, + ParsableEthEmulationKind::ERC20Transfer { receiver_id } => { + Self::ERC20Transfer { receiver_id } + } + } + } } /// The Near protocol actions represented in a form that is suitable for the diff --git a/runtime/near-wallet-contract/res/wallet_contract_localnet.wasm b/runtime/near-wallet-contract/res/wallet_contract_localnet.wasm index 080d387443f..53eaca14a9d 100755 Binary files a/runtime/near-wallet-contract/res/wallet_contract_localnet.wasm and b/runtime/near-wallet-contract/res/wallet_contract_localnet.wasm differ diff --git a/runtime/near-wallet-contract/res/wallet_contract_mainnet.wasm b/runtime/near-wallet-contract/res/wallet_contract_mainnet.wasm index 71620e31cff..c4939da9b13 100755 Binary files a/runtime/near-wallet-contract/res/wallet_contract_mainnet.wasm and b/runtime/near-wallet-contract/res/wallet_contract_mainnet.wasm differ diff --git a/runtime/near-wallet-contract/res/wallet_contract_testnet.wasm b/runtime/near-wallet-contract/res/wallet_contract_testnet.wasm index 6bf0cdd29de..ec1f0b1ce78 100755 Binary files a/runtime/near-wallet-contract/res/wallet_contract_testnet.wasm and b/runtime/near-wallet-contract/res/wallet_contract_testnet.wasm differ diff --git a/runtime/near-wallet-contract/src/lib.rs b/runtime/near-wallet-contract/src/lib.rs index 17e03eb0d91..e7c153cc5d4 100644 --- a/runtime/near-wallet-contract/src/lib.rs +++ b/runtime/near-wallet-contract/src/lib.rs @@ -74,24 +74,24 @@ mod tests { #[test] fn check_mainnet_wallet_contract() { - const WALLET_CONTRACT_HASH: &'static str = "Ai2kwfsF9pNqq4ngErx4HeoB4EJyyLB2R2i1s6Na9VS"; - const MAGIC_BYTES_HASH: &'static str = "FAUvvE5sejk9tQ5sGeZzXJ4bgJ7GzxjNdd2V2snNbT6X"; + const WALLET_CONTRACT_HASH: &'static str = "8FBpAzxX5tUBdymyi7GzQDaemc9aZ36LsN2u7LKMPKQS"; + const MAGIC_BYTES_HASH: &'static str = "5QFxBYLfxLAZyMU6SRp77B3g17Mo5AcxVvp7zG77L1hY"; check_wallet_contract(MAINNET, WALLET_CONTRACT_HASH); check_wallet_contract_magic_bytes(MAINNET, WALLET_CONTRACT_HASH, MAGIC_BYTES_HASH); } #[test] fn check_testnet_wallet_contract() { - const WALLET_CONTRACT_HASH: &'static str = "7FcYSUBNto2q7NkAbvLQ8Lv2kbeFqHcoAWFHqzJHqi9a"; - const MAGIC_BYTES_HASH: &'static str = "DPZnYabhPgsiqHiR83mSKvdVK97JPMTEPw4knLSBEvg5"; + const WALLET_CONTRACT_HASH: &'static str = "8tfDr2HUYU5wMVTgc3XdBm5qGH3Bg7VkGUWZAsHnDkud"; + const MAGIC_BYTES_HASH: &'static str = "42dPV6SXMU7iDnTkXexr4RnGoDYj4HcotkWbH15K7CT9"; check_wallet_contract(TESTNET, WALLET_CONTRACT_HASH); check_wallet_contract_magic_bytes(TESTNET, WALLET_CONTRACT_HASH, MAGIC_BYTES_HASH); } #[test] fn check_localnet_wallet_contract() { - const WALLET_CONTRACT_HASH: &'static str = "283Zi5Gt6SMWcjLT68DGcM9XRKcztMcMBgtMMEuhcJuY"; - const MAGIC_BYTES_HASH: &'static str = "CNnw7N4HmsEeij9KE3pYPpqATrg9HgQUN84gVaJtQHoV"; + const WALLET_CONTRACT_HASH: &'static str = "Gp6zEtywMeB228VU31nV4GBF2pKtVseoVBgfPQvNz7zN"; + const MAGIC_BYTES_HASH: &'static str = "7H117uXfLUKWN25KCF6eBErvpg5qg2f1Kuz1uwY47DDA"; const LOCALNET: &str = "localnet"; check_wallet_contract(LOCALNET, WALLET_CONTRACT_HASH); check_wallet_contract_magic_bytes(LOCALNET, WALLET_CONTRACT_HASH, MAGIC_BYTES_HASH); diff --git a/runtime/runtime-params-estimator/Cargo.toml b/runtime/runtime-params-estimator/Cargo.toml index 94a3e021d6c..5741ad333cb 100644 --- a/runtime/runtime-params-estimator/Cargo.toml +++ b/runtime/runtime-params-estimator/Cargo.toml @@ -80,6 +80,7 @@ nightly = [ "nearcore/nightly", "nightly_protocol", "node-runtime/nightly", + "protocol_feature_bls12381", ] nightly_protocol = [ "genesis-populate/nightly_protocol", @@ -93,5 +94,6 @@ nightly_protocol = [ "nearcore/nightly_protocol", "node-runtime/nightly_protocol", ] -sandbox = ["node-runtime/sandbox"] +sandbox = ["near-o11y/sandbox", "node-runtime/sandbox"] io_trace = ["near-store/io_trace", "near-o11y/io_trace", "near-vm-runner/io_trace"] +protocol_feature_bls12381 = [] diff --git a/runtime/runtime-params-estimator/compiler.sh b/runtime/runtime-params-estimator/compiler.sh index 06cb67f9510..3f121848318 100755 --- a/runtime/runtime-params-estimator/compiler.sh +++ b/runtime/runtime-params-estimator/compiler.sh @@ -9,12 +9,6 @@ if [ "$1" == "wasmtime" ]; then VMKIND="$1"; features="$features" fi -if [ "$1" == "lightbeam" ]; then - VMKIND="wasmtime" - features="$features,lightbeam" -fi - - set -ex diff --git a/runtime/runtime-params-estimator/emu-cost/Dockerfile b/runtime/runtime-params-estimator/emu-cost/Dockerfile index 80754d22b75..cf1111f4751 100644 --- a/runtime/runtime-params-estimator/emu-cost/Dockerfile +++ b/runtime/runtime-params-estimator/emu-cost/Dockerfile @@ -1,5 +1,5 @@ # our local base image -FROM docker.io/rust:1.78.0 +FROM docker.io/rust:1.79.0 LABEL description="Container for builds" diff --git a/runtime/runtime-params-estimator/estimate.sh b/runtime/runtime-params-estimator/estimate.sh index 7beee35223e..584773b711e 100755 --- a/runtime/runtime-params-estimator/estimate.sh +++ b/runtime/runtime-params-estimator/estimate.sh @@ -7,7 +7,7 @@ features="required" if [[ ! -z "$1" ]]; then features="$features,$1" - if [[ "$1" == *"wasmtime"* || "$1" == *"lightbeam"* ]]; then + if [[ "$1" == *"wasmtime"* ]]; then vmkind="wasmtime"; fi fi diff --git a/runtime/runtime-params-estimator/src/action_costs.rs b/runtime/runtime-params-estimator/src/action_costs.rs index d886dd701df..51c5e8b291a 100644 --- a/runtime/runtime-params-estimator/src/action_costs.rs +++ b/runtime/runtime-params-estimator/src/action_costs.rs @@ -770,8 +770,8 @@ pub(crate) fn empty_delegate_action( max_block_height: 1000, public_key: signer.public_key.clone(), }; - let signature = - SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction).sign(&signer); + let signature = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction) + .sign(&signer.into()); Action::Delegate(Box::new(near_primitives::action::delegate::SignedDelegateAction { delegate_action, signature, diff --git a/runtime/runtime-params-estimator/src/cost.rs b/runtime/runtime-params-estimator/src/cost.rs index a27b5efdce1..bfdd43951f1 100644 --- a/runtime/runtime-params-estimator/src/cost.rs +++ b/runtime/runtime-params-estimator/src/cost.rs @@ -605,6 +605,42 @@ pub enum Cost { AltBn128PairingCheckElement, AltBn128G1SumBase, AltBn128G1SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P1SumBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P1SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P2SumBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P2SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381G1MultiexpBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381G1MultiexpElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381G2MultiexpBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381G2MultiexpElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381MapFpToG1Base, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381MapFpToG1Element, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381MapFp2ToG2Base, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381MapFp2ToG2Element, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381PairingBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381PairingElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P1DecompressBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P1DecompressElement, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P2DecompressBase, + #[cfg(feature = "protocol_feature_bls12381")] + Bls12381P2DecompressElement, // Costs used only in estimator // diff --git a/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs b/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs index 4b4d8c521b5..951922050a8 100644 --- a/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs +++ b/runtime/runtime-params-estimator/src/costs_to_runtime_config.rs @@ -1,14 +1,13 @@ +use crate::cost::Cost; +use crate::cost_table::CostTable; +use anyhow::Context; use near_parameters::vm::Config as VMConfig; use near_parameters::{ AccountCreationConfig, ActionCosts, ExtCosts, ExtCostsConfig, Fee, ParameterCost, RuntimeConfig, RuntimeConfigStore, RuntimeFeesConfig, }; use near_primitives::version::PROTOCOL_VERSION; - -use anyhow::Context; - -use crate::cost::Cost; -use crate::cost_table::CostTable; +use std::sync::Arc; /// Turn a [`CostTable`] into a [`RuntimeConfig`]. /// @@ -29,14 +28,14 @@ pub fn costs_to_runtime_config(cost_table: &CostTable) -> anyhow::Result anyhow::Result fee(Cost::DataReceiptCreationBase)?, ActionCosts::new_data_receipt_byte => fee(Cost::DataReceiptCreationPerByte)?, }, - ..actual_fees_config.clone() + ..RuntimeFeesConfig::clone(&actual_fees_config) }; Ok(res) } @@ -155,6 +154,42 @@ fn estimation(cost: ExtCosts) -> Option { ExtCosts::alt_bn128_pairing_check_element => Cost::AltBn128PairingCheckElement, ExtCosts::yield_create_base => Cost::YieldCreateBase, ExtCosts::yield_create_byte => Cost::YieldCreateByte, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_base => Cost::Bls12381P1SumBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_sum_element => Cost::Bls12381P1SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_base => Cost::Bls12381P2SumBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_sum_element => Cost::Bls12381P2SumElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_base => Cost::Bls12381G1MultiexpBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g1_multiexp_element => Cost::Bls12381G1MultiexpElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_base => Cost::Bls12381G2MultiexpBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_g2_multiexp_element => Cost::Bls12381G2MultiexpElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_base => Cost::Bls12381MapFpToG1Base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp_to_g1_element => Cost::Bls12381MapFpToG1Element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_base => Cost::Bls12381MapFp2ToG2Base, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_map_fp2_to_g2_element => Cost::Bls12381MapFp2ToG2Element, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_base => Cost::Bls12381PairingBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_pairing_element => Cost::Bls12381PairingElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_base => Cost::Bls12381P1DecompressBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p1_decompress_element => Cost::Bls12381P1DecompressElement, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_base => Cost::Bls12381P2DecompressBase, + #[cfg(feature = "protocol_feature_bls12381")] + ExtCosts::bls12381_p2_decompress_element => Cost::Bls12381P2DecompressElement, _ => return None, }) } diff --git a/runtime/runtime-params-estimator/src/estimator_context.rs b/runtime/runtime-params-estimator/src/estimator_context.rs index 04e47c0bc52..d81ac18d583 100644 --- a/runtime/runtime-params-estimator/src/estimator_context.rs +++ b/runtime/runtime-params-estimator/src/estimator_context.rs @@ -3,8 +3,9 @@ use crate::config::{Config, GasMetric}; use crate::gas_cost::GasCost; use genesis_populate::get_account_id; use genesis_populate::state_dump::StateDump; +use near_parameters::config::CongestionControlConfig; use near_parameters::{ExtCosts, RuntimeConfigStore}; -use near_primitives::congestion_info::ExtendedCongestionInfo; +use near_primitives::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; use near_primitives::hash::CryptoHash; use near_primitives::receipt::Receipt; use near_primitives::runtime::migration_data::{MigrationData, MigrationFlags}; @@ -123,11 +124,11 @@ impl<'c> EstimatorContext<'c> { fn make_apply_state(cache: FilesystemContractRuntimeCache) -> ApplyState { let mut runtime_config = RuntimeConfigStore::new(None).get_config(PROTOCOL_VERSION).as_ref().clone(); - runtime_config.wasm_config.enable_all_features(); - runtime_config.wasm_config.make_free(); - + let wasm_config = Arc::make_mut(&mut runtime_config.wasm_config); + wasm_config.enable_all_features(); + wasm_config.make_free(); // Override vm limits config to simplify block processing. - runtime_config.wasm_config.limit_config = LimitConfig { + wasm_config.limit_config = LimitConfig { max_total_log_length: u64::MAX, max_number_registers: u64::MAX, max_gas_burnt: u64::MAX, @@ -141,11 +142,20 @@ impl<'c> EstimatorContext<'c> { max_total_prepaid_gas: u64::MAX, - ..runtime_config.wasm_config.limit_config + ..wasm_config.limit_config }; runtime_config.account_creation_config.min_allowed_top_level_account_length = 0; + // Disable congestion control to simplify measuring large workloads. + runtime_config.congestion_control_config = CongestionControlConfig::test_disabled(); let shard_id = ShardUId::single_shard().shard_id(); + let congestion_info = if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { + [(shard_id, ExtendedCongestionInfo::default())].into() + } else { + Default::default() + }; + let congestion_info = BlockCongestionInfo::new(congestion_info); + ApplyState { apply_reason: None, // Put each runtime into a separate shard. @@ -166,11 +176,7 @@ impl<'c> EstimatorContext<'c> { is_new_chunk: true, migration_data: Arc::new(MigrationData::default()), migration_flags: MigrationFlags::default(), - congestion_info: if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { - HashMap::from([(shard_id, ExtendedCongestionInfo::default())]) - } else { - HashMap::new() - }, + congestion_info, } } @@ -347,6 +353,11 @@ impl Testbed<'_> { .apply_to_flat_state(&mut store_update, shard_uid); store_update.commit().unwrap(); self.apply_state.block_height += 1; + if let Some(congestion_info) = apply_result.congestion_info { + self.apply_state + .congestion_info + .insert(shard_uid.shard_id(), ExtendedCongestionInfo::new(congestion_info, 0)); + } let mut total_burnt_gas = 0; if !allow_failures { diff --git a/runtime/runtime-params-estimator/src/function_call.rs b/runtime/runtime-params-estimator/src/function_call.rs index 8ca9a098423..79145da0932 100644 --- a/runtime/runtime-params-estimator/src/function_call.rs +++ b/runtime/runtime-params-estimator/src/function_call.rs @@ -8,6 +8,7 @@ use near_vm_runner::internal::VMKindExt; use near_vm_runner::logic::mocks::mock_external::MockedExternal; use near_vm_runner::{ContractCode, ContractRuntimeCache, FilesystemContractRuntimeCache}; use std::fmt::Write; +use std::sync::Arc; /// Estimates linear cost curve for a function call execution cost per byte of /// total contract code. The contract size is increased by adding more methods @@ -68,42 +69,26 @@ fn compute_function_call_cost( let config_store = RuntimeConfigStore::new(None); let runtime_config = config_store.get_config(protocol_version).as_ref(); let vm_config = runtime_config.wasm_config.clone(); - let runtime = vm_kind.runtime(vm_config).expect("runtime has not been enabled"); let fees = runtime_config.fees.clone(); - let mut fake_external = MockedExternal::new(); - let fake_context = create_context(vec![]); - let promise_results = vec![]; + let mut fake_external = MockedExternal::with_code(contract.clone_for_tests()); + let fake_context = create_context("hello0", vec![]); // Warmup. for _ in 0..warmup_repeats { + let runtime = vm_kind.runtime(vm_config.clone()).expect("runtime has not been enabled"); let result = runtime - .run( - *contract.hash(), - Some(&contract), - "hello0", - &mut fake_external, - &fake_context, - &fees, - &promise_results, - cache, - ) + .prepare(&fake_external, &fake_context, cache) + .run(&mut fake_external, &fake_context, Arc::clone(&fees)) .expect("fatal error"); assert!(result.aborted.is_none()); } // Run with gas metering. let start = GasCost::measure(gas_metric); for _ in 0..repeats { + let runtime = vm_kind.runtime(vm_config.clone()).expect("runtime has not been enabled"); let result = runtime - .run( - *contract.hash(), - Some(&contract), - "hello0", - &mut fake_external, - &fake_context, - &fees, - &promise_results, - cache, - ) + .prepare(&fake_external, &fake_context, cache) + .run(&mut fake_external, &fake_context, Arc::clone(&fees)) .expect("fatal_error"); assert!(result.aborted.is_none()); } diff --git a/runtime/runtime-params-estimator/src/gas_metering.rs b/runtime/runtime-params-estimator/src/gas_metering.rs index 8189eaaf248..4a710379661 100644 --- a/runtime/runtime-params-estimator/src/gas_metering.rs +++ b/runtime/runtime-params-estimator/src/gas_metering.rs @@ -7,6 +7,7 @@ use near_vm_runner::internal::VMKindExt; use near_vm_runner::logic::mocks::mock_external::MockedExternal; use near_vm_runner::{ContractCode, ContractRuntimeCache, FilesystemContractRuntimeCache}; use std::fmt::Write; +use std::sync::Arc; pub(crate) fn gas_metering_cost(config: &Config) -> (GasCost, GasCost) { let mut xs1 = vec![]; @@ -129,32 +130,22 @@ pub(crate) fn compute_gas_metering_cost(config: &Config, contract: &ContractCode let config_store = RuntimeConfigStore::new(None); let runtime_config = config_store.get_config(PROTOCOL_VERSION).as_ref(); let vm_config_gas = runtime_config.wasm_config.clone(); - let vm_config_free = { - let mut cfg = vm_config_gas.clone(); + let vm_config_free = Arc::new({ + let mut cfg = near_parameters::vm::Config::clone(&vm_config_gas); cfg.make_free(); cfg.enable_all_features(); cfg - }; - let runtime = vm_kind.runtime(vm_config_gas).expect("runtime has not been enabled"); - let runtime_free_gas = vm_kind.runtime(vm_config_free).expect("runtime has not been enabled"); + }); let fees = runtime_config.fees.clone(); - let mut fake_external = MockedExternal::new(); - let fake_context = create_context(vec![]); - let promise_results = vec![]; + let mut fake_external = MockedExternal::with_code(contract.clone_for_tests()); + let fake_context = create_context("hello", vec![]); // Warmup with gas metering for _ in 0..warmup_repeats { + let runtime = vm_kind.runtime(vm_config_gas.clone()).expect("runtime has not been enabled"); let result = runtime - .run( - *contract.hash(), - Some(&contract), - "hello", - &mut fake_external, - &fake_context, - &fees, - &promise_results, - cache, - ) + .prepare(&fake_external, &fake_context, cache) + .run(&mut fake_external, &fake_context, Arc::clone(&fees)) .expect("fatal_error"); if let Some(err) = &result.aborted { eprintln!("error: {}", err); @@ -165,17 +156,10 @@ pub(crate) fn compute_gas_metering_cost(config: &Config, contract: &ContractCode // Run with gas metering. let start = GasCost::measure(gas_metric); for _ in 0..repeats { + let runtime = vm_kind.runtime(vm_config_gas.clone()).expect("runtime has not been enabled"); let result = runtime - .run( - *contract.hash(), - Some(&contract), - "hello", - &mut fake_external, - &fake_context, - &fees, - &promise_results, - cache, - ) + .prepare(&fake_external, &fake_context, cache) + .run(&mut fake_external, &fake_context, Arc::clone(&fees)) .expect("fatal_error"); assert!(result.aborted.is_none()); } @@ -183,17 +167,11 @@ pub(crate) fn compute_gas_metering_cost(config: &Config, contract: &ContractCode // Warmup without gas metering for _ in 0..warmup_repeats { + let runtime_free_gas = + vm_kind.runtime(vm_config_free.clone()).expect("runtime has not been enabled"); let result = runtime_free_gas - .run( - *contract.hash(), - Some(&contract), - "hello", - &mut fake_external, - &fake_context, - &fees, - &promise_results, - cache, - ) + .prepare(&fake_external, &fake_context, cache) + .run(&mut fake_external, &fake_context, Arc::clone(&fees)) .expect("fatal_error"); assert!(result.aborted.is_none()); } @@ -201,17 +179,11 @@ pub(crate) fn compute_gas_metering_cost(config: &Config, contract: &ContractCode // Run without gas metering. let start = GasCost::measure(gas_metric); for _ in 0..repeats { + let runtime_free_gas = + vm_kind.runtime(vm_config_free.clone()).expect("runtime has not been enabled"); let result = runtime_free_gas - .run( - *contract.hash(), - Some(&contract), - "hello", - &mut fake_external, - &fake_context, - &fees, - &promise_results, - cache, - ) + .prepare(&fake_external, &fake_context, cache) + .run(&mut fake_external, &fake_context, Arc::clone(&fees)) .expect("fatal_error"); assert!(result.aborted.is_none()); } diff --git a/runtime/runtime-params-estimator/src/lib.rs b/runtime/runtime-params-estimator/src/lib.rs index 72fcb92f9bf..1a24fac8680 100644 --- a/runtime/runtime-params-estimator/src/lib.rs +++ b/runtime/runtime-params-estimator/src/lib.rs @@ -40,7 +40,7 @@ //! //! To run estimations on a non-empty DB with standardised content, we first //! dump all records to a `StateDump` written to a file. Then for each -//! iteration of a an estimation, we first load the records from this dump into +//! iteration of an estimation, we first load the records from this dump into //! a fresh database. Afterwards, it is crucial to run compaction on RocksDB //! before starting measurements. Otherwise, the SST file layout can be very //! inefficient, as there was no time to restructure them. We assume that in @@ -114,6 +114,7 @@ use near_vm_runner::MockContractRuntimeCache; use serde_json::json; use std::convert::TryFrom; use std::iter; +use std::sync::Arc; use std::time::Instant; use utils::{ average_cost, fn_cost, fn_cost_count, fn_cost_in_contract, fn_cost_with_setup, @@ -123,6 +124,42 @@ use utils::{ use vm_estimator::{compile_single_contract_cost, compute_compile_cost_vm}; static ALL_COSTS: &[(Cost, fn(&mut EstimatorContext) -> GasCost)] = &[ + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381MapFpToG1Base, bls12381_map_fp_to_g1_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381MapFpToG1Element, bls12381_map_fp_to_g1_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381MapFp2ToG2Base, bls12381_map_fp2_to_g2_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381MapFp2ToG2Element, bls12381_map_fp2_to_g2_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381PairingBase, bls12381_pairing_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381PairingElement, bls12381_pairing_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P1SumBase, bls12381_p1_sum_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P1SumElement, bls12381_p1_sum_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P2SumBase, bls12381_p2_sum_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P2SumElement, bls12381_p2_sum_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381G1MultiexpBase, bls12381_g1_multiexp_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381G1MultiexpElement, bls12381_g1_multiexp_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381G2MultiexpBase, bls12381_g2_multiexp_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381G2MultiexpElement, bls12381_g2_multiexp_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P1DecompressBase, bls12381_p1_decompress_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P1DecompressElement, bls12381_p1_decompress_element), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P2DecompressBase, bls12381_p2_decompress_base), + #[cfg(feature = "protocol_feature_bls12381")] + (Cost::Bls12381P2DecompressElement, bls12381_p2_decompress_element), (Cost::ActionReceiptCreation, action_receipt_creation), (Cost::ActionSirReceiptCreation, action_sir_receipt_creation), (Cost::ActionReceiptCreationSendSir, action_costs::new_action_receipt_send_sir), @@ -884,28 +921,19 @@ fn wasm_instruction(ctx: &mut EstimatorContext) -> GasCost { let n_iters = 10; let code = ContractCode::new(code.to_vec(), None); - let mut fake_external = MockedExternal::new(); + let mut fake_external = MockedExternal::with_code(code.clone_for_tests()); let config_store = RuntimeConfigStore::new(None); let config = config_store.get_config(PROTOCOL_VERSION).wasm_config.clone(); - let fees = RuntimeFeesConfig::test(); - let promise_results = vec![]; + let fees = Arc::new(RuntimeFeesConfig::test()); let cache = MockContractRuntimeCache::default(); let mut run = || { - let context = create_context(vec![]); + let context = create_context("cpu_ram_soak_test", vec![]); let vm_result = vm_kind .runtime(config.clone()) .unwrap() - .run( - *code.hash(), - Some(&code), - "cpu_ram_soak_test", - &mut fake_external, - &context, - &fees, - &promise_results, - Some(&cache), - ) + .prepare(&fake_external, &context, Some(&cache)) + .run(&mut fake_external, &context, Arc::clone(&fees)) .expect("fatal_error"); assert!(vm_result.aborted.is_some()); vm_result @@ -1073,6 +1101,101 @@ fn alt_bn128_pairing_check_element(ctx: &mut EstimatorContext) -> GasCost { ) } +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p1_sum_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p1_sum_0_100", ExtCosts::bls12381_p1_sum_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p1_sum_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p1_sum_50_100", ExtCosts::bls12381_p1_sum_element, 5000) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p2_sum_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p2_sum_0_100", ExtCosts::bls12381_p2_sum_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p2_sum_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p2_sum_50_100", ExtCosts::bls12381_p2_sum_element, 5000) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_g1_multiexp_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_g1_multiexp_0_100", ExtCosts::bls12381_g1_multiexp_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_g1_multiexp_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_g1_multiexp_50_100", ExtCosts::bls12381_g1_multiexp_element, 50 * 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_g2_multiexp_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_g2_multiexp_0_100", ExtCosts::bls12381_g2_multiexp_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_g2_multiexp_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_g2_multiexp_50_100", ExtCosts::bls12381_g2_multiexp_element, 50 * 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_map_fp_to_g1_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_map_fp_to_g1_0_100", ExtCosts::bls12381_map_fp_to_g1_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_map_fp_to_g1_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_map_fp_to_g1_50_100", ExtCosts::bls12381_map_fp_to_g1_element, 50 * 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_map_fp2_to_g2_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_map_fp2_to_g2_0_100", ExtCosts::bls12381_map_fp2_to_g2_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_map_fp2_to_g2_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost( + ctx, + "bls12381_map_fp2_to_g2_10_100", + ExtCosts::bls12381_map_fp2_to_g2_element, + 10 * 100, + ) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_pairing_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_pairing_0_100", ExtCosts::bls12381_pairing_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_pairing_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_pairing_5_100", ExtCosts::bls12381_pairing_element, 5 * 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p1_decompress_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p1_decompress_0_100", ExtCosts::bls12381_p1_decompress_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p1_decompress_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p1_decompress_50_100", ExtCosts::bls12381_p1_decompress_element, 5000) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p2_decompress_base(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p2_decompress_0_100", ExtCosts::bls12381_p2_decompress_base, 100) +} + +#[cfg(feature = "protocol_feature_bls12381")] +fn bls12381_p2_decompress_element(ctx: &mut EstimatorContext) -> GasCost { + fn_cost(ctx, "bls12381_p2_decompress_50_100", ExtCosts::bls12381_p2_decompress_element, 5000) +} + fn storage_has_key_base(ctx: &mut EstimatorContext) -> GasCost { fn_cost_with_setup( ctx, diff --git a/runtime/runtime-params-estimator/src/transaction_builder.rs b/runtime/runtime-params-estimator/src/transaction_builder.rs index 412fea0d5cd..c72d765716e 100644 --- a/runtime/runtime-params-estimator/src/transaction_builder.rs +++ b/runtime/runtime-params-estimator/src/transaction_builder.rs @@ -59,7 +59,7 @@ impl TransactionBuilder { nonce as u64, sender.clone(), receiver, - &signer, + &signer.into(), actions, CryptoHash::default(), 0, diff --git a/runtime/runtime-params-estimator/src/trie.rs b/runtime/runtime-params-estimator/src/trie.rs index 331e60ae7e2..95ea3361415 100644 --- a/runtime/runtime-params-estimator/src/trie.rs +++ b/runtime/runtime-params-estimator/src/trie.rs @@ -249,7 +249,7 @@ fn read_node_from_accounting_cache_ext( // Create a new cache and load nodes into it as preparation. let caching_storage = testbed.trie_caching_storage(); let mut accounting_cache = TrieAccountingCache::new(None); - accounting_cache.set_enabled(true); + accounting_cache.enable_switch().set(true); let _dummy_sum = read_raw_nodes_from_storage( &caching_storage, &mut accounting_cache, diff --git a/runtime/runtime-params-estimator/src/vm_estimator.rs b/runtime/runtime-params-estimator/src/vm_estimator.rs index 72bd4415ed6..5ef380b666f 100644 --- a/runtime/runtime-params-estimator/src/vm_estimator.rs +++ b/runtime/runtime-params-estimator/src/vm_estimator.rs @@ -15,13 +15,15 @@ const SIGNER_ACCOUNT_ID: &str = "bob"; const SIGNER_ACCOUNT_PK: [u8; 3] = [0, 1, 2]; const PREDECESSOR_ACCOUNT_ID: &str = "carol"; -pub(crate) fn create_context(input: Vec) -> VMContext { +pub(crate) fn create_context(method: &str, input: Vec) -> VMContext { VMContext { current_account_id: CURRENT_ACCOUNT_ID.parse().unwrap(), signer_account_id: SIGNER_ACCOUNT_ID.parse().unwrap(), signer_account_pk: Vec::from(&SIGNER_ACCOUNT_PK[..]), predecessor_account_id: PREDECESSOR_ACCOUNT_ID.parse().unwrap(), + method: method.into(), input, + promise_results: vec![].into(), block_height: 10, block_timestamp: 42, epoch_height: 0, diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index ea992b0883e..69e2949455d 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -66,10 +66,11 @@ no_cache = [ "near-store/no_cache", ] -sandbox = ["near-vm-runner/sandbox"] +sandbox = ["near-o11y/sandbox", "near-vm-runner/sandbox"] test_features = [ "near-primitives/test_features", "near-vm-runner/test_features", + "near-store/test_features", ] [dev-dependencies] diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 4a595bb302e..8137966238f 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -5,7 +5,6 @@ use crate::config::{ use crate::ext::{ExternalError, RuntimeExt}; use crate::receipt_manager::ReceiptManager; use crate::{metrics, ActionResult, ApplyState}; - use near_crypto::PublicKey; use near_parameters::{AccountCreationConfig, ActionCosts, RuntimeConfig, RuntimeFeesConfig}; use near_primitives::account::{AccessKey, AccessKeyPermission, Account}; @@ -36,51 +35,29 @@ use near_store::{ StorageError, TrieUpdate, }; use near_vm_runner::logic::errors::{ - CacheError, CompilationError, FunctionCallError, InconsistentStateError, VMRunnerError, + CompilationError, FunctionCallError, InconsistentStateError, VMRunnerError, }; use near_vm_runner::logic::types::PromiseResult; use near_vm_runner::logic::{VMContext, VMOutcome}; use near_vm_runner::precompile_contract; use near_vm_runner::ContractCode; use near_wallet_contract::{wallet_contract, wallet_contract_magic_bytes}; - use std::sync::Arc; -/// Returns `ContractCode` (if exists) for the given `account` or returns `StorageError`. -/// For ETH-implicit accounts returns `Wallet Contract` implementation that it is a part -/// of the protocol and it's cached in memory. -fn get_contract_code( - runtime_ext: &RuntimeExt, - account: &Account, - protocol_version: ProtocolVersion, -) -> Result>, StorageError> { - let account_id = runtime_ext.account_id(); - let code_hash = account.code_hash(); - if checked_feature!("stable", EthImplicitAccounts, protocol_version) - && account_id.get_account_type() == AccountType::EthImplicitAccount - { - let chain_id = runtime_ext.chain_id(); - assert!(&code_hash == wallet_contract_magic_bytes(&chain_id).hash()); - return Ok(Some(wallet_contract(&chain_id))); - } - Ok(runtime_ext.get_code(code_hash).map(Arc::new)) -} - /// Runs given function call with given context / apply state. pub(crate) fn execute_function_call( apply_state: &ApplyState, runtime_ext: &mut RuntimeExt, - account: &Account, predecessor_id: &AccountId, action_receipt: &ActionReceipt, - promise_results: &[PromiseResult], + promise_results: Arc<[PromiseResult]>, function_call: &FunctionCallAction, action_hash: &CryptoHash, config: &RuntimeConfig, is_last_action: bool, view_config: Option, ) -> Result { - let account_id = runtime_ext.account_id(); + let account_id = runtime_ext.account_id().clone(); tracing::debug!(target: "runtime", %account_id, "Calling the contract"); // Output data receipts are ignored if the function call is not the last action in the batch. let output_data_receivers: Vec<_> = if is_last_action { @@ -99,13 +76,15 @@ pub(crate) fn execute_function_call( signer_account_pk: borsh::to_vec(&action_receipt.signer_public_key) .expect("Failed to serialize"), predecessor_account_id: predecessor_id.clone(), + method: function_call.method_name.clone(), input: function_call.args.clone(), + promise_results, block_height: apply_state.block_height, block_timestamp: apply_state.block_timestamp, epoch_height: apply_state.epoch_height, - account_balance: account.amount(), - account_locked_balance: account.locked(), - storage_usage: account.storage_usage(), + account_balance: runtime_ext.account().amount(), + account_locked_balance: runtime_ext.account().locked(), + storage_usage: runtime_ext.account().storage_usage(), attached_deposit: function_call.deposit, prepaid_gas: function_call.gas, random_seed, @@ -118,67 +97,20 @@ pub(crate) fn execute_function_call( // charge only for trie nodes touched during function calls. // TODO (#5920): Consider using RAII for switching the state back - let protocol_version = runtime_ext.protocol_version(); - if checked_feature!("stable", ChunkNodesCache, protocol_version) { - runtime_ext.set_trie_cache_mode(TrieCacheMode::CachingChunk); - } - near_vm_runner::reset_metrics(); - - let result_from_cache = near_vm_runner::run( - account, - None, - &function_call.method_name, + let mode = match checked_feature!("stable", ChunkNodesCache, runtime_ext.protocol_version()) { + true => Some(TrieCacheMode::CachingChunk), + false => None, + }; + let mode_guard = runtime_ext.trie_update.with_trie_cache_mode(mode); + let result = near_vm_runner::run( runtime_ext, &context, - &config.wasm_config, - &config.fees, - promise_results, + Arc::clone(&config.wasm_config), + Arc::clone(&config.fees), apply_state.cache.as_deref(), ); - let result = match result_from_cache { - Err(VMRunnerError::CacheError(CacheError::ReadError(err))) - if err.kind() == std::io::ErrorKind::NotFound => - { - if checked_feature!("stable", ChunkNodesCache, protocol_version) { - runtime_ext.set_trie_cache_mode(TrieCacheMode::CachingShard); - } - let code = match get_contract_code( - &runtime_ext, - account, - apply_state.current_protocol_version, - ) { - Ok(Some(code)) => code, - Ok(None) => { - let error = - FunctionCallError::CompilationError(CompilationError::CodeDoesNotExist { - account_id: account_id.as_str().into(), - }); - return Ok(VMOutcome::nop_outcome(error)); - } - Err(e) => { - return Err(RuntimeError::StorageError(e)); - } - }; - if checked_feature!("stable", ChunkNodesCache, protocol_version) { - runtime_ext.set_trie_cache_mode(TrieCacheMode::CachingChunk); - } - let r = near_vm_runner::run( - account, - Some(&code), - &function_call.method_name, - runtime_ext, - &context, - &config.wasm_config, - &config.fees, - promise_results, - apply_state.cache.as_deref(), - ); - r - } - res => res, - }; - + drop(mode_guard); near_vm_runner::report_metrics( &apply_state.shard_id.to_string(), &apply_state @@ -187,42 +119,45 @@ pub(crate) fn execute_function_call( .map_or_else(|| String::from("unknown"), |r| r.to_string()), ); - if checked_feature!("stable", ChunkNodesCache, protocol_version) { - runtime_ext.set_trie_cache_mode(TrieCacheMode::CachingShard); - } - // There are many specific errors that the runtime can encounter. // Some can be translated to the more general `RuntimeError`, which allows to pass // the error up to the caller. For all other cases, panicking here is better // than leaking the exact details further up. // Note that this does not include errors caused by user code / input, those are // stored in outcome.aborted. - let mut outcome = result.map_err(|e| match e { - VMRunnerError::ExternalError(any_err) => { + let mut outcome = match result { + Err(VMRunnerError::ContractCodeNotPresent) => { + let error = FunctionCallError::CompilationError(CompilationError::CodeDoesNotExist { + account_id: account_id.as_str().into(), + }); + return Ok(VMOutcome::nop_outcome(error)); + } + Err(VMRunnerError::ExternalError(any_err)) => { let err: ExternalError = any_err.downcast().expect("Downcasting AnyError should not fail"); - match err { + return Err(match err { ExternalError::StorageError(err) => err.into(), ExternalError::ValidatorError(err) => RuntimeError::ValidatorError(err), - } - } - VMRunnerError::InconsistentStateError(err @ InconsistentStateError::IntegerOverflow) => { - StorageError::StorageInconsistentState(err.to_string()).into() + }); } - VMRunnerError::CacheError(err) => { + Err(VMRunnerError::InconsistentStateError( + err @ InconsistentStateError::IntegerOverflow, + )) => return Err(StorageError::StorageInconsistentState(err.to_string()).into()), + Err(VMRunnerError::CacheError(err)) => { metrics::FUNCTION_CALL_PROCESSED_CACHE_ERRORS.with_label_values(&[(&err).into()]).inc(); - StorageError::StorageInconsistentState(err.to_string()).into() + return Err(StorageError::StorageInconsistentState(err.to_string()).into()); } - VMRunnerError::LoadingError(msg) => { + Err(VMRunnerError::LoadingError(msg)) => { panic!("Contract runtime failed to load a contrct: {msg}") } - VMRunnerError::Nondeterministic(msg) => { + Err(VMRunnerError::Nondeterministic(msg)) => { panic!("Contract runner returned non-deterministic error '{}', aborting", msg) } - VMRunnerError::WasmUnknownError { debug_message } => { + Err(VMRunnerError::WasmUnknownError { debug_message }) => { panic!("Wasmer returned unknown message: {}", debug_message) } - })?; + Ok(r) => r, + }; if !view_config.is_some() { let unused_gas = function_call.gas.saturating_sub(outcome.used_gas); @@ -238,14 +173,14 @@ pub(crate) fn action_function_call( account: &mut Account, receipt: &Receipt, action_receipt: &ActionReceipt, - promise_results: &[PromiseResult], + promise_results: Arc<[PromiseResult]>, result: &mut ActionResult, account_id: &AccountId, function_call: &FunctionCallAction, action_hash: &CryptoHash, config: &RuntimeConfig, is_last_action: bool, - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), ) -> Result<(), RuntimeError> { if account.amount().checked_add(function_call.deposit).is_none() { return Err(StorageError::StorageInconsistentState( @@ -254,22 +189,24 @@ pub(crate) fn action_function_call( .into()); } state_update.trie.request_code_recording(account_id.clone()); + #[cfg(feature = "test_features")] + apply_recorded_storage_garbage(function_call, state_update); let mut receipt_manager = ReceiptManager::default(); let mut runtime_ext = RuntimeExt::new( state_update, &mut receipt_manager, - account_id, - action_hash, - &apply_state.epoch_id, - &apply_state.prev_block_hash, - &apply_state.block_hash, + account_id.clone(), + account.clone(), + *action_hash, + apply_state.epoch_id, + apply_state.prev_block_hash, + apply_state.block_hash, epoch_info_provider, apply_state.current_protocol_version, ); let outcome = execute_function_call( apply_state, &mut runtime_ext, - account, receipt.predecessor_id(), action_receipt, promise_results, @@ -651,7 +588,7 @@ pub(crate) fn action_implicit_account_creation_transfer( // is a no-op if the contract was already compiled. precompile_contract( &wallet_contract(&chain_id), - &apply_state.config.wasm_config, + Arc::clone(&apply_state.config.wasm_config), apply_state.cache.as_deref(), ) .ok(); @@ -693,7 +630,12 @@ pub(crate) fn action_deploy_contract( // Precompile the contract and store result (compiled code or error) in the database. // Note, that contract compilation costs are already accounted in deploy cost using // special logic in estimator (see get_runtime_config() function). - precompile_contract(&code, &apply_state.config.wasm_config, apply_state.cache.as_deref()).ok(); + precompile_contract( + &code, + Arc::clone(&apply_state.config.wasm_config), + apply_state.cache.as_deref(), + ) + .ok(); Ok(()) } @@ -1187,6 +1129,23 @@ fn check_transfer_to_nonexisting_account( } } +/// See #11703 for more details +#[cfg(feature = "test_features")] +fn apply_recorded_storage_garbage( + function_call: &FunctionCallAction, + state_update: &mut TrieUpdate, +) { + if let Some(garbage_size_mbs) = function_call + .method_name + .strip_prefix("internal_record_storage_garbage_") + .and_then(|suf| suf.parse::().ok()) + { + if state_update.trie.record_storage_garbage(garbage_size_mbs) { + tracing::warn!(target: "runtime", %garbage_size_mbs, "Generated storage proof garbage"); + } + } +} + #[cfg(test)] mod tests { @@ -1194,6 +1153,7 @@ mod tests { use crate::near_primitives::shard_layout::ShardUId; use near_primitives::account::FunctionCallPermission; use near_primitives::action::delegate::NonDelegateAction; + use near_primitives::congestion_info::BlockCongestionInfo; use near_primitives::errors::InvalidAccessKeyError; use near_primitives::hash::hash; use near_primitives::runtime::migration_data::MigrationFlags; @@ -1203,7 +1163,6 @@ mod tests { use near_primitives_core::version::PROTOCOL_VERSION; use near_store::set_account; use near_store::test_utils::TestTriesBuilder; - use std::collections::HashMap; use std::sync::Arc; fn test_action_create_account( @@ -1445,7 +1404,7 @@ mod tests { is_new_chunk: false, migration_data: Arc::default(), migration_flags: MigrationFlags::default(), - congestion_info: HashMap::new(), + congestion_info: BlockCongestionInfo::default(), } } diff --git a/runtime/runtime/src/balance_checker.rs b/runtime/runtime/src/balance_checker.rs index 474cc72557b..0fed196bb3e 100644 --- a/runtime/runtime/src/balance_checker.rs +++ b/runtime/runtime/src/balance_checker.rs @@ -123,7 +123,9 @@ fn total_postponed_receipts_cost( } }; - safe_add_balance(total, cost).map_err(|_| RuntimeError::UnexpectedIntegerOverflow) + safe_add_balance(total, cost).map_err(|_| { + RuntimeError::UnexpectedIntegerOverflow("total_postponed_receipts_cost".into()) + }) }) } @@ -364,7 +366,6 @@ pub(crate) fn check_balance( incoming_receipts_balance, processed_delayed_receipts_balance, initial_postponed_receipts_balance, - #[cfg(feature = "nightly")] forwarded_buffered_receipts_balance, // Outputs final_accounts_balance, @@ -373,7 +374,6 @@ pub(crate) fn check_balance( final_postponed_receipts_balance, tx_burnt_amount: stats.tx_burnt_amount, slashed_burnt_amount: stats.slashed_burnt_amount, - #[cfg(feature = "nightly")] new_buffered_receipts_balance, other_burnt_amount: stats.other_burnt_amount, } @@ -567,7 +567,7 @@ mod tests { 0, sender, receiver, - &signer, + &signer.into(), deposit, CryptoHash::default(), ); @@ -614,7 +614,7 @@ mod tests { let tx = transfer_tx(alice_id, bob_id, 2); let receipt = extract_transfer_receipt(&tx, gas_price, deposit); - assert_eq!( + assert_matches!( check_balance( &RuntimeConfig::test(), &initial_state, @@ -625,7 +625,7 @@ mod tests { &[], &ApplyStats::default(), ), - Err(RuntimeError::UnexpectedIntegerOverflow) + Err(RuntimeError::UnexpectedIntegerOverflow(_)) ); } @@ -671,7 +671,7 @@ mod tests { ); } - /// When adding a receipt to the outgoing buffer, its balance must must be + /// When adding a receipt to the outgoing buffer, its balance must be /// picked up by the balance checker. Test it by simulating a transfer /// action removing some balance from an account and placing the receipt in /// the buffer. diff --git a/runtime/runtime/src/congestion_control.rs b/runtime/runtime/src/congestion_control.rs index ccc898056f2..c830979ba45 100644 --- a/runtime/runtime/src/congestion_control.rs +++ b/runtime/runtime/src/congestion_control.rs @@ -42,10 +42,17 @@ pub(crate) struct ReceiptSinkV2<'a> { /// used to make forwarding decisions. pub(crate) own_congestion_info: &'a mut CongestionInfo, pub(crate) outgoing_receipts: &'a mut Vec, - pub(crate) outgoing_limit: HashMap, + pub(crate) outgoing_limit: HashMap, pub(crate) outgoing_buffers: ShardsOutgoingReceiptBuffer, } +/// Limits for outgoing receipts to a shard. +/// Receipts are sent out until the limit is hit, after that they're buffered. +pub(crate) struct OutgoingLimit { + pub gas: Gas, + pub size: u64, +} + enum ReceiptForwarding { Forwarded, NotForwarded(Receipt), @@ -82,7 +89,7 @@ impl<'a> ReceiptSink<'a> { debug_assert!(ProtocolFeature::CongestionControl.enabled(protocol_version)); let outgoing_buffers = ShardsOutgoingReceiptBuffer::load(trie)?; - let outgoing_limit: HashMap = apply_state + let outgoing_limit: HashMap = apply_state .congestion_info .iter() .map(|(&shard_id, congestion)| { @@ -91,8 +98,19 @@ impl<'a> ReceiptSink<'a> { congestion.congestion_info, congestion.missed_chunks_count, ); - - (shard_id, other_congestion_control.outgoing_limit(apply_state.shard_id)) + let gas_limit = if shard_id != apply_state.shard_id { + other_congestion_control.outgoing_gas_limit(apply_state.shard_id) + } else { + // No gas limits on receipts that stay on the same shard. Backpressure + // wouldn't help, the receipt takes the same memory if buffered or + // in the delayed receipts queue. + Gas::MAX + }; + + let size_limit = + other_congestion_control.outgoing_size_limit(apply_state.shard_id); + + (shard_id, OutgoingLimit { gas: gas_limit, size: size_limit }) }) .collect(); @@ -216,13 +234,6 @@ impl ReceiptSinkV2<'_> { ) -> Result<(), RuntimeError> { let shard = epoch_info_provider .account_id_to_shard_id(receipt.receiver_id(), &apply_state.epoch_id)?; - if shard == apply_state.shard_id { - // No limits on receipts that stay on the same shard. Backpressure - // wouldn't help, the receipt takes the same memory if buffered or - // in the delayed receipts queue. - self.outgoing_receipts.push(receipt); - return Ok(()); - } match Self::try_forward( receipt, shard, @@ -247,7 +258,7 @@ impl ReceiptSinkV2<'_> { fn try_forward( receipt: Receipt, shard: ShardId, - outgoing_limit: &mut HashMap, + outgoing_limit: &mut HashMap, outgoing_receipts: &mut Vec, apply_state: &ApplyState, ) -> Result { @@ -256,12 +267,20 @@ impl ReceiptSinkV2<'_> { // could be a special case during resharding events. Or even a bug. In // any case, if we cannot know a limit, treating it as literally "no // limit" is the safest approach to ensure availability. - let forward_limit = outgoing_limit.entry(shard).or_insert(Gas::MAX); + // For the size limit, we default to the usual limit that is applied to all (non-special) shards. + let forward_limit = outgoing_limit.entry(shard).or_insert(OutgoingLimit { + gas: Gas::MAX, + size: apply_state.config.congestion_control_config.outgoing_receipts_usual_size_limit, + }); let gas_to_forward = receipt_congestion_gas(&receipt, &apply_state.config)?; - if *forward_limit > gas_to_forward { + let size_to_forward: u64 = + receipt_size(&receipt)?.try_into().expect("Can't convert usize to u64"); + + if forward_limit.gas > gas_to_forward && forward_limit.size > size_to_forward { outgoing_receipts.push(receipt); - // underflow impossible: checked forward_limit > gas_to_forward above - *forward_limit -= gas_to_forward; + // underflow impossible: checked forward_limit > gas/size_to_forward above + forward_limit.gas -= gas_to_forward; + forward_limit.size -= size_to_forward; Ok(ReceiptForwarding::Forwarded) } else { Ok(ReceiptForwarding::NotForwarded(receipt)) diff --git a/runtime/runtime/src/conversions.rs b/runtime/runtime/src/conversions.rs index 303e3205b8c..94f8cc82bb0 100644 --- a/runtime/runtime/src/conversions.rs +++ b/runtime/runtime/src/conversions.rs @@ -49,6 +49,9 @@ mod compilation_error { }, From::PrepareError(pe) => Self::PrepareError(super::Convert::convert(pe)), From::WasmerCompileError { msg } => Self::WasmerCompileError { msg }, + // Intentionally converting into "Wasmer" error here in order to avoid + // this particular detail being visible to the protocol unnecessarily. + From::WasmtimeCompileError { msg } => Self::WasmerCompileError { msg }, } } } @@ -56,6 +59,7 @@ mod compilation_error { mod function_call_error { use near_vm_runner::logic::errors::FunctionCallError as From; + impl super::Convert for near_primitives::errors::FunctionCallError { fn convert(outer_err: From) -> Self { match outer_err { @@ -77,3 +81,16 @@ impl Convert for near_vm_runner::logic::TrieNo Self { db_reads: other.db_reads, mem_reads: other.mem_reads } } } + +mod profile_data_v3 { + use near_vm_runner::ProfileDataV3 as From; + impl super::Convert for near_primitives::profile_data_v3::ProfileDataV3 { + fn convert(other: From) -> Self { + Self { + actions_profile: other.actions_profile, + wasm_ext_profile: other.wasm_ext_profile, + wasm_gas: other.wasm_gas, + } + } + } +} diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index 06962947cbd..9e1bda98c38 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -1,5 +1,8 @@ use crate::conversions::Convert; use crate::receipt_manager::ReceiptManager; +use near_primitives::account::id::AccountType; +use near_primitives::account::Account; +use near_primitives::checked_feature; use near_primitives::errors::{EpochError, StorageError}; use near_primitives::hash::CryptoHash; use near_primitives::trie_key::{trie_key_parsers, TrieKey}; @@ -11,17 +14,20 @@ use near_vm_runner::logic::errors::{AnyError, VMLogicError}; use near_vm_runner::logic::types::ReceiptIndex; use near_vm_runner::logic::{External, StorageGetMode, ValuePtr}; use near_vm_runner::ContractCode; +use near_wallet_contract::{wallet_contract, wallet_contract_magic_bytes}; +use std::sync::Arc; pub struct RuntimeExt<'a> { - trie_update: &'a mut TrieUpdate, + pub(crate) trie_update: &'a mut TrieUpdate, pub(crate) receipt_manager: &'a mut ReceiptManager, - account_id: &'a AccountId, - action_hash: &'a CryptoHash, + account_id: AccountId, + account: Account, + action_hash: CryptoHash, data_count: u64, - epoch_id: &'a EpochId, - prev_block_hash: &'a CryptoHash, - last_block_hash: &'a CryptoHash, - epoch_info_provider: &'a dyn EpochInfoProvider, + epoch_id: EpochId, + prev_block_hash: CryptoHash, + last_block_hash: CryptoHash, + epoch_info_provider: &'a (dyn EpochInfoProvider), current_protocol_version: ProtocolVersion, } @@ -57,18 +63,20 @@ impl<'a> RuntimeExt<'a> { pub fn new( trie_update: &'a mut TrieUpdate, receipt_manager: &'a mut ReceiptManager, - account_id: &'a AccountId, - action_hash: &'a CryptoHash, - epoch_id: &'a EpochId, - prev_block_hash: &'a CryptoHash, - last_block_hash: &'a CryptoHash, - epoch_info_provider: &'a dyn EpochInfoProvider, + account_id: AccountId, + account: Account, + action_hash: CryptoHash, + epoch_id: EpochId, + prev_block_hash: CryptoHash, + last_block_hash: CryptoHash, + epoch_info_provider: &'a (dyn EpochInfoProvider), current_protocol_version: ProtocolVersion, ) -> Self { RuntimeExt { trie_update, receipt_manager, account_id, + account, action_hash, data_count: 0, epoch_id, @@ -80,22 +88,19 @@ impl<'a> RuntimeExt<'a> { } #[inline] - pub fn account_id(&self) -> &'a AccountId { - self.account_id + pub fn account_id(&self) -> &AccountId { + &self.account_id } - pub fn get_code(&self, code_hash: CryptoHash) -> Option { - self.trie_update.get_code(self.account_id.clone(), code_hash) + #[inline] + pub fn account(&self) -> &Account { + &self.account } pub fn create_storage_key(&self, key: &[u8]) -> TrieKey { TrieKey::ContractData { account_id: self.account_id.clone(), key: key.to_vec() } } - pub fn set_trie_cache_mode(&mut self, state: TrieCacheMode) { - self.trie_update.set_trie_cache_mode(state); - } - #[inline] pub fn protocol_version(&self) -> ProtocolVersion { self.current_protocol_version @@ -156,10 +161,10 @@ impl<'a> External for RuntimeExt<'a> { fn storage_remove_subtree(&mut self, prefix: &[u8]) -> ExtResult<()> { let data_keys = self .trie_update - .iter(&trie_key_parsers::get_raw_prefix_for_contract_data(self.account_id, prefix)) + .iter(&trie_key_parsers::get_raw_prefix_for_contract_data(&self.account_id, prefix)) .map_err(wrap_storage_error)? .map(|raw_key| { - trie_key_parsers::parse_data_key_from_contract_data_key(&raw_key?, self.account_id) + trie_key_parsers::parse_data_key_from_contract_data_key(&raw_key?, &self.account_id) .map_err(|_e| { StorageError::StorageInconsistentState( "Can't parse data key from raw key for ContractData".to_string(), @@ -179,9 +184,9 @@ impl<'a> External for RuntimeExt<'a> { fn generate_data_id(&mut self) -> CryptoHash { let data_id = create_receipt_id_from_action_hash( self.current_protocol_version, - self.action_hash, - self.prev_block_hash, - self.last_block_hash, + &self.action_hash, + &self.prev_block_hash, + &self.last_block_hash, self.data_count as usize, ); self.data_count += 1; @@ -203,13 +208,13 @@ impl<'a> External for RuntimeExt<'a> { fn validator_stake(&self, account_id: &AccountId) -> ExtResult> { self.epoch_info_provider - .validator_stake(self.epoch_id, self.prev_block_hash, account_id) + .validator_stake(&self.epoch_id, &self.prev_block_hash, account_id) .map_err(|e| ExternalError::ValidatorError(e).into()) } fn validator_total_stake(&self) -> ExtResult { self.epoch_info_provider - .validator_total_stake(self.epoch_id, self.prev_block_hash) + .validator_total_stake(&self.epoch_id, &self.prev_block_hash) .map_err(|e| ExternalError::ValidatorError(e).into()) } @@ -354,4 +359,27 @@ impl<'a> External for RuntimeExt<'a> { fn get_receipt_receiver(&self, receipt_index: ReceiptIndex) -> &AccountId { self.receipt_manager.get_receipt_receiver(receipt_index) } + + fn code_hash(&self) -> CryptoHash { + self.account.code_hash() + } + + fn get_contract(&self) -> Option> { + let account_id = self.account_id(); + let code_hash = self.code_hash(); + let version = self.current_protocol_version; + let chain_id = self.chain_id(); + if checked_feature!("stable", EthImplicitAccounts, self.current_protocol_version) + && account_id.get_account_type() == AccountType::EthImplicitAccount + && &code_hash == wallet_contract_magic_bytes(&chain_id).hash() + { + return Some(wallet_contract(&chain_id)); + } + let mode = match checked_feature!("stable", ChunkNodesCache, version) { + true => Some(TrieCacheMode::CachingShard), + false => None, + }; + let _guard = self.trie_update.with_trie_cache_mode(mode); + self.trie_update.get_code(self.account_id.clone(), code_hash).map(Arc::new) + } } diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 30759db13c8..99e85bbda39 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -13,14 +13,16 @@ pub use crate::verifier::{ use config::total_prepaid_send_fees; pub use congestion_control::bootstrap_congestion_info; use congestion_control::ReceiptSink; +use metrics::ApplyMetrics; pub use near_crypto; use near_parameters::{ActionCosts, RuntimeConfig}; pub use near_primitives; use near_primitives::account::Account; use near_primitives::checked_feature; -use near_primitives::congestion_info::{CongestionInfo, ExtendedCongestionInfo}; +use near_primitives::congestion_info::{BlockCongestionInfo, CongestionInfo}; use near_primitives::errors::{ - ActionError, ActionErrorKind, IntegerOverflowError, RuntimeError, TxExecutionError, + ActionError, ActionErrorKind, IntegerOverflowError, InvalidTxError, RuntimeError, + TxExecutionError, }; use near_primitives::hash::CryptoHash; use near_primitives::receipt::{ @@ -51,9 +53,9 @@ use near_primitives_core::apply::ApplyChunkReason; use near_store::trie::receipts_column_helper::DelayedReceiptQueue; use near_store::{ get, get_account, get_postponed_receipt, get_promise_yield_receipt, get_received_data, - has_received_data, remove_postponed_receipt, remove_promise_yield_receipt, set, set_access_key, - set_account, set_code, set_postponed_receipt, set_promise_yield_receipt, set_received_data, - PartialStorage, StorageError, Trie, TrieAccess, TrieChanges, TrieUpdate, + has_received_data, remove_account, remove_postponed_receipt, remove_promise_yield_receipt, set, + set_access_key, set_account, set_code, set_postponed_receipt, set_promise_yield_receipt, + set_received_data, PartialStorage, StorageError, Trie, TrieAccess, TrieChanges, TrieUpdate, }; use near_vm_runner::logic::types::PromiseResult; use near_vm_runner::logic::ReturnData; @@ -126,7 +128,7 @@ pub struct ApplyState { /// chunk. If the next chunks is the first with congestion control enabled, /// the congestion info needs to be computed while applying receipts. /// TODO(congestion_info) - verify performance of initialization when congested - pub congestion_info: HashMap, + pub congestion_info: BlockCongestionInfo, } /// Contains information to update validators accounts at the first block of a new epoch. @@ -287,7 +289,7 @@ impl Runtime { apply_state: &ApplyState, signed_transaction: &SignedTransaction, stats: &mut ApplyStats, - ) -> Result<(Receipt, ExecutionOutcomeWithId), RuntimeError> { + ) -> Result<(Receipt, ExecutionOutcomeWithId), InvalidTxError> { let span = tracing::Span::current(); metrics::TRANSACTION_PROCESSED_TOTAL.inc(); @@ -326,7 +328,8 @@ impl Runtime { }), }); stats.tx_burnt_amount = - safe_add_balance(stats.tx_burnt_amount, verification_result.burnt_amount)?; + safe_add_balance(stats.tx_burnt_amount, verification_result.burnt_amount) + .map_err(|_| InvalidTxError::CostOverflow)?; let gas_burnt = verification_result.gas_burnt; let compute_usage = verification_result.gas_burnt; let outcome = ExecutionOutcomeWithId { @@ -366,11 +369,11 @@ impl Runtime { actor_id: &mut AccountId, receipt: &Receipt, action_receipt: &ActionReceipt, - promise_results: &[PromiseResult], + promise_results: Arc<[PromiseResult]>, action_hash: &CryptoHash, action_index: usize, actions: &[Action], - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), ) -> Result { let _span = tracing::debug_span!( target: "runtime", @@ -545,7 +548,7 @@ impl Runtime { receipt_sink: &mut ReceiptSink, validator_proposals: &mut Vec, stats: &mut ApplyStats, - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), ) -> Result { let _span = tracing::debug_span!( target: "runtime", @@ -579,7 +582,7 @@ impl Runtime { None => Ok(PromiseResult::Failed), } }) - .collect::, RuntimeError>>()?; + .collect::, RuntimeError>>()?; // state_update might already have some updates so we need to make sure we commit it before // executing the actual receipt @@ -615,7 +618,7 @@ impl Runtime { &mut actor_id, receipt, action_receipt, - &promise_results, + Arc::clone(&promise_results), &action_hash, action_index, &action_receipt.actions, @@ -876,7 +879,9 @@ impl Runtime { compute_usage: Some(result.compute_usage), tokens_burnt, executor_id: account_id.clone(), - metadata: ExecutionMetadata::V3(result.profile), + metadata: ExecutionMetadata::V3(Box::new(conversions::Convert::convert( + *result.profile, + ))), }, }) } @@ -962,7 +967,7 @@ impl Runtime { receipt_sink: &mut ReceiptSink, validator_proposals: &mut Vec, stats: &mut ApplyStats, - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), ) -> Result, RuntimeError> { let account_id = receipt.receiver_id(); match receipt.receipt() { @@ -1165,12 +1170,9 @@ impl Runtime { if let Some(mut account) = get_account(state_update, account_id)? { if let Some(reward) = validator_accounts_update.validator_rewards.get(account_id) { debug!(target: "runtime", "account {} adding reward {} to stake {}", account_id, reward, account.locked()); - account.set_locked( - account - .locked() - .checked_add(*reward) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?, - ); + account.set_locked(account.locked().checked_add(*reward).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow("update_validator_accounts".into()) + })?); } debug!(target: "runtime", @@ -1189,20 +1191,26 @@ impl Runtime { let return_stake = account .locked() .checked_sub(max(*max_of_stakes, last_proposal)) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; + .ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow( + "update_validator_accounts - return stake".into(), + ) + })?; debug!(target: "runtime", "account {} return stake {}", account_id, return_stake); - account.set_locked( - account - .locked() - .checked_sub(return_stake) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?, - ); - account.set_amount( - account - .amount() - .checked_add(return_stake) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?, - ); + account.set_locked(account.locked().checked_sub(return_stake).ok_or_else( + || { + RuntimeError::UnexpectedIntegerOverflow( + "update_validator_accounts - set_locked".into(), + ) + }, + )?); + account.set_amount(account.amount().checked_add(return_stake).ok_or_else( + || { + RuntimeError::UnexpectedIntegerOverflow( + "update_validator_accounts - set_amount".into(), + ) + }, + )?); set_account(state_update, account_id.clone(), &account); } else if *max_of_stakes > 0 { @@ -1225,16 +1233,19 @@ impl Runtime { "FATAL: staking invariant does not hold. Account locked {} is less than slashed {}", account.locked(), amount_to_slash)).into()); } - stats.slashed_burnt_amount = stats - .slashed_burnt_amount - .checked_add(amount_to_slash) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?; - account.set_locked( - account - .locked() - .checked_sub(amount_to_slash) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?, - ); + stats.slashed_burnt_amount = + stats.slashed_burnt_amount.checked_add(amount_to_slash).ok_or_else(|| { + RuntimeError::UnexpectedIntegerOverflow( + "update_validator_accounts - slashed".into(), + ) + })?; + account.set_locked(account.locked().checked_sub(amount_to_slash).ok_or_else( + || { + RuntimeError::UnexpectedIntegerOverflow( + "update_validator_accounts - slash locked".into(), + ) + }, + )?); set_account(state_update, account_id.clone(), &account); } else { return Err(StorageError::StorageInconsistentState(format!( @@ -1263,12 +1274,13 @@ impl Runtime { account_id )) })?; - account.set_amount( - account - .amount() - .checked_add(treasury_reward) - .ok_or_else(|| RuntimeError::UnexpectedIntegerOverflow)?, - ); + account.set_amount(account.amount().checked_add(treasury_reward).ok_or_else( + || { + RuntimeError::UnexpectedIntegerOverflow( + "update_validator_accounts - treasure_reward".into(), + ) + }, + )?); set_account(state_update, account_id.clone(), &account); } } @@ -1318,6 +1330,17 @@ impl Runtime { vec![] }; + // Remove the only testnet account with large storage key. + if ProtocolFeature::RemoveAccountWithLongStorageKey.protocol_version() == protocol_version + && migration_flags.is_first_block_with_chunk_of_version + { + let account_id = "contractregistry.testnet".parse().unwrap(); + if get_account(state_update, &account_id)?.is_some() { + remove_account(state_update, &account_id)?; + state_update.commit(StateChangeCause::Migration); + } + } + Ok((gas_used, receipts_to_restore)) } @@ -1343,7 +1366,7 @@ impl Runtime { apply_state: &ApplyState, incoming_receipts: &[Receipt], transactions: &[SignedTransaction], - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), state_patch: SandboxStatePatch, ) -> Result { // state_patch must be empty unless this is sandbox build. Thanks to @@ -1351,41 +1374,53 @@ impl Runtime { // the check is not necessary. It’s defence in depth to make sure any // future refactoring won’t break the condition. assert!(cfg!(feature = "sandbox") || state_patch.is_empty()); - let protocol_version = apply_state.current_protocol_version; - let mut prefetcher = TriePrefetcher::new_if_enabled(&trie); - let mut state_update = TrieUpdate::new(trie); - let mut total = TotalResourceGuard { - span: tracing::Span::current(), - // This contains the gas "burnt" for refund receipts. Even though we don't actually - // charge any gas for refund receipts, we still count the gas use towards the block gas - // limit - gas: 0, - compute: 0, - }; - if let Some(prefetcher) = &mut prefetcher { + // What this function does can be broken down conceptually into the following steps: + // 1. Update validator accounts. + // 2. Apply migrations. + // 3. Process transactions. + // 4. Process receipts. + // 5. Validate and apply the state update. + + let mut processing_state = + ApplyProcessingState::new(&apply_state, trie, epoch_info_provider, transactions); + + if let Some(prefetcher) = &mut processing_state.prefetcher { // Prefetcher is allowed to fail _ = prefetcher.prefetch_transactions_data(transactions); } - let mut stats = ApplyStats::default(); - + // Step 1: update validator accounts. if let Some(validator_accounts_update) = validator_accounts_update { self.update_validator_accounts( - &mut state_update, + &mut processing_state.state_update, validator_accounts_update, - &mut stats, + &mut processing_state.stats, )?; } + // Step 2: apply migrations. let (gas_used_for_migrations, mut receipts_to_restore) = self .apply_migrations( - &mut state_update, + &mut processing_state.state_update, &apply_state.migration_data, &apply_state.migration_flags, - protocol_version, + processing_state.protocol_version, ) .map_err(RuntimeError::StorageError)?; + processing_state.total.add(gas_used_for_migrations, gas_used_for_migrations)?; + + let delayed_receipts = DelayedReceiptQueue::load(&processing_state.state_update)?; + let delayed_receipts = DelayedReceiptQueueWrapper::new(delayed_receipts); + + // If the chunk is missing, exit early and don't process any receipts. + if !apply_state.is_new_chunk + && processing_state.protocol_version + >= ProtocolFeature::FixApplyChunks.protocol_version() + { + return missing_chunk_apply_result(&delayed_receipts, processing_state); + } + // If we have receipts that need to be restored, prepend them to the list of incoming receipts let incoming_receipts = if receipts_to_restore.is_empty() { incoming_receipts @@ -1394,60 +1429,95 @@ impl Runtime { receipts_to_restore.as_slice() }; - let delayed_receipts_queue = DelayedReceiptQueue::load(&state_update)?; - let mut delayed_receipts = DelayedReceiptQueueWrapper::new(delayed_receipts_queue); - let mut own_congestion_info = - apply_state.own_congestion_info(protocol_version, &state_update)?; - - if !apply_state.is_new_chunk - && protocol_version >= ProtocolFeature::FixApplyChunks.protocol_version() - { - let (trie, trie_changes, state_changes) = state_update.finalize()?; - let proof = trie.recorded_storage(); - - return Ok(ApplyResult { - state_root: trie_changes.new_root, - trie_changes, - validator_proposals: vec![], - outgoing_receipts: vec![], - outcomes: vec![], - state_changes, - stats, - processed_delayed_receipts: vec![], - processed_yield_timeouts: vec![], - proof, - delayed_receipts_count: delayed_receipts.len(), - metrics: None, - congestion_info: own_congestion_info, - }); - } - let mut outgoing_receipts = Vec::new(); - let mut validator_proposals = vec![]; - let mut local_receipts = vec![]; - let mut outcomes = vec![]; - let mut processed_delayed_receipts = vec![]; - let mut metrics = metrics::ApplyMetrics::default(); + let mut processing_state = + processing_state.into_processing_receipt_state(incoming_receipts, delayed_receipts); + let mut own_congestion_info = apply_state.own_congestion_info( + processing_state.protocol_version, + &processing_state.state_update, + )?; let mut receipt_sink = ReceiptSink::new( - protocol_version, - &state_update.trie, + processing_state.protocol_version, + &processing_state.state_update.trie, apply_state, &mut own_congestion_info, &mut outgoing_receipts, )?; - // Forward buffered receipts from previous chunks. - receipt_sink.forward_from_buffer(&mut state_update, apply_state)?; + receipt_sink.forward_from_buffer(&mut processing_state.state_update, apply_state)?; + + // Step 3: process transactions. + let local_receipts = self.process_transactions(&mut processing_state, &mut receipt_sink)?; + + // Step 4: process receipts. + let process_receipts_result = + self.process_receipts(&mut processing_state, &mut receipt_sink, &local_receipts)?; + + // After receipt processing is done, report metrics on outgoing buffers + // and on congestion indicators. + metrics::report_congestion_metrics( + &receipt_sink, + apply_state.shard_id, + &apply_state.config.congestion_control_config, + ); + + // Step 5: validate and apply the state update. + self.validate_apply_state_update( + processing_state, + process_receipts_result, + own_congestion_info, + validator_accounts_update, + state_patch, + outgoing_receipts, + ) + } + + fn apply_state_patch(&self, state_update: &mut TrieUpdate, state_patch: SandboxStatePatch) { + if state_patch.is_empty() { + return; + } + for record in state_patch { + match record { + StateRecord::Account { account_id, account } => { + set_account(state_update, account_id, &account); + } + StateRecord::Data { account_id, data_key, value } => { + state_update.set(TrieKey::ContractData { key: data_key.into(), account_id }, value.into()); + } + StateRecord::Contract { account_id, code } => { + let acc = get_account(state_update, &account_id).expect("Failed to read state").expect("Code state record should be preceded by the corresponding account record"); + // Recompute contract code hash. + let code = ContractCode::new(code, None); + set_code(state_update, account_id, &code); + assert_eq!(*code.hash(), acc.code_hash()); + } + StateRecord::AccessKey { account_id, public_key, access_key } => { + set_access_key(state_update, account_id, public_key, &access_key); + } + _ => unimplemented!("patch_state can only patch Account, AccessKey, Contract and Data kind of StateRecord") + } + } + state_update.commit(StateChangeCause::Migration); + } - total.add(gas_used_for_migrations, gas_used_for_migrations)?; + /// Processes a collection of transactions. Returns the receipts generated during processing. + fn process_transactions<'a>( + &self, + processing_state: &mut ApplyProcessingReceiptState<'a>, + receipt_sink: &mut ReceiptSink, + ) -> Result, RuntimeError> { + let total = &mut processing_state.total; + let apply_state = &mut processing_state.apply_state; + let state_update = &mut processing_state.state_update; + let mut local_receipts = Vec::new(); - for signed_transaction in transactions { + for signed_transaction in processing_state.transactions { let (receipt, outcome_with_id) = self.process_transaction( - &mut state_update, + state_update, apply_state, signed_transaction, - &mut stats, + &mut processing_state.stats, )?; if receipt.receiver_id() == signed_transaction.transaction.signer_id() { local_receipts.push(receipt); @@ -1455,154 +1525,182 @@ impl Runtime { receipt_sink.forward_or_buffer_receipt( receipt, apply_state, - &mut state_update, - epoch_info_provider, + state_update, + processing_state.epoch_info_provider, )?; } - - total.add( - outcome_with_id.outcome.gas_burnt, - outcome_with_id - .outcome - .compute_usage - .expect("`process_transaction` must populate compute usage"), - )?; - if !checked_feature!("stable", ComputeCosts, protocol_version) { + let compute = outcome_with_id.outcome.compute_usage; + let compute = compute.expect("`process_transaction` must populate compute usage"); + total.add(outcome_with_id.outcome.gas_burnt, compute)?; + if !checked_feature!("stable", ComputeCosts, processing_state.protocol_version) { assert_eq!(total.compute, total.gas, "Compute usage must match burnt gas"); } - - outcomes.push(outcome_with_id); + processing_state.outcomes.push(outcome_with_id); } - metrics.tx_processing_done(total.gas, total.compute); - - let mut process_receipt = |receipt: &Receipt, - state_update: &mut TrieUpdate, - total: &mut TotalResourceGuard| - -> Result<_, RuntimeError> { - let span = tracing::debug_span!( - target: "runtime", - "process_receipt", - receipt_id = %receipt.receipt_id(), - predecessor = %receipt.predecessor_id(), - receiver = %receipt.receiver_id(), - gas_burnt = tracing::field::Empty, - compute_usage = tracing::field::Empty, - ) - .entered(); - let node_counter_before = state_update.trie().get_trie_nodes_count(); - let recorded_storage_size_before = state_update.trie().recorded_storage_size(); - let storage_proof_size_upper_bound_before = - state_update.trie().recorded_storage_size_upper_bound(); - let result = self.process_receipt( - state_update, - apply_state, - receipt, - &mut receipt_sink, - &mut validator_proposals, - &mut stats, - epoch_info_provider, - ); - let node_counter_after = state_update.trie().get_trie_nodes_count(); - tracing::trace!(target: "runtime", ?node_counter_before, ?node_counter_after); - - let recorded_storage_diff = state_update - .trie() - .recorded_storage_size() - .saturating_sub(recorded_storage_size_before) - as f64; - let recorded_storage_upper_bound_diff = state_update - .trie() - .recorded_storage_size_upper_bound() - .saturating_sub(storage_proof_size_upper_bound_before) - as f64; - metrics::RECEIPT_RECORDED_SIZE.observe(recorded_storage_diff); - metrics::RECEIPT_RECORDED_SIZE_UPPER_BOUND.observe(recorded_storage_upper_bound_diff); - let recorded_storage_proof_ratio = - recorded_storage_upper_bound_diff / f64::max(1.0, recorded_storage_diff); - // Record the ratio only for large receipts, small receipts can have a very high ratio, - // but the ratio is not that important for them. - if recorded_storage_upper_bound_diff > 100_000. { - metrics::RECEIPT_RECORDED_SIZE_UPPER_BOUND_RATIO - .observe(recorded_storage_proof_ratio); - } - if let Some(outcome_with_id) = result? { - let gas_burnt = outcome_with_id.outcome.gas_burnt; - let compute_usage = outcome_with_id - .outcome - .compute_usage - .expect("`process_receipt` must populate compute usage"); - total.add(gas_burnt, compute_usage)?; - span.record("gas_burnt", gas_burnt); - span.record("compute_usage", compute_usage); + processing_state.metrics.tx_processing_done(total.gas, total.compute); + Ok(local_receipts) + } - if !checked_feature!("stable", ComputeCosts, protocol_version) { - assert_eq!(total.compute, total.gas, "Compute usage must match burnt gas"); - } - outcomes.push(outcome_with_id); + /// This function wraps [Runtime::process_receipt]. It adds a tracing span around the latter + /// and populates various metrics. + fn process_receipt_with_metrics<'a>( + &self, + receipt: &Receipt, + processing_state: &mut ApplyProcessingReceiptState<'a>, + mut receipt_sink: &mut ReceiptSink, + mut validator_proposals: &mut Vec, + ) -> Result<(), RuntimeError> { + let span = tracing::debug_span!( + target: "runtime", + "process_receipt", + receipt_id = %receipt.receipt_id(), + predecessor = %receipt.predecessor_id(), + receiver = %receipt.receiver_id(), + gas_burnt = tracing::field::Empty, + compute_usage = tracing::field::Empty, + ) + .entered(); + let total = &mut processing_state.total; + let state_update = &mut processing_state.state_update; + let node_counter_before = state_update.trie().get_trie_nodes_count(); + let recorded_storage_size_before = state_update.trie().recorded_storage_size(); + let storage_proof_size_upper_bound_before = + state_update.trie().recorded_storage_size_upper_bound(); + let result = self.process_receipt( + state_update, + processing_state.apply_state, + receipt, + &mut receipt_sink, + &mut validator_proposals, + &mut processing_state.stats, + processing_state.epoch_info_provider, + ); + let node_counter_after = state_update.trie().get_trie_nodes_count(); + tracing::trace!(target: "runtime", ?node_counter_before, ?node_counter_after); + + let recorded_storage_diff = state_update + .trie() + .recorded_storage_size() + .saturating_sub(recorded_storage_size_before) + as f64; + let recorded_storage_upper_bound_diff = state_update + .trie() + .recorded_storage_size_upper_bound() + .saturating_sub(storage_proof_size_upper_bound_before) + as f64; + let shard_id_str = processing_state.apply_state.shard_id.to_string(); + metrics::RECEIPT_RECORDED_SIZE + .with_label_values(&[shard_id_str.as_str()]) + .observe(recorded_storage_diff); + metrics::RECEIPT_RECORDED_SIZE_UPPER_BOUND + .with_label_values(&[shard_id_str.as_str()]) + .observe(recorded_storage_upper_bound_diff); + let recorded_storage_proof_ratio = + recorded_storage_upper_bound_diff / f64::max(1.0, recorded_storage_diff); + // Record the ratio only for large receipts, small receipts can have a very high ratio, + // but the ratio is not that important for them. + if recorded_storage_upper_bound_diff > 100_000. { + metrics::RECEIPT_RECORDED_SIZE_UPPER_BOUND_RATIO + .with_label_values(&[shard_id_str.as_str()]) + .observe(recorded_storage_proof_ratio); + } + if let Some(outcome_with_id) = result? { + let gas_burnt = outcome_with_id.outcome.gas_burnt; + let compute_usage = outcome_with_id + .outcome + .compute_usage + .expect("`process_receipt` must populate compute usage"); + total.add(gas_burnt, compute_usage)?; + span.record("gas_burnt", gas_burnt); + span.record("compute_usage", compute_usage); + + if !checked_feature!("stable", ComputeCosts, processing_state.protocol_version) { + assert_eq!(total.compute, total.gas, "Compute usage must match burnt gas"); } - Ok(()) - }; - - // TODO(#8859): Introduce a dedicated `compute_limit` for the chunk. - // For now compute limit always matches the gas limit. - let compute_limit = apply_state.gas_limit.unwrap_or(Gas::max_value()); - let proof_size_limit = - if checked_feature!("stable", StateWitnessSizeLimit, protocol_version) { - Some(apply_state.config.witness_config.main_storage_proof_size_soft_limit) - } else { - None - }; + processing_state.outcomes.push(outcome_with_id); + } + Ok(()) + } - // We first process local receipts. They contain staking, local contract calls, etc. + fn process_local_receipts<'a>( + &self, + mut processing_state: &mut ApplyProcessingReceiptState<'a>, + receipt_sink: &mut ReceiptSink, + compute_limit: u64, + proof_size_limit: Option, + validator_proposals: &mut Vec, + local_receipts: &'a [Receipt], + ) -> Result<(), RuntimeError> { let local_processing_start = std::time::Instant::now(); - if let Some(prefetcher) = &mut prefetcher { + if let Some(prefetcher) = &mut processing_state.prefetcher { // Prefetcher is allowed to fail _ = prefetcher.prefetch_receipts_data(&local_receipts); } for receipt in local_receipts.iter() { - if total.compute >= compute_limit + if processing_state.total.compute >= compute_limit || proof_size_limit.is_some_and(|limit| { - state_update.trie.recorded_storage_size_upper_bound() > limit + processing_state.state_update.trie.recorded_storage_size_upper_bound() > limit }) { - delayed_receipts.push(&mut state_update, receipt, &apply_state.config)?; + processing_state.delayed_receipts.push( + &mut processing_state.state_update, + receipt, + &processing_state.apply_state.config, + )?; } else { // NOTE: We don't need to validate the local receipt, because it's just validated in // the `verify_and_charge_transaction`. - process_receipt(receipt, &mut state_update, &mut total)?; + self.process_receipt_with_metrics( + receipt, + &mut processing_state, + receipt_sink, + validator_proposals, + )? } } - metrics.local_receipts_done( + processing_state.metrics.local_receipts_done( local_receipts.len() as u64, local_processing_start.elapsed(), - total.gas, - total.compute, + processing_state.total.gas, + processing_state.total.compute, ); + Ok(()) + } - // Then we process the delayed receipts. It's a backlog of receipts from the past blocks. + fn process_delayed_receipts<'a>( + &self, + mut processing_state: &mut ApplyProcessingReceiptState<'a>, + receipt_sink: &mut ReceiptSink, + compute_limit: u64, + proof_size_limit: Option, + validator_proposals: &mut Vec, + ) -> Result, RuntimeError> { let delayed_processing_start = std::time::Instant::now(); + let protocol_version = processing_state.protocol_version; let mut delayed_receipt_count = 0; - while delayed_receipts.len() > 0 { - if total.compute >= compute_limit + let mut processed_delayed_receipts = vec![]; + while processing_state.delayed_receipts.len() > 0 { + if processing_state.total.compute >= compute_limit || proof_size_limit.is_some_and(|limit| { - state_update.trie.recorded_storage_size_upper_bound() > limit + processing_state.state_update.trie.recorded_storage_size_upper_bound() > limit }) { break; } delayed_receipt_count += 1; - let receipt = delayed_receipts - .pop(&mut state_update, &apply_state.config)? + let receipt = processing_state + .delayed_receipts + .pop(&mut processing_state.state_update, &processing_state.apply_state.config)? .expect("queue is not empty"); - if let Some(prefetcher) = &mut prefetcher { + if let Some(prefetcher) = &mut processing_state.prefetcher { // Prefetcher is allowed to fail _ = prefetcher.prefetch_receipts_data(std::slice::from_ref(&receipt)); } // Validating the delayed receipt. If it fails, it's likely the state is inconsistent. validate_receipt( - &apply_state.config.wasm_config.limit_config, + &processing_state.apply_state.config.wasm_config.limit_config, &receipt, protocol_version, ) @@ -1613,145 +1711,180 @@ impl Runtime { )) })?; - process_receipt(&receipt, &mut state_update, &mut total)?; + self.process_receipt_with_metrics( + &receipt, + &mut processing_state, + receipt_sink, + validator_proposals, + )?; processed_delayed_receipts.push(receipt); } - metrics.delayed_receipts_done( + processing_state.metrics.delayed_receipts_done( delayed_receipt_count, delayed_processing_start.elapsed(), - total.gas, - total.compute, + processing_state.total.gas, + processing_state.total.compute, ); - // And then we process the new incoming receipts. These are receipts from other shards. + Ok(processed_delayed_receipts) + } + + fn process_incoming_receipts<'a>( + &self, + mut processing_state: &mut ApplyProcessingReceiptState<'a>, + receipt_sink: &mut ReceiptSink, + compute_limit: u64, + proof_size_limit: Option, + validator_proposals: &mut Vec, + ) -> Result<(), RuntimeError> { let incoming_processing_start = std::time::Instant::now(); - if let Some(prefetcher) = &mut prefetcher { + let protocol_version = processing_state.protocol_version; + if let Some(prefetcher) = &mut processing_state.prefetcher { // Prefetcher is allowed to fail - _ = prefetcher.prefetch_receipts_data(&incoming_receipts); + _ = prefetcher.prefetch_receipts_data(&processing_state.incoming_receipts); } - for receipt in incoming_receipts.iter() { + for receipt in processing_state.incoming_receipts.iter() { // Validating new incoming no matter whether we have available gas or not. We don't // want to store invalid receipts in state as delayed. validate_receipt( - &apply_state.config.wasm_config.limit_config, + &processing_state.apply_state.config.wasm_config.limit_config, receipt, protocol_version, ) .map_err(RuntimeError::ReceiptValidationError)?; - if total.compute >= compute_limit + if processing_state.total.compute >= compute_limit || proof_size_limit.is_some_and(|limit| { - state_update.trie.recorded_storage_size_upper_bound() > limit + processing_state.state_update.trie.recorded_storage_size_upper_bound() > limit }) { - delayed_receipts.push(&mut state_update, receipt, &apply_state.config)?; + processing_state.delayed_receipts.push( + &mut processing_state.state_update, + receipt, + &processing_state.apply_state.config, + )?; } else { - process_receipt(receipt, &mut state_update, &mut total)?; + self.process_receipt_with_metrics( + &receipt, + &mut processing_state, + receipt_sink, + validator_proposals, + )?; } } - metrics.incoming_receipts_done( - incoming_receipts.len() as u64, + processing_state.metrics.incoming_receipts_done( + processing_state.incoming_receipts.len() as u64, incoming_processing_start.elapsed(), - total.gas, - total.compute, + processing_state.total.gas, + processing_state.total.compute, ); + Ok(()) + } - // Resolve timed-out PromiseYield receipts - let mut promise_yield_indices: PromiseYieldIndices = - get(&state_update, &TrieKey::PromiseYieldIndices)?.unwrap_or_default(); - let initial_promise_yield_indices = promise_yield_indices.clone(); - let mut new_receipt_index: usize = 0; - - let mut processed_yield_timeouts = vec![]; - let mut timeout_receipts = vec![]; - let yield_processing_start = std::time::Instant::now(); - while promise_yield_indices.first_index < promise_yield_indices.next_available_index { - if total.compute >= compute_limit - || proof_size_limit.is_some_and(|limit| { - state_update.trie.recorded_storage_size_upper_bound() > limit - }) - { - break; - } + /// Processes all receipts (local, delayed and incoming). + /// Returns a structure containing the result of the processing. + fn process_receipts<'a>( + &self, + processing_state: &mut ApplyProcessingReceiptState<'a>, + receipt_sink: &mut ReceiptSink, + local_receipts: &'a [Receipt], + ) -> Result { + let mut validator_proposals = vec![]; + let protocol_version = processing_state.protocol_version; + let apply_state = &processing_state.apply_state; - let queue_entry_key = - TrieKey::PromiseYieldTimeout { index: promise_yield_indices.first_index }; + // TODO(#8859): Introduce a dedicated `compute_limit` for the chunk. + // For now compute limit always matches the gas limit. + let compute_limit = apply_state.gas_limit.unwrap_or(Gas::max_value()); + let proof_size_limit = + if checked_feature!("stable", StateWitnessSizeLimit, protocol_version) { + Some(apply_state.config.witness_config.main_storage_proof_size_soft_limit) + } else { + None + }; - let queue_entry = get::(&state_update, &queue_entry_key)? - .ok_or_else(|| { - StorageError::StorageInconsistentState(format!( - "PromiseYield timeout queue entry #{} should be in the state", - promise_yield_indices.first_index - )) - })?; + // We first process local receipts. They contain staking, local contract calls, etc. + self.process_local_receipts( + processing_state, + receipt_sink, + compute_limit, + proof_size_limit, + &mut validator_proposals, + local_receipts, + )?; - // Queue entries are ordered by expires_at - if queue_entry.expires_at > apply_state.block_height { - break; - } + // Then we process the delayed receipts. It's a backlog of receipts from the past blocks. + let processed_delayed_receipts = self.process_delayed_receipts( + processing_state, + receipt_sink, + compute_limit, + proof_size_limit, + &mut validator_proposals, + )?; - // Check if the yielded promise still needs to be resolved - let promise_yield_key = TrieKey::PromiseYieldReceipt { - receiver_id: queue_entry.account_id.clone(), - data_id: queue_entry.data_id, - }; - if state_update.contains_key(&promise_yield_key)? { - let new_receipt_id = create_receipt_id_from_receipt_id( - protocol_version, - &queue_entry.data_id, - &apply_state.prev_block_hash, - &apply_state.block_hash, - new_receipt_index, - ); - new_receipt_index += 1; - - // Create a PromiseResume receipt to resolve the timed-out yield. - let resume_receipt = Receipt::V0(ReceiptV0 { - predecessor_id: queue_entry.account_id.clone(), - receiver_id: queue_entry.account_id.clone(), - receipt_id: new_receipt_id, - receipt: ReceiptEnum::PromiseResume(DataReceipt { - data_id: queue_entry.data_id, - data: None, - }), - }); + // And then we process the new incoming receipts. These are receipts from other shards. + self.process_incoming_receipts( + processing_state, + receipt_sink, + compute_limit, + proof_size_limit, + &mut validator_proposals, + )?; - // The receipt is destined for the local shard and will be placed in the outgoing - // receipts buffer. It is possible that there is already an outgoing receipt resolving - // this yield if `yield_resume` was invoked by some receipt which was processed in - // the current chunk. The ordering will be maintained because the receipts are - // destined for the same shard; the timeout will be processed second and discarded. - receipt_sink.forward_or_buffer_receipt( - resume_receipt.clone(), - apply_state, - &mut state_update, - epoch_info_provider, - )?; - timeout_receipts.push(resume_receipt); - } + // Resolve timed-out PromiseYield receipts + let promise_yield_result = resolve_promise_yield_timeouts( + processing_state, + receipt_sink, + compute_limit, + proof_size_limit, + )?; - processed_yield_timeouts.push(queue_entry); - state_update.remove(queue_entry_key); - // Math checked above: first_index is less than next_available_index - promise_yield_indices.first_index += 1; + let shard_id_str = processing_state.apply_state.shard_id.to_string(); + if processing_state.total.compute >= compute_limit { + metrics::CHUNK_RECEIPTS_LIMITED_BY + .with_label_values(&[shard_id_str.as_str(), "compute_limit"]) + .inc(); + } else if proof_size_limit.is_some_and(|limit| { + processing_state.state_update.trie.recorded_storage_size_upper_bound() > limit + }) { + metrics::CHUNK_RECEIPTS_LIMITED_BY + .with_label_values(&[shard_id_str.as_str(), "storage_proof_size_limit"]) + .inc(); + } else { + metrics::CHUNK_RECEIPTS_LIMITED_BY + .with_label_values(&[shard_id_str.as_str(), "unlimited"]) + .inc(); } - metrics.yield_timeouts_done( - processed_yield_timeouts.len() as u64, - yield_processing_start.elapsed(), - total.gas, - total.compute, - ); - // After receipt processing is done, report metrics on outgoing buffers - // and on congestion indicators. - metrics::report_congestion_metrics( - &receipt_sink, - apply_state.shard_id, - &apply_state.config.congestion_control_config, - ); + Ok(ProcessReceiptsResult { + promise_yield_result, + validator_proposals, + processed_delayed_receipts, + }) + } + + fn validate_apply_state_update<'a>( + &self, + processing_state: ApplyProcessingReceiptState<'a>, + process_receipts_result: ProcessReceiptsResult, + mut own_congestion_info: Option, + validator_accounts_update: &Option, + state_patch: SandboxStatePatch, + outgoing_receipts: Vec, + ) -> Result { let _span = tracing::debug_span!(target: "runtime", "apply_commit").entered(); + let apply_state = processing_state.apply_state; + let mut state_update = processing_state.state_update; + let delayed_receipts = processing_state.delayed_receipts; + let promise_yield_result = process_receipts_result.promise_yield_result; - if promise_yield_indices != initial_promise_yield_indices { - set(&mut state_update, TrieKey::PromiseYieldIndices, &promise_yield_indices); + if promise_yield_result.promise_yield_indices + != promise_yield_result.initial_promise_yield_indices + { + set( + &mut state_update, + TrieKey::PromiseYieldIndices, + &promise_yield_result.promise_yield_indices, + ); } // Congestion info needs a final touch to select an allowed shard if @@ -1760,19 +1893,13 @@ impl Runtime { let delayed_receipts_count = delayed_receipts.len(); if let Some(congestion_info) = &mut own_congestion_info { delayed_receipts.apply_congestion_changes(congestion_info)?; - let other_shards = apply_state - .congestion_info - .keys() - .filter(|&&id| id != apply_state.shard_id) - .copied() - .collect::>(); + let all_shards = apply_state.congestion_info.all_shards(); - let congestion_seed = apply_state.block_height; + let congestion_seed = apply_state.block_height.wrapping_add(apply_state.shard_id); congestion_info.finalize_allowed_shard( apply_state.shard_id, - &other_shards, + all_shards.as_slice(), congestion_seed, - &apply_state.config.congestion_control_config, ); } @@ -1780,20 +1907,24 @@ impl Runtime { &apply_state.config, &state_update, validator_accounts_update, - incoming_receipts, - &timeout_receipts, - transactions, + processing_state.incoming_receipts, + &promise_yield_result.timeout_receipts, + processing_state.transactions, &outgoing_receipts, - &stats, + &processing_state.stats, )?; state_update.commit(StateChangeCause::UpdatedDelayedReceipts); self.apply_state_patch(&mut state_update, state_patch); let chunk_recorded_size_upper_bound = state_update.trie.recorded_storage_size_upper_bound() as f64; - metrics::CHUNK_RECORDED_SIZE_UPPER_BOUND.observe(chunk_recorded_size_upper_bound); + let shard_id_str = apply_state.shard_id.to_string(); + metrics::CHUNK_RECORDED_SIZE_UPPER_BOUND + .with_label_values(&[shard_id_str.as_str()]) + .observe(chunk_recorded_size_upper_bound); let (trie, trie_changes, state_changes) = state_update.finalize()?; - if let Some(prefetcher) = &prefetcher { + + if let Some(prefetcher) = &processing_state.prefetcher { // Only clear the prefetcher queue after finalize is done because as part of receipt // processing we also prefetch account data and access keys that are accessed in // finalize. This data can take a very long time otherwise if not prefetched. @@ -1812,7 +1943,7 @@ impl Runtime { // The order is deterministically changed. let mut unique_proposals = vec![]; let mut account_ids = HashSet::new(); - for proposal in validator_proposals.into_iter().rev() { + for proposal in process_receipts_result.validator_proposals.into_iter().rev() { let account_id = proposal.account_id(); if !account_ids.contains(account_id) { account_ids.insert(account_id.clone()); @@ -1822,54 +1953,31 @@ impl Runtime { let state_root = trie_changes.new_root; let chunk_recorded_size = trie.recorded_storage_size() as f64; - metrics::CHUNK_RECORDED_SIZE.observe(chunk_recorded_size); + metrics::CHUNK_RECORDED_SIZE + .with_label_values(&[shard_id_str.as_str()]) + .observe(chunk_recorded_size); metrics::CHUNK_RECORDED_SIZE_UPPER_BOUND_RATIO + .with_label_values(&[shard_id_str.as_str()]) .observe(chunk_recorded_size_upper_bound / f64::max(1.0, chunk_recorded_size)); let proof = trie.recorded_storage(); + let processed_delayed_receipts = process_receipts_result.processed_delayed_receipts; + let processed_yield_timeouts = promise_yield_result.processed_yield_timeouts; Ok(ApplyResult { state_root, trie_changes, validator_proposals: unique_proposals, outgoing_receipts, - outcomes, + outcomes: processing_state.outcomes, state_changes, - stats, + stats: processing_state.stats, processed_delayed_receipts, processed_yield_timeouts, proof, delayed_receipts_count, - metrics: Some(metrics), + metrics: Some(processing_state.metrics), congestion_info: own_congestion_info, }) } - - fn apply_state_patch(&self, state_update: &mut TrieUpdate, state_patch: SandboxStatePatch) { - if state_patch.is_empty() { - return; - } - for record in state_patch { - match record { - StateRecord::Account { account_id, account } => { - set_account(state_update, account_id, &account); - } - StateRecord::Data { account_id, data_key, value } => { - state_update.set(TrieKey::ContractData { key: data_key.into(), account_id }, value.into()); - } - StateRecord::Contract { account_id, code } => { - let acc = get_account(state_update, &account_id).expect("Failed to read state").expect("Code state record should be preceded by the corresponding account record"); - // Recompute contract code hash. - let code = ContractCode::new(code, None); - set_code(state_update, account_id, &code); - assert_eq!(*code.hash(), acc.code_hash()); - } - StateRecord::AccessKey { account_id, public_key, access_key } => { - set_access_key(state_update, account_id, public_key, &access_key); - } - _ => unimplemented!("patch_state can only patch Account, AccessKey, Contract and Data kind of StateRecord") - } - } - state_update.commit(StateChangeCause::Migration); - } } impl ApplyState { @@ -1950,6 +2058,140 @@ fn action_transfer_or_implicit_account_creation( }) } +fn missing_chunk_apply_result( + delayed_receipts: &DelayedReceiptQueueWrapper, + processing_state: ApplyProcessingState, +) -> Result { + let (trie, trie_changes, state_changes) = processing_state.state_update.finalize()?; + let proof = trie.recorded_storage(); + + // For old chunks, copy the congestion info exactly as it came in, + // potentially returning `None` even if the congestion control + // feature is enabled for the protocol version. + let congestion_info = processing_state + .apply_state + .congestion_info + .get(&processing_state.apply_state.shard_id) + .map(|extended_info| extended_info.congestion_info); + + return Ok(ApplyResult { + state_root: trie_changes.new_root, + trie_changes, + validator_proposals: vec![], + outgoing_receipts: vec![], + outcomes: vec![], + state_changes, + stats: processing_state.stats, + processed_delayed_receipts: vec![], + processed_yield_timeouts: vec![], + proof, + delayed_receipts_count: delayed_receipts.len(), + metrics: None, + congestion_info, + }); +} + +fn resolve_promise_yield_timeouts( + processing_state: &mut ApplyProcessingReceiptState, + receipt_sink: &mut ReceiptSink, + compute_limit: u64, + proof_size_limit: Option, +) -> Result { + let mut state_update = &mut processing_state.state_update; + let total = &mut processing_state.total; + let apply_state = &processing_state.apply_state; + + let mut promise_yield_indices: PromiseYieldIndices = + get(state_update, &TrieKey::PromiseYieldIndices)?.unwrap_or_default(); + let initial_promise_yield_indices = promise_yield_indices.clone(); + let mut new_receipt_index: usize = 0; + + let mut processed_yield_timeouts = vec![]; + let mut timeout_receipts = vec![]; + let yield_processing_start = std::time::Instant::now(); + while promise_yield_indices.first_index < promise_yield_indices.next_available_index { + if total.compute >= compute_limit + || proof_size_limit + .is_some_and(|limit| state_update.trie.recorded_storage_size_upper_bound() > limit) + { + break; + } + + let queue_entry_key = + TrieKey::PromiseYieldTimeout { index: promise_yield_indices.first_index }; + + let queue_entry = + get::(state_update, &queue_entry_key)?.ok_or_else(|| { + StorageError::StorageInconsistentState(format!( + "PromiseYield timeout queue entry #{} should be in the state", + promise_yield_indices.first_index + )) + })?; + + // Queue entries are ordered by expires_at + if queue_entry.expires_at > apply_state.block_height { + break; + } + + // Check if the yielded promise still needs to be resolved + let promise_yield_key = TrieKey::PromiseYieldReceipt { + receiver_id: queue_entry.account_id.clone(), + data_id: queue_entry.data_id, + }; + if state_update.contains_key(&promise_yield_key)? { + let new_receipt_id = create_receipt_id_from_receipt_id( + processing_state.protocol_version, + &queue_entry.data_id, + &apply_state.prev_block_hash, + &apply_state.block_hash, + new_receipt_index, + ); + new_receipt_index += 1; + + // Create a PromiseResume receipt to resolve the timed-out yield. + let resume_receipt = Receipt::V0(ReceiptV0 { + predecessor_id: queue_entry.account_id.clone(), + receiver_id: queue_entry.account_id.clone(), + receipt_id: new_receipt_id, + receipt: ReceiptEnum::PromiseResume(DataReceipt { + data_id: queue_entry.data_id, + data: None, + }), + }); + + // The receipt is destined for the local shard and will be placed in the outgoing + // receipts buffer. It is possible that there is already an outgoing receipt resolving + // this yield if `yield_resume` was invoked by some receipt which was processed in + // the current chunk. The ordering will be maintained because the receipts are + // destined for the same shard; the timeout will be processed second and discarded. + receipt_sink.forward_or_buffer_receipt( + resume_receipt.clone(), + apply_state, + &mut state_update, + processing_state.epoch_info_provider, + )?; + timeout_receipts.push(resume_receipt); + } + + processed_yield_timeouts.push(queue_entry); + state_update.remove(queue_entry_key); + // Math checked above: first_index is less than next_available_index + promise_yield_indices.first_index += 1; + } + processing_state.metrics.yield_timeouts_done( + processed_yield_timeouts.len() as u64, + yield_processing_start.elapsed(), + total.gas, + total.compute, + ); + Ok(ResolvePromiseYieldTimeoutsResult { + timeout_receipts, + initial_promise_yield_indices, + promise_yield_indices, + processed_yield_timeouts, + }) +} + struct TotalResourceGuard { gas: u64, compute: u64, @@ -1971,6 +2213,101 @@ impl TotalResourceGuard { } } +struct ProcessReceiptsResult { + promise_yield_result: ResolvePromiseYieldTimeoutsResult, + validator_proposals: Vec, + processed_delayed_receipts: Vec, +} + +struct ResolvePromiseYieldTimeoutsResult { + timeout_receipts: Vec, + initial_promise_yield_indices: PromiseYieldIndices, + promise_yield_indices: PromiseYieldIndices, + processed_yield_timeouts: Vec, +} + +/// This struct is a convenient way to hold the processing state during [Runtime::apply]. +struct ApplyProcessingState<'a> { + protocol_version: ProtocolVersion, + apply_state: &'a ApplyState, + prefetcher: Option, + state_update: TrieUpdate, + epoch_info_provider: &'a (dyn EpochInfoProvider), + transactions: &'a [SignedTransaction], + total: TotalResourceGuard, + stats: ApplyStats, +} + +impl<'a> ApplyProcessingState<'a> { + fn new( + apply_state: &'a ApplyState, + trie: Trie, + epoch_info_provider: &'a (dyn EpochInfoProvider), + transactions: &'a [SignedTransaction], + ) -> Self { + let protocol_version = apply_state.current_protocol_version; + let prefetcher = TriePrefetcher::new_if_enabled(&trie); + let state_update = TrieUpdate::new(trie); + let total = TotalResourceGuard { + span: tracing::Span::current(), + // This contains the gas "burnt" for refund receipts. Even though we don't actually + // charge any gas for refund receipts, we still count the gas use towards the block gas + // limit + gas: 0, + compute: 0, + }; + let stats = ApplyStats::default(); + Self { + protocol_version, + apply_state, + prefetcher, + state_update, + epoch_info_provider, + transactions, + total, + stats, + } + } + + fn into_processing_receipt_state( + self, + incoming_receipts: &'a [Receipt], + delayed_receipts: DelayedReceiptQueueWrapper, + ) -> ApplyProcessingReceiptState<'a> { + ApplyProcessingReceiptState { + protocol_version: self.protocol_version, + apply_state: self.apply_state, + prefetcher: self.prefetcher, + state_update: self.state_update, + epoch_info_provider: self.epoch_info_provider, + transactions: self.transactions, + total: self.total, + stats: self.stats, + outcomes: Vec::new(), + metrics: metrics::ApplyMetrics::default(), + incoming_receipts, + delayed_receipts, + } + } +} + +/// Similar to [ApplyProcessingState], with the difference that this contains extra state used +/// by receipt processing. +struct ApplyProcessingReceiptState<'a> { + protocol_version: ProtocolVersion, + apply_state: &'a ApplyState, + prefetcher: Option, + state_update: TrieUpdate, + epoch_info_provider: &'a (dyn EpochInfoProvider), + transactions: &'a [SignedTransaction], + total: TotalResourceGuard, + stats: ApplyStats, + outcomes: Vec, + metrics: ApplyMetrics, + incoming_receipts: &'a [Receipt], + delayed_receipts: DelayedReceiptQueueWrapper, +} + #[cfg(test)] mod tests { use assert_matches::assert_matches; @@ -1980,7 +2317,7 @@ mod tests { use near_primitives::action::delegate::{ DelegateAction, NonDelegateAction, SignedDelegateAction, }; - use near_primitives::congestion_info::CongestionControl; + use near_primitives::congestion_info::{CongestionControl, ExtendedCongestionInfo}; use near_primitives::hash::hash; use near_primitives::receipt::ReceiptPriority; use near_primitives::shard_layout::ShardUId; @@ -2009,7 +2346,7 @@ mod tests { fn create_receipt_with_actions( account_id: AccountId, - signer: Arc, + signer: Arc, actions: Vec, ) -> Receipt { Receipt::V0(ReceiptV0 { @@ -2065,8 +2402,7 @@ mod tests { initial_balance: Balance, initial_locked: Balance, gas_limit: Gas, - ) -> (Runtime, ShardTries, CryptoHash, ApplyState, Arc, impl EpochInfoProvider) - { + ) -> (Runtime, ShardTries, CryptoHash, ApplyState, Arc, impl EpochInfoProvider) { setup_runtime_for_shard( initial_balance, initial_locked, @@ -2080,17 +2416,15 @@ mod tests { initial_locked: Balance, gas_limit: Gas, shard_uid: ShardUId, - ) -> (Runtime, ShardTries, CryptoHash, ApplyState, Arc, impl EpochInfoProvider) - { + ) -> (Runtime, ShardTries, CryptoHash, ApplyState, Arc, impl EpochInfoProvider) { let tries = TestTriesBuilder::new().build(); let root = MerkleHash::default(); let runtime = Runtime::new(); let account_id = alice_account(); - let signer = Arc::new(InMemorySigner::from_seed( - account_id.clone(), - KeyType::ED25519, - account_id.as_ref(), - )); + let signer: Arc = Arc::new( + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()) + .into(), + ); let mut initial_state = tries.new_trie_update(shard_uid, root); let mut initial_account = account_new(initial_balance, hash(&[])); @@ -2110,12 +2444,13 @@ mod tests { let root = tries.apply_all(&trie_changes, shard_uid, &mut store_update); store_update.commit().unwrap(); let contract_cache = FilesystemContractRuntimeCache::test().unwrap(); - let congestion_info: HashMap = - if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { - [(0, ExtendedCongestionInfo::default())].into() - } else { - [].into() - }; + let shards_congestion_info = if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) + { + [(0, ExtendedCongestionInfo::default())].into() + } else { + [].into() + }; + let congestion_info = BlockCongestionInfo::new(shards_congestion_info); let apply_state = ApplyState { apply_reason: None, block_height: 1, @@ -2479,8 +2814,8 @@ mod tests { let receipt_exec_gas_fee = 1000; let mut free_config = RuntimeConfig::free(); - free_config.fees.action_fees[ActionCosts::new_action_receipt].execution = - receipt_exec_gas_fee; + let fees = Arc::make_mut(&mut free_config.fees); + fees.action_fees[ActionCosts::new_action_receipt].execution = receipt_exec_gas_fee; apply_state.config = Arc::new(free_config); // This allows us to execute 3 receipts per apply. apply_state.gas_limit = Some(receipt_exec_gas_fee * 3); @@ -3001,7 +3336,8 @@ mod tests { gas: Gas::from(1_000_000u64), compute: Compute::from(10_000_000_000_000u64), }; - free_config.wasm_config.ext_costs.costs[ExtCosts::sha256_base] = sha256_cost.clone(); + let wasm_config = Arc::make_mut(&mut free_config.wasm_config); + wasm_config.ext_costs.costs[ExtCosts::sha256_base] = sha256_cost.clone(); apply_state.config = Arc::new(free_config); // This allows us to execute 1 receipt with a function call per apply. apply_state.gas_limit = Some(sha256_cost.compute); @@ -3376,7 +3712,7 @@ mod tests { // Check congestion is 1.0 let congestion = apply_state.congestion_control(receiver_shard, 0); assert_eq!(congestion.congestion_level(), 1.0); - assert_eq!(congestion.outgoing_limit(local_shard), 0); + assert_eq!(congestion.outgoing_gas_limit(local_shard), 0); // release congestion to just below 1.0, which should allow one receipt // to be forwarded per round @@ -3395,7 +3731,7 @@ mod tests { // this exact number does not matter but if it changes the test setup // needs to adapt to ensure the number of forwarded receipts is as expected assert!( - congestion.outgoing_limit(local_shard) - min_outgoing_gas < 100 * 10u64.pow(9), + congestion.outgoing_gas_limit(local_shard) - min_outgoing_gas < 100 * 10u64.pow(9), "allowed forwarding must be less than 100 GGas away from MIN_OUTGOING_GAS" ); @@ -3437,6 +3773,54 @@ mod tests { } } + /// Create a scenario where `apply` is called without congestion info but + /// cross-shard congestion control is enabled, then check what congestion + /// info is in the apply result. + fn check_congestion_info_bootstrapping(is_new_chunk: bool, want: Option) { + let initial_balance = to_yocto(1_000_000); + let initial_locked = to_yocto(500_000); + let gas_limit = 10u64.pow(15); + let (runtime, tries, root, mut apply_state, _, epoch_info_provider) = + setup_runtime(initial_balance, initial_locked, gas_limit); + + // Delete previous congestion info to trigger bootstrapping it. An empty + // shards congestion info map is what we should see in the first chunk + // with the feature enabled. + apply_state.congestion_info = BlockCongestionInfo::default(); + + // Apply test specific settings + apply_state.is_new_chunk = is_new_chunk; + + let apply_result = runtime + .apply( + tries.get_trie_for_shard(ShardUId::single_shard(), root), + &None, + &apply_state, + &[], + &[], + &epoch_info_provider, + Default::default(), + ) + .unwrap(); + + assert_eq!(want, apply_result.congestion_info); + } + + /// Test that applying a new chunk triggers bootstrapping the congestion + /// info but applying an old chunk doesn't. (We don't want bootstrapping to + /// be triggered on missed chunks.) + #[test] + fn test_congestion_info_bootstrapping() { + if !ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { + return; + } + let is_new_chunk = true; + check_congestion_info_bootstrapping(is_new_chunk, Some(CongestionInfo::default())); + + let is_new_chunk = false; + check_congestion_info_bootstrapping(is_new_chunk, None); + } + // Apply trie changes in `ApplyResult` and update `ApplyState` with new // congestion info for the next call to apply(). fn commit_apply_result( @@ -3462,7 +3846,7 @@ mod tests { fn congestion_control(&self, shard_id: ShardId, missed_chunks: u64) -> CongestionControl { CongestionControl::new( self.config.congestion_control_config, - self.congestion_info[&shard_id].congestion_info, + self.congestion_info.get(&shard_id).unwrap().congestion_info, missed_chunks, ) } @@ -3491,7 +3875,7 @@ pub mod estimator { outgoing_receipts: &mut Vec, validator_proposals: &mut Vec, stats: &mut ApplyStats, - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), ) -> Result { // TODO(congestion_control - edit runtime config parameters for limitless estimator runs let mut congestion_info = CongestionInfo::default(); @@ -3500,7 +3884,7 @@ pub mod estimator { let mut receipt_sink = ReceiptSink::V2(ReceiptSinkV2 { own_congestion_info: &mut congestion_info, - outgoing_limit: outgoing_limit, + outgoing_limit, outgoing_buffers: ShardsOutgoingReceiptBuffer::load(&state_update.trie)?, outgoing_receipts, }); diff --git a/runtime/runtime/src/metrics.rs b/runtime/runtime/src/metrics.rs index 5090f7dec11..3ca9e6ef7e3 100644 --- a/runtime/runtime/src/metrics.rs +++ b/runtime/runtime/src/metrics.rs @@ -1,9 +1,9 @@ use crate::congestion_control::ReceiptSink; use near_o11y::metrics::{ exponential_buckets, linear_buckets, try_create_counter_vec, try_create_gauge_vec, - try_create_histogram_vec, try_create_histogram_with_buckets, try_create_int_counter, - try_create_int_counter_vec, try_create_int_gauge_vec, CounterVec, GaugeVec, Histogram, - HistogramVec, IntCounter, IntCounterVec, IntGaugeVec, + try_create_histogram_vec, try_create_int_counter, try_create_int_counter_vec, + try_create_int_gauge_vec, CounterVec, GaugeVec, HistogramVec, IntCounter, IntCounterVec, + IntGaugeVec, }; use near_parameters::config::CongestionControlConfig; use near_primitives::congestion_info::CongestionInfo; @@ -295,51 +295,57 @@ static CHUNK_TX_TGAS: Lazy = Lazy::new(|| { ) .unwrap() }); -pub static RECEIPT_RECORDED_SIZE: Lazy = Lazy::new(|| { - try_create_histogram_with_buckets( +pub static RECEIPT_RECORDED_SIZE: Lazy = Lazy::new(|| { + try_create_histogram_vec( "near_receipt_recorded_size", "Size of storage proof recorded when executing a receipt", - buckets_for_receipt_storage_proof_size(), + &["shard_id"], + Some(buckets_for_receipt_storage_proof_size()), ) .unwrap() }); -pub static RECEIPT_RECORDED_SIZE_UPPER_BOUND: Lazy = Lazy::new(|| { - try_create_histogram_with_buckets( +pub static RECEIPT_RECORDED_SIZE_UPPER_BOUND: Lazy = Lazy::new(|| { + try_create_histogram_vec( "near_receipt_recorded_size_upper_bound", "Upper bound estimation (e.g with extra size added for deletes) of storage proof size recorded when executing a receipt", - buckets_for_receipt_storage_proof_size(), + &["shard_id"], + Some(buckets_for_receipt_storage_proof_size()), ) .unwrap() }); -pub static RECEIPT_RECORDED_SIZE_UPPER_BOUND_RATIO: Lazy = Lazy::new(|| { - try_create_histogram_with_buckets( +pub static RECEIPT_RECORDED_SIZE_UPPER_BOUND_RATIO: Lazy = Lazy::new(|| { + try_create_histogram_vec( "near_receipt_recorded_size_upper_bound_ratio", "Ratio of upper bound to true recorded size, calculated only for sizes larger than 100KB, equal to (near_receipt_recorded_size_upper_bound / near_receipt_recorded_size)", - buckets_for_storage_proof_size_ratio(), + &["shard_id"], + Some(buckets_for_storage_proof_size_ratio()), ) .unwrap() }); -pub static CHUNK_RECORDED_SIZE: Lazy = Lazy::new(|| { - try_create_histogram_with_buckets( +pub static CHUNK_RECORDED_SIZE: Lazy = Lazy::new(|| { + try_create_histogram_vec( "near_chunk_recorded_size", "Total size of storage proof (recorded trie nodes for state witness, post-finalization) for a single chunk", - buckets_for_chunk_storage_proof_size(), + &["shard_id"], + Some(buckets_for_chunk_storage_proof_size()), ) .unwrap() }); -pub static CHUNK_RECORDED_SIZE_UPPER_BOUND: Lazy = Lazy::new(|| { - try_create_histogram_with_buckets( +pub static CHUNK_RECORDED_SIZE_UPPER_BOUND: Lazy = Lazy::new(|| { + try_create_histogram_vec( "near_chunk_recorded_size_upper_bound", "Upper bound of storage proof size (recorded trie nodes size + estimated charges, pre-finalization) for a single chunk", - buckets_for_chunk_storage_proof_size(), + &["shard_id"], + Some(buckets_for_chunk_storage_proof_size()), ) .unwrap() }); -pub static CHUNK_RECORDED_SIZE_UPPER_BOUND_RATIO: Lazy = Lazy::new(|| { - try_create_histogram_with_buckets( +pub static CHUNK_RECORDED_SIZE_UPPER_BOUND_RATIO: Lazy = Lazy::new(|| { + try_create_histogram_vec( "near_chunk_recorded_size_upper_bound_ratio", "Ratio of upper bound to true storage proof size, equal to (near_chunk_recorded_size_upper_bound / near_chunk_recorded_size)", - buckets_for_storage_proof_size_ratio(), + &["shard_id"], + Some(buckets_for_storage_proof_size_ratio()), ) .unwrap() }); @@ -398,6 +404,15 @@ static CONGESTION_OUTGOING_GAS: Lazy = Lazy::new(|| { .unwrap() }); +pub(crate) static CHUNK_RECEIPTS_LIMITED_BY: Lazy = Lazy::new(|| { + try_create_int_counter_vec( + "near_chunk_receipts_limited_by", + "Number of chunks where the number of processed receipts was limited by a certain factor.", + &["shard_id", "limited_by"], + ) + .unwrap() +}); + /// Buckets used for burned gas in receipts. /// /// The maximum possible is 1300 Tgas for a full chunk. @@ -662,14 +677,14 @@ fn report_outgoing_buffers( inner: &crate::congestion_control::ReceiptSinkV2, sender_shard_label: String, ) { - for (&receiver_shard_id, &unused_capacity) in inner.outgoing_limit.iter() { + for (receiver_shard_id, unused_capacity) in inner.outgoing_limit.iter() { let receiver_shard_label = receiver_shard_id.to_string(); CONGESTION_RECEIPT_FORWARDING_UNUSED_CAPACITY_GAS .with_label_values(&[&sender_shard_label, &receiver_shard_label]) - .set(i64::try_from(unused_capacity).unwrap_or(i64::MAX)); + .set(i64::try_from(unused_capacity.gas).unwrap_or(i64::MAX)); - if let Some(len) = inner.outgoing_buffers.buffer_len(receiver_shard_id) { + if let Some(len) = inner.outgoing_buffers.buffer_len(*receiver_shard_id) { CONGESTION_OUTGOING_RECEIPT_BUFFER_LEN .with_label_values(&[&sender_shard_label, &receiver_shard_label]) .set(i64::try_from(len).unwrap_or(i64::MAX)); diff --git a/runtime/runtime/src/state_viewer/mod.rs b/runtime/runtime/src/state_viewer/mod.rs index b7954849ab7..89100952b38 100644 --- a/runtime/runtime/src/state_viewer/mod.rs +++ b/runtime/runtime/src/state_viewer/mod.rs @@ -11,22 +11,47 @@ use near_primitives::receipt::ActionReceipt; use near_primitives::runtime::migration_data::{MigrationData, MigrationFlags}; use near_primitives::transaction::FunctionCallAction; use near_primitives::trie_key::trie_key_parsers; -use near_primitives::types::{AccountId, EpochInfoProvider, Gas}; +use near_primitives::types::{ + AccountId, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, Gas, ShardId, +}; use near_primitives::version::PROTOCOL_VERSION; -use near_primitives::views::{StateItem, ViewApplyState, ViewStateResult}; +use near_primitives::views::{StateItem, ViewStateResult}; use near_primitives_core::config::ViewConfig; use near_store::{get_access_key, get_account, get_code, TrieUpdate}; -use near_vm_runner::logic::ReturnData; -use near_vm_runner::ContractCode; +use near_vm_runner::logic::{ProtocolVersion, ReturnData}; +use near_vm_runner::{ContractCode, ContractRuntimeCache}; use std::{str, sync::Arc, time::Instant}; use tracing::debug; pub mod errors; +/// State for the view call. +#[derive(Debug)] +pub struct ViewApplyState { + /// Currently building block height. + pub block_height: BlockHeight, + /// Prev block hash + pub prev_block_hash: CryptoHash, + /// Currently building block hash + pub block_hash: CryptoHash, + /// To which shard the applied chunk belongs. + pub shard_id: ShardId, + /// Current epoch id + pub epoch_id: EpochId, + /// Current epoch height + pub epoch_height: EpochHeight, + /// The current block timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC). + pub block_timestamp: u64, + /// Current Protocol version when we apply the state transition + pub current_protocol_version: ProtocolVersion, + /// Cache for compiled contracts. + pub cache: Option>, +} + pub struct TrieViewer { /// Upper bound of the byte size of contract state that is still viewable. None is no limit state_size_limit: Option, - /// Gas limit used when when handling call_function queries. + /// Gas limit used when handling call_function queries. max_gas_burnt_view: Gas, } @@ -161,11 +186,11 @@ impl TrieViewer { method_name: &str, args: &[u8], logs: &mut Vec, - epoch_info_provider: &dyn EpochInfoProvider, + epoch_info_provider: &(dyn EpochInfoProvider), ) -> Result, errors::CallFunctionError> { let now = Instant::now(); let root = *state_update.get_root(); - let mut account = get_account(&state_update, contract_id)?.ok_or_else(|| { + let account = get_account(&state_update, contract_id)?.ok_or_else(|| { errors::CallFunctionError::AccountDoesNotExist { requested_account_id: contract_id.clone(), } @@ -178,11 +203,12 @@ impl TrieViewer { let mut runtime_ext = RuntimeExt::new( &mut state_update, &mut receipt_manager, - contract_id, - &empty_hash, - &view_state.epoch_id, - &view_state.prev_block_hash, - &view_state.block_hash, + contract_id.clone(), + account, + empty_hash, + view_state.epoch_id, + view_state.prev_block_hash, + view_state.block_hash, epoch_info_provider, view_state.current_protocol_version, ); @@ -195,7 +221,7 @@ impl TrieViewer { prev_block_hash: view_state.prev_block_hash, block_hash: view_state.block_hash, shard_id: view_state.shard_id, - epoch_id: view_state.epoch_id.clone(), + epoch_id: view_state.epoch_id, epoch_height: view_state.epoch_height, gas_price: 0, block_timestamp: view_state.block_timestamp, @@ -226,10 +252,9 @@ impl TrieViewer { let outcome = execute_function_call( &apply_state, &mut runtime_ext, - &mut account, originator_id, &action_receipt, - &[], + [].into(), &function_call, &empty_hash, config, diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs index 550aa70ecbc..8930b78a10a 100644 --- a/runtime/runtime/src/verifier.rs +++ b/runtime/runtime/src/verifier.rs @@ -8,7 +8,6 @@ use near_primitives::action::delegate::SignedDelegateAction; use near_primitives::checked_feature; use near_primitives::errors::{ ActionsValidationError, InvalidAccessKeyError, InvalidTxError, ReceiptValidationError, - RuntimeError, }; use near_primitives::receipt::{ActionReceipt, DataReceipt, Receipt, ReceiptEnum}; use near_primitives::transaction::DeleteAccountAction; @@ -97,10 +96,10 @@ pub fn validate_transaction( signed_transaction: &SignedTransaction, verify_signature: bool, current_protocol_version: ProtocolVersion, -) -> Result { +) -> Result { // Don't allow V1 currently. This will be changed when the new protocol version is introduced. if matches!(signed_transaction.transaction, near_primitives::transaction::Transaction::V1(_)) { - return Err(InvalidTxError::InvalidTransactionVersion.into()); + return Err(InvalidTxError::InvalidTransactionVersion); } let transaction = &signed_transaction.transaction; let signer_id = transaction.signer_id(); @@ -110,7 +109,7 @@ pub fn validate_transaction( .signature .verify(signed_transaction.get_hash().as_ref(), transaction.public_key()) { - return Err(InvalidTxError::InvalidSignature.into()); + return Err(InvalidTxError::InvalidSignature); } let transaction_size = signed_transaction.get_size(); @@ -146,7 +145,7 @@ pub fn verify_and_charge_transaction( verify_signature: bool, block_height: Option, current_protocol_version: ProtocolVersion, -) -> Result { +) -> Result { let _span = tracing::debug_span!(target: "runtime", "verify_and_charge_transaction").entered(); let TransactionCost { gas_burnt, gas_remaining, receipt_gas_price, total_cost, burnt_amount } = validate_transaction( @@ -163,7 +162,7 @@ pub fn verify_and_charge_transaction( let mut signer = match get_account(state_update, signer_id)? { Some(signer) => signer, None => { - return Err(InvalidTxError::SignerDoesNotExist { signer_id: signer_id.clone() }.into()); + return Err(InvalidTxError::SignerDoesNotExist { signer_id: signer_id.clone() }); } }; let mut access_key = match get_access_key(state_update, signer_id, transaction.public_key())? { @@ -235,7 +234,7 @@ pub fn verify_and_charge_transaction( .into()) } Err(StorageStakingError::StorageError(err)) => { - return Err(RuntimeError::StorageError(StorageError::StorageInconsistentState(err))) + return Err(StorageError::StorageInconsistentState(err).into()); } }; @@ -295,6 +294,15 @@ pub(crate) fn validate_receipt( receipt: &Receipt, current_protocol_version: ProtocolVersion, ) -> Result<(), ReceiptValidationError> { + let receipt_size: u64 = + borsh::to_vec(receipt).unwrap().len().try_into().expect("Can't convert usize to u64"); + if receipt_size > limit_config.max_receipt_size { + return Err(ReceiptValidationError::ReceiptSizeExceeded { + size: receipt_size, + limit: limit_config.max_receipt_size, + }); + } + // We retain these checks here as to maintain backwards compatibility // with AccountId validation since we illegally parse an AccountId // in near-vm-logic/logic.rs#fn(VMLogic::read_and_parse_account_id) @@ -611,7 +619,7 @@ mod tests { initial_balance: Balance, initial_locked: Balance, access_key: Option, - ) -> (Arc, TrieUpdate, Balance) { + ) -> (Arc, TrieUpdate, Balance) { let access_keys = if let Some(key) = access_key { vec![key] } else { vec![] }; setup_accounts(vec![( alice_account(), @@ -627,16 +635,15 @@ mod tests { // two bools: first one is whether the account has a contract, second one is whether the // account has data accounts: Vec<(AccountId, Balance, Balance, Vec, bool, bool)>, - ) -> (Arc, TrieUpdate, Balance) { + ) -> (Arc, TrieUpdate, Balance) { let tries = TestTriesBuilder::new().build(); let root = MerkleHash::default(); let account_id = alice_account(); - let signer = Arc::new(InMemorySigner::from_seed( - account_id.clone(), - KeyType::ED25519, - account_id.as_ref(), - )); + let signer: Arc = Arc::new( + InMemorySigner::from_seed(account_id.clone(), KeyType::ED25519, account_id.as_ref()) + .into(), + ); let mut initial_state = tries.new_trie_update(ShardUId::single_shard(), root); for (account_id, initial_balance, initial_locked, access_keys, has_contract, has_data) in @@ -713,7 +720,7 @@ mod tests { state_update: &mut TrieUpdate, gas_price: Balance, signed_transaction: &SignedTransaction, - expected_err: RuntimeError, + expected_err: InvalidTxError, ) { assert_eq!( validate_transaction(config, gas_price, signed_transaction, true, PROTOCOL_VERSION) @@ -907,7 +914,7 @@ mod tests { &mut state_update, gas_price, &tx, - RuntimeError::InvalidTxError(InvalidTxError::InvalidSignature), + InvalidTxError::InvalidSignature, ); } @@ -934,12 +941,10 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::AccessKeyNotFound { - account_id: alice_account(), - public_key: bad_signer.public_key().into(), - }, - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::AccessKeyNotFound { + account_id: alice_account(), + public_key: bad_signer.public_key().into(), + },), ); } @@ -949,7 +954,8 @@ mod tests { let (signer, mut state_update, gas_price) = setup_common(TESTING_INIT_BALANCE, 0, Some(AccessKey::full_access())); - config.wasm_config.limit_config.max_total_prepaid_gas = 100; + let wasm_config = Arc::make_mut(&mut config.wasm_config); + wasm_config.limit_config.max_total_prepaid_gas = 100; assert_err_both_validations( &config, @@ -969,12 +975,10 @@ mod tests { CryptoHash::default(), 0, ), - RuntimeError::InvalidTxError(InvalidTxError::ActionsValidation( - ActionsValidationError::TotalPrepaidGasExceeded { - total_prepaid_gas: 200, - limit: 100, - }, - )), + InvalidTxError::ActionsValidation(ActionsValidationError::TotalPrepaidGasExceeded { + total_prepaid_gas: 200, + limit: 100, + }), ); } @@ -1002,9 +1006,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::SignerDoesNotExist { - signer_id: bob_account() - }), + InvalidTxError::SignerDoesNotExist { signer_id: bob_account() }, ); } @@ -1035,7 +1037,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidNonce { tx_nonce: 1, ak_nonce: 2 }), + InvalidTxError::InvalidNonce { tx_nonce: 1, ak_nonce: 2 }, ); } @@ -1057,7 +1059,7 @@ mod tests { u128::max_value(), CryptoHash::default(), ), - RuntimeError::InvalidTxError(InvalidTxError::CostOverflow), + InvalidTxError::CostOverflow, ); } @@ -1080,7 +1082,7 @@ mod tests { CryptoHash::default(), 1, ), - RuntimeError::InvalidTxError(InvalidTxError::InvalidTransactionVersion), + InvalidTxError::InvalidTransactionVersion, ); } @@ -1107,12 +1109,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"); - if let RuntimeError::InvalidTxError(InvalidTxError::NotEnoughBalance { - signer_id, - balance, - cost, - }) = err - { + if let InvalidTxError::NotEnoughBalance { signer_id, balance, cost } = err { assert_eq!(signer_id, alice_account()); assert_eq!(balance, TESTING_INIT_BALANCE); assert!(cost > balance); @@ -1160,9 +1157,12 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"); - if let RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::NotEnoughAllowance { account_id, public_key, allowance, cost }, - )) = err + if let InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::NotEnoughAllowance { + account_id, + public_key, + allowance, + cost, + }) = err { assert_eq!(account_id, alice_account()); assert_eq!(*public_key, signer.public_key()); @@ -1179,7 +1179,8 @@ mod tests { #[test] fn test_validate_transaction_invalid_low_balance() { let mut config = RuntimeConfig::free(); - config.fees.storage_usage_config.storage_amount_per_byte = 10_000_000; + let fees = Arc::make_mut(&mut config.fees); + fees.storage_usage_config.storage_amount_per_byte = 10_000_000; let initial_balance = 1_000_000_000; let transfer_amount = 950_000_000; let (signer, mut state_update, gas_price) = @@ -1210,7 +1211,8 @@ mod tests { #[test] fn test_validate_transaction_invalid_low_balance_many_keys() { let mut config = RuntimeConfig::free(); - config.fees.storage_usage_config.storage_amount_per_byte = 10_000_000; + let fees = Arc::make_mut(&mut config.fees); + fees.storage_usage_config.storage_amount_per_byte = 10_000_000; let initial_balance = 1_000_000_000; let transfer_amount = 950_000_000; let account_id = alice_account(); @@ -1245,11 +1247,11 @@ mod tests { assert_eq!( res, - RuntimeError::InvalidTxError(InvalidTxError::LackBalanceForState { + InvalidTxError::LackBalanceForState { signer_id: account_id, amount: Balance::from(account.storage_usage()) * config.storage_amount_per_byte() - (initial_balance - transfer_amount) - }) + } ); } @@ -1296,9 +1298,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::RequiresFullAccess - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::RequiresFullAccess), ); assert_eq!( @@ -1320,9 +1320,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::RequiresFullAccess - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::RequiresFullAccess), ); assert_eq!( @@ -1344,9 +1342,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::RequiresFullAccess - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::RequiresFullAccess), ); } @@ -1390,12 +1386,10 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::ReceiverMismatch { - tx_receiver: eve_dot_alice_account(), - ak_receiver: bob_account().into() - } - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::ReceiverMismatch { + tx_receiver: eve_dot_alice_account(), + ak_receiver: bob_account().into() + }), ); } @@ -1439,9 +1433,9 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::MethodNameMismatch { method_name: "hello".to_string() } - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::MethodNameMismatch { + method_name: "hello".to_string() + }), ); } @@ -1485,9 +1479,7 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::InvalidAccessKeyError( - InvalidAccessKeyError::DepositWithFunctionCall, - )), + InvalidTxError::InvalidAccessKeyError(InvalidAccessKeyError::DepositWithFunctionCall,), ); } @@ -1509,7 +1501,8 @@ mod tests { let mut config = RuntimeConfig::test(); let max_transaction_size = transaction_size - 1; - config.wasm_config.limit_config.max_transaction_size = transaction_size - 1; + let wasm_config = Arc::make_mut(&mut config.wasm_config); + wasm_config.limit_config.max_transaction_size = transaction_size - 1; assert_eq!( verify_and_charge_transaction( @@ -1522,13 +1515,14 @@ mod tests { PROTOCOL_VERSION, ) .expect_err("expected an error"), - RuntimeError::InvalidTxError(InvalidTxError::TransactionSizeExceeded { + InvalidTxError::TransactionSizeExceeded { size: transaction_size, limit: max_transaction_size - }), + }, ); - config.wasm_config.limit_config.max_transaction_size = transaction_size + 1; + let wasm_config = Arc::make_mut(&mut config.wasm_config); + wasm_config.limit_config.max_transaction_size = transaction_size + 1; verify_and_charge_transaction( &config, &mut state_update, diff --git a/runtime/runtime/tests/runtime_group_tools/mod.rs b/runtime/runtime/tests/runtime_group_tools/mod.rs index 2d3b015b3d0..da4cab147fb 100644 --- a/runtime/runtime/tests/runtime_group_tools/mod.rs +++ b/runtime/runtime/tests/runtime_group_tools/mod.rs @@ -2,7 +2,7 @@ use near_chain_configs::{get_initial_supply, Genesis, GenesisConfig, GenesisReco use near_crypto::{InMemorySigner, KeyType}; use near_parameters::ActionCosts; use near_primitives::account::{AccessKey, Account}; -use near_primitives::congestion_info::ExtendedCongestionInfo; +use near_primitives::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::receipt::Receipt; use near_primitives::runtime::migration_data::{MigrationData, MigrationFlags}; @@ -55,11 +55,13 @@ impl StandaloneRuntime { validators: Vec, ) -> Self { let mut runtime_config = random_config(); + let wasm_config = Arc::make_mut(&mut runtime_config.wasm_config); // Bumping costs to avoid inflation overflows. - runtime_config.wasm_config.limit_config.max_total_prepaid_gas = 10u64.pow(15); - runtime_config.fees.action_fees[ActionCosts::new_action_receipt].execution = + wasm_config.limit_config.max_total_prepaid_gas = 10u64.pow(15); + let fees = Arc::make_mut(&mut runtime_config.fees); + fees.action_fees[ActionCosts::new_action_receipt].execution = runtime_config.wasm_config.limit_config.max_total_prepaid_gas / 64; - runtime_config.fees.action_fees[ActionCosts::new_data_receipt_base].execution = + fees.action_fees[ActionCosts::new_data_receipt_base].execution = runtime_config.wasm_config.limit_config.max_total_prepaid_gas / 64; let runtime = Runtime::new(); @@ -89,17 +91,17 @@ impl StandaloneRuntime { &genesis, account_ids, ); - let congestion_info: HashMap<_, _> = - if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { - genesis - .config - .shard_layout - .shard_ids() - .map(|shard_id| (shard_id, ExtendedCongestionInfo::default())) - .collect() - } else { - Default::default() - }; + let congestion_info = if ProtocolFeature::CongestionControl.enabled(PROTOCOL_VERSION) { + genesis + .config + .shard_layout + .shard_ids() + .map(|shard_id| (shard_id, ExtendedCongestionInfo::default())) + .collect() + } else { + Default::default() + }; + let congestion_info = BlockCongestionInfo::new(congestion_info); let apply_state = ApplyState { apply_reason: None, diff --git a/runtime/runtime/tests/runtime_group_tools/random_config.rs b/runtime/runtime/tests/runtime_group_tools/random_config.rs index 80de3116371..1ae6f7bb986 100644 --- a/runtime/runtime/tests/runtime_group_tools/random_config.rs +++ b/runtime/runtime/tests/runtime_group_tools/random_config.rs @@ -10,7 +10,7 @@ pub fn random_config() -> RuntimeConfig { execution: rng.next_u64() % 1000, }; RuntimeConfig { - fees: RuntimeFeesConfig { + fees: std::sync::Arc::new(RuntimeFeesConfig { action_fees: enum_map::enum_map! { _ => random_fee(), }, @@ -24,7 +24,7 @@ pub fn random_config() -> RuntimeConfig { (101 + rng.next_u32() % 10).try_into().unwrap(), 100, ), - }, + }), ..RuntimeConfig::test() } } diff --git a/runtime/runtime/tests/test_async_calls.rs b/runtime/runtime/tests/test_async_calls.rs index d849cefd239..7ddaa5b4d9a 100644 --- a/runtime/runtime/tests/test_async_calls.rs +++ b/runtime/runtime/tests/test_async_calls.rs @@ -29,7 +29,7 @@ fn test_simple_func_call() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "sum_n".to_string(), args: 10u64.to_le_bytes().to_vec(), @@ -76,7 +76,7 @@ fn test_single_promise_no_callback() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -143,7 +143,7 @@ fn test_single_promise_with_callback() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -228,7 +228,7 @@ fn test_two_promises_no_callbacks() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -323,7 +323,7 @@ fn test_two_promises_with_two_callbacks() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -415,7 +415,7 @@ fn test_single_promise_no_callback_batch() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -488,7 +488,7 @@ fn test_single_promise_with_callback_batch() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -563,7 +563,7 @@ fn test_simple_transfer() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -631,7 +631,7 @@ fn test_create_account_with_transfer_and_full_key() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -744,7 +744,7 @@ fn test_account_factory() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -891,7 +891,7 @@ fn test_create_account_add_key_call_delete_key_delete_account() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -986,7 +986,7 @@ fn test_transfer_64len_hex() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), @@ -1053,7 +1053,7 @@ fn test_create_transfer_64len_hex_fail() { 1, signer_sender.account_id.clone(), signer_receiver.account_id, - &signer_sender, + &signer_sender.into(), vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "call_promise".to_string(), args: serde_json::to_vec(&data).unwrap(), diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 26daf2d4ba2..aff29172ed5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,6 +2,6 @@ # This specifies the version of Rust we use to build. # Individual crates in the workspace may support a lower version, as indicated by `rust-version` field in each crate's `Cargo.toml`. # The version specified below, should be at least as high as the maximum `rust-version` within the workspace. -channel = "1.78.0" -components = [ "rustfmt", "clippy" ] -targets = [ "wasm32-unknown-unknown" ] +channel = "1.79.0" +components = ["rustfmt", "clippy", "rust-analyzer"] +targets = ["wasm32-unknown-unknown"] diff --git a/scripts/binary_release.sh b/scripts/binary_release.sh index 619afb42071..8eb5dafe2aa 100755 --- a/scripts/binary_release.sh +++ b/scripts/binary_release.sh @@ -1,10 +1,10 @@ #!/bin/bash set -xeo pipefail -release="${1:-neard-release}" +release="${1:-release}" case "$release" in - neard-release|nightly-release|perf-release|assertions-release|statelessnet-release) + release|nightly-release|perf-release|assertions-release) ;; *) echo "Unsupported release type '$release'. Please provide no argument for normal release or provide nightly-release for nightly." @@ -35,7 +35,7 @@ function tar_binary { make $release function upload_binary { - if [ "$release" = "neard-release" ] + if [ "$release" = "release" ] then tar_binary $1 tar_file=$1.tar.gz @@ -67,7 +67,12 @@ upload_binary neard # upload_binary store-validator # fi -# if [ "$release" = "release" ] -# then -# upload_binary near-sandbox -# fi +# near-sandbox is used by near-workspaces which is an SDK for end-to-end contracts testing that automatically +# spins up localnet using near-sandbox (neard with extra features useful for testing - state patching, time travel). +# There are JS and Rust SDKs and it wouldn’t be efficient to build nearcore from scratch on the +# user machine and CI, so it relies on the prebuilt binaries. +# example PR https://github.com/near/near-sandbox/pull/81/files +if [ "$release" = "release" ] +then + upload_binary near-sandbox +fi diff --git a/scripts/ft-benchmark-data-sender.py b/scripts/ft-benchmark-data-sender.py new file mode 100644 index 00000000000..9f2f9b97086 --- /dev/null +++ b/scripts/ft-benchmark-data-sender.py @@ -0,0 +1,164 @@ +import requests +from prometheus_client.parser import text_string_to_metric_families +from datetime import datetime +from time import sleep +import numpy as np +import subprocess +from os import chdir +import os +import json +import tempfile +import argparse + +# TPS polling interval in seconds +POLL_INTERVAL = 30 + +# Maximum failed_tx / total_tx ratio we accept +EPS = 0.001 + + +# Returns the total number of transactions processed by the network at the moment +def calculate_processed_transactions() -> int: + interested_metrics = [ + "near_transaction_processed_successfully_total", + "near_transaction_processed_total", + ] + metrics_dict = { + "near_transaction_processed_successfully_total": None, + "near_transaction_processed_total": None, + } + url = "http://127.0.0.1:3030/metrics" + response = requests.get(url) + if response.status_code != 200: + raise f"Failed to retrieve TPS data. HTTP Status code: {response.status_code}" + metrics_data = response.text + for family in text_string_to_metric_families(metrics_data): + for sample in family.samples: + if sample.name in interested_metrics: + metrics_dict[sample.name] = sample.value + if (metrics_dict["near_transaction_processed_total"] - + metrics_dict["near_transaction_processed_successfully_total"] + ) / metrics_dict["near_transaction_processed_total"] > EPS: + raise "Too much failed transactions" + return metrics_dict["near_transaction_processed_successfully_total"] + + +# Returns the number of shards in the network +def calculate_shards() -> int: + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "EXPERIMENTAL_protocol_config", + "params": { + "finality": "final" + }, + } + url = "http://127.0.0.1:3030" + response = requests.post(url, json=payload) + if response.status_code != 200: + raise f"Failed to retrieve shards amount. HTTP Status code: {response.status_code}" + return len(response.json()["result"]["shard_layout"]) + + +# Returns a tuple with the commit hash and the commit timestamp +def get_commit() -> tuple[str, datetime]: + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "status", + "params": [] + } + local_url = "http://127.0.0.1:3030" + response = requests.post(local_url, json=payload) + if response.status_code != 200: + raise f"Failed to retrieve commit hash. HTTP Status code: {response.status_code}" + version = response.json()["result"]["version"]["build"] + short_commit = version.split("-")[2][1:] + github_url = f"https://api.github.com/repos/near/nearcore/commits/{short_commit}" + response = requests.get(github_url) + commit_data = response.json() + full_commit_hash = commit_data["sha"] + commit_timestamp_str = commit_data["commit"]["author"]["date"] + commit_timestamp = datetime.strptime(commit_timestamp_str, + "%Y-%m-%dT%H:%M:%SZ") + return (full_commit_hash, commit_timestamp) + + +def commit_to_db(data: dict) -> None: + print(data) + chdir(os.path.expanduser("~/nearcore/benchmarks/continous/db/tool")) + with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8') as fp: + json.dump(data, fp) + fp.flush() + os.fsync(fp.fileno()) + subprocess.run(f"cargo run -p cli -- insert-ft-transfer {fp.name}", + shell=True) + fp.close() + + +# TODO: send signal to this process if ft-benchmark.sh decided to switch neard to another commit. +# add handling of this signal to this script +if __name__ == "__main__": + + parser = argparse.ArgumentParser( + description= + "Collect data from local prometheus and send to ft-benchmark db.") + parser.add_argument('--duration', + type=int, + required=True, + help='Duration of experiment in seconds') + parser.add_argument('--users', + type=int, + required=True, + help='Number of users') + parser.add_argument('--user', type=str, default='unknown', help='User name') + parser.add_argument('--context', + type=str, + default='unknown', + help='Context') + args = parser.parse_args() + DURATION = args.duration / 3600 + + state_size = (int( + subprocess.check_output(["du", "-s", "~/.near/localnet/node0/data"], + stderr=subprocess.PIPE, + shell=True).decode("utf-8").split()[0]) * 1024) + processed_transactions = [] + time_begin = datetime.now() + while True: + print( + f"Data sender loop. Time elapsed: {(datetime.now() - time_begin).seconds} seconds" + ) + if (datetime.now() - time_begin).seconds / 3600 > DURATION: + break + processed_transactions.append(calculate_processed_transactions()) + sleep(POLL_INTERVAL) + processed_transactions_deltas = np.diff(processed_transactions) + processed_transactions_deltas = np.array( + list(map(lambda x: x / POLL_INTERVAL, processed_transactions_deltas))) + average_tps = np.mean(processed_transactions_deltas) + variance_tps = np.var(processed_transactions_deltas) + # TODO: will be good to have all "probably should be filled by terraform" as command line arguments + # TODO: add start_time and end_time instead of time to db schema + commit_hash, commit_time = get_commit() + response = { + "time": time_begin.strftime('%Y-%m-%dT%H:%M:%SZ'), + "time_end": datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ'), + "git_commit_hash": commit_hash, + "git_commit_time": commit_time.strftime('%Y-%m-%dT%H:%M:%SZ'), + "num_nodes": 1, # TODO: probably should be filled by terraform + "node_hardware": ["n2d-standard-8" + ], # TODO: probably should be filled by terraform + "num_traffic_gen_machines": + 0, # TODO: probably should be filled by terraform + "disjoint_workloads": + False, # TODO: probably should be filled by terraform + "num_shards": calculate_shards(), + "num_unique_users": args.users, + "size_state_bytes": state_size, + "tps": int(average_tps), + "total_transactions": int(processed_transactions[-1]), + "initiator": args.user, + "context": args.context, + } + commit_to_db(response) diff --git a/scripts/ft-benchmark.sh b/scripts/ft-benchmark.sh index 6c87fd6381d..90f661b935a 100755 --- a/scripts/ft-benchmark.sh +++ b/scripts/ft-benchmark.sh @@ -8,38 +8,17 @@ date # Otherwise nearup and cargo don't work even if installed properly PATH=/home/ubuntu/.local/bin/:$PATH export PATH=$PATH:$HOME/.cargo/bin +source benchmarks/continous/db/tool/dbprofile # Fetch the latest changes from the remote git fetch -# Check if the local master branch is up to date with the remote master branch -LOCAL=$(git rev-parse master) -REMOTE=$(git rev-parse origin/master) +git pull -if [ $LOCAL = $REMOTE ]; then - echo "The repository is up to date with the remote. No rebuilds or restarts needed." - exit 0 -else - echo "The local repository is behind the remote. Pulling changes..." - git pull -fi - -# Stop previous experiment -pkill -9 locust || true -nearup stop - -make neard - -# Start neard -nearup run localnet --binary-path target/release/ --num-nodes 1 --num-shards 1 --override - -# Prepare python environment -python3 -m venv .venv -source .venv/bin/activate -python -m pip install -r pytest/requirements.txt -python -m pip install locust -export KEY=~/.near/localnet/node0/validator_key.json - -# Run benchmark -cd pytest/tests/loadtest/locust/ -locust -H 127.0.0.1:3030 -f locustfiles/ft.py --funding-key=$KEY -u 3500 -r 10 --processes 8 --headless +# some logging improvements +NEW_COMMIT_HASH=$(git rev-parse origin/master) +LOG_DIR=scripts/ft-benchmark-logs +MAIN_LOG_FILE=$LOG_DIR/${NEW_COMMIT_HASH}.log +exec > >(tee -a $MAIN_LOG_FILE) 2>&1 +export DATABASE_URL_CLI=postgres://benchmark_runner@34.90.190.128/benchmarks +python3 scripts/run-ft-benchmark.py --user "scheduled_run_on_crt_ft_benchmark" diff --git a/scripts/mac-release.sh b/scripts/mac-release.sh index be3544f59a5..04360caaba3 100755 --- a/scripts/mac-release.sh +++ b/scripts/mac-release.sh @@ -67,7 +67,6 @@ function upload_binary { } upload_binary neard -upload_binary store-validator if [ "$release" == "release" ] then diff --git a/scripts/run-ft-benchmark.py b/scripts/run-ft-benchmark.py new file mode 100644 index 00000000000..a15a99c05de --- /dev/null +++ b/scripts/run-ft-benchmark.py @@ -0,0 +1,70 @@ +import argparse +import os +import subprocess +from locust.util.timespan import parse_timespan + +LOCK_FILE = "/tmp/run-ft-benchmark.lock" +REPO_DIR = os.path.expanduser("~/nearcore") + + +def create_lock_file(user: str) -> None: + if os.path.exists(LOCK_FILE): + with open(LOCK_FILE, 'r') as f: + running_user = f.read().strip() + raise RuntimeError(f"{running_user} already running benchmark") + with open(LOCK_FILE, 'w+') as f: + f.write(user) + + +def remove_lock_file() -> None: + if os.path.exists(LOCK_FILE): + os.remove(LOCK_FILE) + else: + raise RuntimeError("Somebody already removed the lock file!!!") + + +def run_benchmark(repo_dir: str, time: str, users: int, shards: int, nodes: int, + rump_up: int, user: str, context: str) -> None: + benchmark_command = ( + f"./scripts/start-benchmark.sh {time} {users} {shards} {nodes} {rump_up} {user} {context}" + ) + subprocess.run(benchmark_command, cwd=repo_dir, shell=True, check=True) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Run FT benchmark", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--time', + type=str, + default='1h', + help="Time duration (e.g., 2h, 30m, 45s)") + parser.add_argument('--users', + type=int, + default=1000, + help="Number of users") + parser.add_argument('--shards', + type=int, + default=1, + help="Number of shards") + parser.add_argument('--nodes', type=int, default=1, help="Number of nodes") + parser.add_argument('--rump-up', type=int, default=10, help="Rump-up rate") + parser.add_argument('--user', type=str, default='unknown', help="User name") + parser.add_argument('--context', + type=str, + default='unknown', + help="Context") + args = parser.parse_args() + time_seconds = parse_timespan(args.time) + try: + create_lock_file(args.user) + run_benchmark(REPO_DIR, time_seconds, args.users, args.shards, + args.nodes, args.rump_up, args.user, args.context) + except RuntimeError as e: + print(e) + finally: + remove_lock_file() + + +if __name__ == "__main__": + main() diff --git a/scripts/start-benchmark.sh b/scripts/start-benchmark.sh new file mode 100755 index 00000000000..469136432be --- /dev/null +++ b/scripts/start-benchmark.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Get arguments from the command line +TIME=$1 +USERS=$2 +SHARDS=$3 +NODES=$4 +RUMP_UP=$5 +USER=$6 +CONTEXT=$7 + +# Stop previous experiment +pkill -9 locust || true +nearup stop + +# Build the project +make neard + +# Start neard +nearup run localnet --binary-path target/release/ --num-nodes $NODES --num-shards $SHARDS --override + +# Prepare python environment +python3 -m venv .venv +source .venv/bin/activate +python -m pip install -r pytest/requirements.txt +python -m pip install locust +export KEY=~/.near/localnet/node0/validator_key.json + +# Run benchmark +cd pytest/tests/loadtest/locust/ +nohup locust -H 127.0.0.1:3030 -f locustfiles/ft.py --funding-key=$KEY -t "${TIME}s" -u $USERS -r $RUMP_UP --processes 8 --headless & + +# Give locust 0.5 minutes to start and rump up +sleep 30 + +# Run data collector +cd ~/nearcore +python3 scripts/ft-benchmark-data-sender.py --duration $TIME --users $USERS --user $USER --context $CONTEXT + +echo "Benchmark completed." diff --git a/test-utils/runtime-tester/src/run_test.rs b/test-utils/runtime-tester/src/run_test.rs index 56540ba0b06..63597a937e2 100644 --- a/test-utils/runtime-tester/src/run_test.rs +++ b/test-utils/runtime-tester/src/run_test.rs @@ -16,6 +16,7 @@ use near_vm_runner::{ContractRuntimeCache, FilesystemContractRuntimeCache}; use nearcore::NightshadeRuntime; use std::io; use std::path::Path; +use std::sync::Arc; use std::time::Duration; pub struct ScenarioResult { @@ -38,8 +39,8 @@ impl Scenario { let clients = vec![accounts[0].clone()]; let mut genesis = Genesis::test(accounts, 1); let mut runtime_config = near_parameters::RuntimeConfig::test(); - runtime_config.wasm_config.limit_config.max_total_prepaid_gas = - self.runtime_config.max_total_prepaid_gas; + let wasm_config = Arc::make_mut(&mut runtime_config.wasm_config); + wasm_config.limit_config.max_total_prepaid_gas = self.runtime_config.max_total_prepaid_gas; genesis.config.epoch_length = self.runtime_config.epoch_length; genesis.config.gas_limit = self.runtime_config.gas_limit; let runtime_config_store = RuntimeConfigStore::with_one_config(runtime_config); @@ -182,7 +183,7 @@ impl TransactionConfig { self.nonce, self.signer_id.clone(), self.receiver_id.clone(), - &self.signer, + &self.signer.clone().into(), self.actions.clone(), *last_block.hash(), 0, diff --git a/test-utils/store-validator/src/main.rs b/test-utils/store-validator/src/main.rs index 14a60da94b9..326c446c729 100644 --- a/test-utils/store-validator/src/main.rs +++ b/test-utils/store-validator/src/main.rs @@ -52,7 +52,7 @@ fn main() { ) .expect("could not create transaction runtime"); let mut store_validator = StoreValidator::new( - near_config.validator_signer.as_ref().map(|x| x.validator_id().clone()), + near_config.validator_signer.get().map(|x| x.validator_id().clone()), near_config.genesis.config, epoch_manager, shard_tracker, diff --git a/tools/amend-genesis/Cargo.toml b/tools/amend-genesis/Cargo.toml index c64a4050793..cca0042923d 100644 --- a/tools/amend-genesis/Cargo.toml +++ b/tools/amend-genesis/Cargo.toml @@ -20,11 +20,11 @@ serde.workspace = true serde_json.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-chain-configs.workspace = true near-crypto.workspace = true near-primitives.workspace = true near-primitives-core.workspace = true [dev-dependencies] -tempfile.workspace = true \ No newline at end of file +tempfile.workspace = true diff --git a/tools/amend-genesis/src/cli.rs b/tools/amend-genesis/src/cli.rs index 3afad616594..0559ccc3e12 100644 --- a/tools/amend-genesis/src/cli.rs +++ b/tools/amend-genesis/src/cli.rs @@ -54,6 +54,9 @@ pub struct AmendGenesisCommand { /// chunk_producer_kickout_threshold to set in the output genesis file #[clap(long)] chunk_producer_kickout_threshold: Option, + /// chunk_validator_only_kickout_threshold to set in the output genesis file + #[clap(long)] + chunk_validator_only_kickout_threshold: Option, /// protocol_reward_rate to set in the output genesis file. Give a ratio here (e.g. "1/10") #[clap(long)] protocol_reward_rate: Option, @@ -94,6 +97,7 @@ impl AmendGenesisCommand { max_inflation_rate: self.max_inflation_rate, block_producer_kickout_threshold: self.block_producer_kickout_threshold, chunk_producer_kickout_threshold: self.chunk_producer_kickout_threshold, + chunk_validator_only_kickout_threshold: self.chunk_validator_only_kickout_threshold, gas_limit: self.gas_limit, min_gas_price: self.min_gas_price, max_gas_price: self.max_gas_price, diff --git a/tools/amend-genesis/src/lib.rs b/tools/amend-genesis/src/lib.rs index a5019b02778..b4b71d9e301 100644 --- a/tools/amend-genesis/src/lib.rs +++ b/tools/amend-genesis/src/lib.rs @@ -280,6 +280,7 @@ pub struct GenesisChanges { pub max_inflation_rate: Option, pub block_producer_kickout_threshold: Option, pub chunk_producer_kickout_threshold: Option, + pub chunk_validator_only_kickout_threshold: Option, pub gas_limit: Option, pub min_gas_price: Option, pub max_gas_price: Option, @@ -409,6 +410,9 @@ pub fn amend_genesis( if let Some(t) = genesis_changes.chunk_producer_kickout_threshold { genesis.config.chunk_producer_kickout_threshold = t; } + if let Some(t) = genesis_changes.chunk_validator_only_kickout_threshold { + genesis.config.chunk_validator_only_kickout_threshold = t; + } if let Some(l) = genesis_changes.gas_limit { genesis.config.gas_limit = l; } @@ -426,7 +430,6 @@ pub fn amend_genesis( #[cfg(test)] mod test { use anyhow::Context; - use near_async::time::Clock; use near_chain_configs::{get_initial_supply, Genesis, GenesisConfig, NEAR_BASE}; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardLayout; @@ -436,6 +439,7 @@ mod test { use near_primitives::version::PROTOCOL_VERSION; use near_primitives_core::account::{AccessKey, Account}; use near_primitives_core::types::{Balance, StorageUsage}; + use near_time::Clock; use num_rational::Rational32; use std::collections::{HashMap, HashSet}; use std::str::FromStr; @@ -641,6 +645,8 @@ mod test { near_chain_configs::BLOCK_PRODUCER_KICKOUT_THRESHOLD, chunk_producer_kickout_threshold: near_chain_configs::CHUNK_PRODUCER_KICKOUT_THRESHOLD, + chunk_validator_only_kickout_threshold: + near_chain_configs::CHUNK_VALIDATOR_ONLY_KICKOUT_THRESHOLD, online_max_threshold: Rational32::new(99, 100), online_min_threshold: Rational32::new( near_chain_configs::BLOCK_PRODUCER_KICKOUT_THRESHOLD as i32, diff --git a/tools/chainsync-loadtest/src/main.rs b/tools/chainsync-loadtest/src/main.rs index 03490f94928..c47e642767a 100644 --- a/tools/chainsync-loadtest/src/main.rs +++ b/tools/chainsync-loadtest/src/main.rs @@ -10,6 +10,7 @@ use near_async::messaging::IntoSender; use near_async::messaging::LateBoundSender; use near_async::time; use near_chain_configs::Genesis; +use near_chain_configs::MutableConfigValue; use near_network::concurrency::ctx; use near_network::concurrency::scope; use near_network::PeerManagerActor; @@ -69,7 +70,8 @@ fn download_configs(chain_id: &str, dir: &std::path::Path) -> anyhow::Result /// /// Changing this could be part of a congestion strategy. pub const TX_GAS_LIMIT: GGas = 500 * TGAS; diff --git a/tools/database/src/analyze_contract_sizes.rs b/tools/database/src/analyze_contract_sizes.rs index ac14e423db4..ecda0eede9d 100644 --- a/tools/database/src/analyze_contract_sizes.rs +++ b/tools/database/src/analyze_contract_sizes.rs @@ -1,8 +1,3 @@ -use std::collections::BTreeMap; -use std::path::PathBuf; -use std::rc::Rc; -use std::str::FromStr; - use bytesize::ByteSize; use clap::Parser; use near_chain::{ChainStore, ChainStoreAccess}; @@ -12,6 +7,11 @@ use near_primitives::trie_key::col; use near_primitives::types::AccountId; use near_store::{ShardUId, Trie, TrieDBStorage}; use nearcore::{load_config, open_storage}; +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::rc::Rc; +use std::str::FromStr; +use std::sync::Arc; #[derive(Parser)] pub(crate) struct AnalyzeContractSizesCommand { @@ -95,7 +95,7 @@ impl AnalyzeContractSizesCommand { chain_store.get_chunk_extra(&head.last_block_hash, &shard_uid).unwrap(); let state_root = chunk_extra.state_root(); - let trie_storage = Rc::new(TrieDBStorage::new(store.clone(), shard_uid)); + let trie_storage = Arc::new(TrieDBStorage::new(store.clone(), shard_uid)); let trie = Trie::new(trie_storage, *state_root, None); let mut iterator = trie.disk_iter().unwrap(); diff --git a/tools/database/src/commands.rs b/tools/database/src/commands.rs index 8905b7f6bd8..95e55953a2d 100644 --- a/tools/database/src/commands.rs +++ b/tools/database/src/commands.rs @@ -41,7 +41,7 @@ enum SubCommand { /// Make snapshot of the database MakeSnapshot(MakeSnapshotCommand), - /// Run migrations, + /// Run migrations RunMigrations(RunMigrationsCommand), /// Run performance test for State column reads. diff --git a/tools/database/src/memtrie.rs b/tools/database/src/memtrie.rs index d61af3dc04e..4b5196eafa6 100644 --- a/tools/database/src/memtrie.rs +++ b/tools/database/src/memtrie.rs @@ -19,6 +19,8 @@ use std::time::Duration; pub struct LoadMemTrieCommand { #[clap(long, use_value_delimiter = true, value_delimiter = ',')] shard_id: Option>, + #[clap(long)] + no_parallel: bool, } impl LoadMemTrieCommand { @@ -59,8 +61,14 @@ impl LoadMemTrieCommand { .context("could not create the transaction runtime")?; println!("Loading memtries for shards {:?}...", selected_shard_uids); - runtime.get_tries().load_mem_tries_for_enabled_shards(&selected_shard_uids)?; - println!("Finished loading memtries, press Ctrl-C to exit."); + let start_time = std::time::Instant::now(); + runtime + .get_tries() + .load_mem_tries_for_enabled_shards(&selected_shard_uids, !self.no_parallel)?; + println!( + "Finished loading memtries, took {:?}, press Ctrl-C to exit.", + start_time.elapsed() + ); std::thread::sleep(Duration::from_secs(10_000_000_000)); Ok(()) } diff --git a/tools/debug-ui/package-lock.json b/tools/debug-ui/package-lock.json index e6f55d951d2..0e64ed46255 100644 --- a/tools/debug-ui/package-lock.json +++ b/tools/debug-ui/package-lock.json @@ -13,6 +13,7 @@ "@types/node": "^16.18.77", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", + "date-fns": "^3.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1", @@ -5841,11 +5842,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6790,6 +6791,16 @@ "node": ">=10" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -8549,9 +8560,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -22299,11 +22310,11 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -22972,6 +22983,11 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==" + }, "debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -24251,9 +24267,9 @@ "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } diff --git a/tools/debug-ui/package.json b/tools/debug-ui/package.json index 572cac4937c..16c9e70384d 100644 --- a/tools/debug-ui/package.json +++ b/tools/debug-ui/package.json @@ -4,12 +4,13 @@ "private": true, "dependencies": { "@patternfly/react-log-viewer": "^4.87.101", + "@tanstack/react-query": "^4.0.0", "@types/node": "^16.18.77", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", + "date-fns": "^3.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "@tanstack/react-query": "^4.0.0", "react-router-dom": "^6.21.1", "react-scripts": "^5.0.1", "react-tooltip": "^5.22.0", @@ -48,4 +49,4 @@ "typescript": "^4.9.5", "typescript-plugin-css-modules": "^4.2.2" } -} \ No newline at end of file +} diff --git a/tools/debug-ui/src/EpochValidatorsView.scss b/tools/debug-ui/src/EpochValidatorsView.scss index 0c08bcf7b9d..e489977c7ab 100644 --- a/tools/debug-ui/src/EpochValidatorsView.scss +++ b/tools/debug-ui/src/EpochValidatorsView.scss @@ -82,37 +82,60 @@ &:nth-child(2), &:nth-child(3), - &:nth-child(4), - &:nth-child(5) { + &:nth-child(4) { opacity: 0.5; } - &:nth-child(6) { + &:nth-child(5) { border-left: $current-border; } - &:nth-child(10) { + &:nth-child(9) { border-right: $current-border; } } thead tr:nth-child(2) th { + &:nth-child(5), &:nth-child(6), &:nth-child(7), &:nth-child(8), - &:nth-child(9), - &:nth-child(10) { + &:nth-child(9) { background-color: #d6ffd0; } } tbody tr:last-child td { + &:nth-child(5), &:nth-child(6), &:nth-child(7), &:nth-child(8), - &:nth-child(9), - &:nth-child(10) { + &:nth-child(9) { border-bottom: $current-border; } } } + +.validator-role { + padding: 4px 8px; + border-radius: 8px; + color: black; + margin: 0px 2px; + font-size: 12px; + font-weight: bold; +} + +.block-producer { + @extend .validator-role; + background-color: thistle; +} + +.chunk-producer { + @extend .validator-role; + background-color: lightblue; +} + +.chunk-validator { + @extend .validator-role; + background-color: bisque; +} diff --git a/tools/debug-ui/src/EpochValidatorsView.tsx b/tools/debug-ui/src/EpochValidatorsView.tsx index ae2ffc951a3..882037a5300 100644 --- a/tools/debug-ui/src/EpochValidatorsView.tsx +++ b/tools/debug-ui/src/EpochValidatorsView.tsx @@ -9,13 +9,27 @@ interface ProducedAndExpected { expected: number; } -type ValidatorRole = 'BlockProducer' | 'ChunkOnlyProducer' | 'None'; +interface BlockProducer { + kind: 'BlockProducer' +} + +interface ChunkProducer { + kind: 'ChunkProducer' + shards: number[]; +} + +interface ChunkValidator { + kind: 'ChunkValidator' +} + +type ValidatorRole = BlockProducer | ChunkProducer | ChunkValidator; interface CurrentValidatorInfo { stake: number; shards: number[]; blocks: ProducedAndExpected; chunks: ProducedAndExpected; + endorsements: ProducedAndExpected; } interface NextValidatorInfo { @@ -29,7 +43,7 @@ interface ValidatorInfo { next: NextValidatorInfo | null; proposalStake: number | null; kickoutReason: ValidatorKickoutReason | null; - roles: ValidatorRole[]; + roles: ValidatorRole[][]; } class Validators { @@ -41,9 +55,9 @@ class Validators { if (this.validators.has(accountId)) { return this.validators.get(accountId)!; } - const roles = [] as ValidatorRole[]; + const roles = [] as ValidatorRole[][]; for (let i = 0; i < this.numEpochs; i++) { - roles.push('None'); + roles.push([]); } this.validators.set(accountId, { accountId, @@ -56,9 +70,12 @@ class Validators { return this.validators.get(accountId)!; } - setValidatorRole(accountId: string, epochIndex: number, role: ValidatorRole) { + addValidatorRole(accountId: string, epochIndex: number, role: ValidatorRole) { const validator = this.validator(accountId); - validator.roles[epochIndex] = role; + validator.roles[epochIndex].push(role); + validator.roles[epochIndex].sort((a, b) => { + return a.kind.localeCompare(b.kind) + }) } sorted(): ValidatorInfo[] { @@ -107,7 +124,8 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { let maxStake = 0, totalStake = 0, maxExpectedBlocks = 0, - maxExpectedChunks = 0; + maxExpectedChunks = 0, + maxExpectedEndorsements = 0; const epochs = epochData!.status_response.EpochInfo; const validators = new Validators(epochs.length); const currentValidatorInfo = epochData!.status_response.EpochInfo[1].validator_info; @@ -125,11 +143,19 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { produced: validatorInfo.num_produced_chunks, expected: validatorInfo.num_expected_chunks, }, + endorsements: { + produced: validatorInfo.num_produced_endorsements, + expected: validatorInfo.num_expected_endorsements, + }, }; maxStake = Math.max(maxStake, stake); totalStake += stake; maxExpectedBlocks = Math.max(maxExpectedBlocks, validatorInfo.num_expected_blocks); maxExpectedChunks = Math.max(maxExpectedChunks, validatorInfo.num_expected_chunks); + maxExpectedEndorsements = Math.max( + maxExpectedEndorsements, + validatorInfo.num_expected_endorsements + ); } for (const validatorInfo of currentValidatorInfo.next_validators) { const validator = validators.validator(validatorInfo.account_id); @@ -137,6 +163,9 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { stake: parseFloat(validatorInfo.stake), shards: validatorInfo.shards, }; + if (validatorInfo.shards.length > 0) { + validators.addValidatorRole(validator.accountId, 0, { kind: 'ChunkProducer', shards: validatorInfo.shards }); + } } for (const proposal of currentValidatorInfo.current_proposals) { const validator = validators.validator(proposal.account_id); @@ -147,11 +176,18 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { validator.kickoutReason = kickout.reason; } epochs.forEach((epochInfo, index) => { - for (const chunkOnlyProducer of epochInfo.chunk_only_producers) { - validators.setValidatorRole(chunkOnlyProducer, index, 'ChunkOnlyProducer'); - } for (const blockProducer of epochInfo.block_producers) { - validators.setValidatorRole(blockProducer.account_id, index, 'BlockProducer'); + validators.addValidatorRole(blockProducer.account_id, index, { kind: 'BlockProducer'}); + } + if (epochInfo.validator_info != null) { + for (const validator of epochInfo.validator_info.current_validators) { + if (validator.num_expected_chunks > 0) { + validators.addValidatorRole(validator.account_id, index, { kind: 'ChunkProducer', shards: validator.shards }); + } + if (validator.num_expected_endorsements > 0) { + validators.addValidatorRole(validator.account_id, index, { kind: 'ChunkValidator'}); + } + } } }); @@ -160,23 +196,22 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { - Next Epoch + Next Epoch Current Epoch Past Epochs Validator - Role - Shards + Roles (shards) Stake Proposal - Role - Shards + Roles (shards) Stake Blocks - Chunks + Produced Chunks + Endorsed Chunks Kickout {epochs.slice(2).map((epoch) => { @@ -193,15 +228,13 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { return ( {validator.accountId} - {renderRole(validator.roles[0])} - {validator.next?.shards?.join(',') ?? ''} + {renderRoles(validator.roles[0])} {drawStakeBar(validator.next?.stake ?? null, maxStake, totalStake)} {drawStakeBar(validator.proposalStake, maxStake, totalStake)} - {renderRole(validator.roles[1])} - {validator.current?.shards?.join(',') ?? ''} + {renderRoles(validator.roles[1])} {drawStakeBar( validator.current?.stake ?? null, @@ -221,12 +254,18 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { maxExpectedChunks )} + + {drawProducedAndExpectedBar( + validator.current?.endorsements ?? null, + maxExpectedEndorsements + )} + - {validator.roles.slice(2).map((role, i) => { - return {renderRole(role)}; + {validator.roles.slice(2).map((roles, i) => { + return {renderRoles(roles)}; })} ); @@ -238,7 +277,8 @@ export const EpochValidatorsView = ({ addr }: EpochValidatorViewProps) => { function drawProducedAndExpectedBar( producedAndExpected: ProducedAndExpected | null, - maxExpected: number + maxExpected: number, + scale = 1 ): JSX.Element { if (producedAndExpected === null) { return <>; @@ -263,10 +303,10 @@ function drawProducedAndExpectedBar( return (
{produced}
-
+
{produced !== expected && ( <> -
+
{expected - produced}
)} @@ -291,15 +331,22 @@ function drawStakeBar(stake: number | null, maxStake: number, totalStake: number ); } -function renderRole(role: ValidatorRole): JSX.Element { - switch (role) { - case 'BlockProducer': - return BP; - case 'ChunkOnlyProducer': - return CP; - default: - return <>; +function renderRoles(roles: ValidatorRole[]): JSX.Element { + const renderedItems = []; + for (const role of roles) { + switch (role.kind) { + case 'BlockProducer': + renderedItems.push(BP); + break; + case 'ChunkProducer': + renderedItems.push(CP({role.shards.join(",")})); + break; + case 'ChunkValidator': + renderedItems.push(CV); + break; + } } + return <>{renderedItems}; } const KickoutReason = ({ reason }: { reason: ValidatorKickoutReason | null }) => { diff --git a/tools/debug-ui/src/RecentEpochsView.tsx b/tools/debug-ui/src/RecentEpochsView.tsx index 992f0a5838b..da281372f46 100644 --- a/tools/debug-ui/src/RecentEpochsView.tsx +++ b/tools/debug-ui/src/RecentEpochsView.tsx @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { fetchEpochInfo, fetchFullStatus } from './api'; +import { parse } from 'date-fns'; +import { EpochInfoView, fetchEpochInfo, fetchFullStatus } from './api'; import { formatDurationInMillis } from './utils'; import './RecentEpochsView.scss'; @@ -33,12 +34,15 @@ export const RecentEpochsView = ({ addr }: RecentEpochsViewProps) => { + Epoch Height Epoch ID Start Height Protocol Version First Block Epoch Start Block Producers + Chunk Producers + Chunk Validators Chunk-only Producers @@ -60,9 +64,25 @@ export const RecentEpochsView = ({ addr }: RecentEpochsViewProps) => { } } else { firstBlockColumn = epochInfo.first_block[0]; + // The date object inside epochInfo.first_block is very particular. + // It looks like this: + // 2024,180,0,15,28,88423066,0,0,0 + // year,days,hours,minutes,seconds,nanoseconds,timezone offsets + // The solution below parses the first part of the date object, up the the seconds, in UTC. epochStartColumn = `${formatDurationInMillis( - Date.now() - Date.parse(epochInfo.first_block[1]) - )} ago`; + Date.now() - + parse( + epochInfo.first_block[1] + .toString() + .split(",") + .slice(0, 5) + .concat(["+00"]) + .join(","), + "yyyy,D,H,m,s,x", + new Date(), + { useAdditionalDayOfYearTokens: true } + ).getTime() + )} ago`; } let rowClassName = ''; let firstColumnText = ''; @@ -77,12 +97,15 @@ export const RecentEpochsView = ({ addr }: RecentEpochsViewProps) => { return ( {firstColumnText} + {epochInfo.epoch_height} {epochInfo.epoch_id} {epochInfo.height} {epochInfo.protocol_version} {firstBlockColumn} {epochStartColumn} {epochInfo.block_producers.length} + {getChunkProducersTotal(epochInfo)} + {getChunkValidatorsTotal(epochInfo)} {epochInfo.chunk_only_producers.length} ); @@ -91,3 +114,21 @@ export const RecentEpochsView = ({ addr }: RecentEpochsViewProps) => { ); }; + +function getChunkProducersTotal(epochInfo: EpochInfoView) { + return epochInfo.validator_info?.current_validators.reduce((acc, it) => { + if (it.num_expected_chunks > 0) { + acc = acc + 1; + } + return acc; + }, 0) ?? "N/A" +} + +function getChunkValidatorsTotal(epochInfo: EpochInfoView) { + return epochInfo.validator_info?.current_validators.reduce((acc, it) => { + if (it.num_expected_endorsements > 0) { + acc = acc + 1; + } + return acc; + }, 0) ?? "N/A"; +} \ No newline at end of file diff --git a/tools/debug-ui/src/SnapshotHostsView.tsx b/tools/debug-ui/src/SnapshotHostsView.tsx index c3cbc66c17a..72f75160884 100644 --- a/tools/debug-ui/src/SnapshotHostsView.tsx +++ b/tools/debug-ui/src/SnapshotHostsView.tsx @@ -19,7 +19,13 @@ export const SnapshotHostsView = ({ addr }: SnapshotHostsViewProps) => { return
{(error as Error).stack}
; } - const snapshot_hosts = snapshotHosts!.status_response.SnapshotHosts; + const snapshot_hosts = snapshotHosts!.status_response.SnapshotHosts.hosts; + snapshot_hosts.sort((a, b) => { + if (a.epoch_height != b.epoch_height) { + return b.epoch_height - a.epoch_height; + } + return a.peer_id.localeCompare(b.peer_id); + }); return (
@@ -31,7 +37,7 @@ export const SnapshotHostsView = ({ addr }: SnapshotHostsViewProps) => { Sync Hash - {snapshot_hosts.hosts.map((host) => { + {snapshot_hosts.map((host) => { return ( {host.peer_id} diff --git a/tools/debug-ui/src/api.tsx b/tools/debug-ui/src/api.tsx index c638949c714..b7549a5fde8 100644 --- a/tools/debug-ui/src/api.tsx +++ b/tools/debug-ui/src/api.tsx @@ -172,6 +172,7 @@ export interface DebugChunkStatus { } export interface EpochInfoView { + epoch_height: number; epoch_id: string; height: number; first_block: null | [string, string]; @@ -203,6 +204,8 @@ export interface CurrentEpochValidatorInfo { num_expected_blocks: number; num_produced_chunks: number; num_expected_chunks: number; + num_produced_endorsements: number; + num_expected_endorsements: number; } export interface NextEpochValidatorInfo { diff --git a/tools/debug-ui/src/entity_debug/keys.tsx b/tools/debug-ui/src/entity_debug/keys.tsx index f7bfb5afde3..e55a01613cd 100644 --- a/tools/debug-ui/src/entity_debug/keys.tsx +++ b/tools/debug-ui/src/entity_debug/keys.tsx @@ -48,7 +48,13 @@ export function parseEntityKey(keyType: EntityKeyType, input: string): EntityKey case 'receipt_id': case 'transaction_hash': case 'state_root': - if (input.length != 44) { + // Length of 32-byte array encoded in base58 is 43 or 44 characters, + // depending on whether we need additional character or not. + // + // Short explanation: 32 bytes are 256 bits, each base58 character + // encodes log2(58) ≈ 5.858 bits. Then length of 32-byte array + // encoded in base58 is ≈ 256 / 5.858 ≈ 43.7. + if (![43, 44].includes(input.length)) { return null; } return new StringEntityKey(keyType, input); diff --git a/tools/epoch-sync/src/cli.rs b/tools/epoch-sync/src/cli.rs index 969efd19e8a..5c19688219f 100644 --- a/tools/epoch-sync/src/cli.rs +++ b/tools/epoch-sync/src/cli.rs @@ -1,9 +1,6 @@ -#![cfg(feature = "new_epoch_sync")] - use anyhow::Context; use clap; use near_chain::{ChainStore, ChainStoreAccess, ChainUpdate, DoomslugThresholdMode}; -use near_epoch_manager::shard_tracker::{ShardTracker, TrackedConfig}; use near_epoch_manager::EpochManager; use near_primitives::block::BlockHeader; use near_primitives::borsh::BorshDeserialize; @@ -107,10 +104,6 @@ impl ValidateEpochSyncInfoCmd { let epoch_manager = EpochManager::new_arc_handle(storage.get_hot_store(), &config.genesis.config); - let shard_tracker = ShardTracker::new( - TrackedConfig::from_config(&config.client_config), - epoch_manager.clone(), - ); let runtime = NightshadeRuntime::from_config( home_dir, storage.get_hot_store(), @@ -121,7 +114,6 @@ impl ValidateEpochSyncInfoCmd { let chain_update = ChainUpdate::new( &mut chain_store, epoch_manager, - shard_tracker, runtime, DoomslugThresholdMode::TwoThirds, config.genesis.config.transaction_validity_period, @@ -195,7 +187,7 @@ impl ValidateEpochSyncInfoCmd { last_header.raw_timestamp(), ); - *last_block_info.epoch_id_mut() = last_header.epoch_id().clone(); + *last_block_info.epoch_id_mut() = *last_header.epoch_id(); *last_block_info.epoch_first_block_mut() = first_block_hash; let next_epoch_first_hash = hash_to_next_hash[last_header.hash()]; diff --git a/tools/flat-storage/src/commands.rs b/tools/flat-storage/src/commands.rs index f2855d32762..6e4cc70fd05 100644 --- a/tools/flat-storage/src/commands.rs +++ b/tools/flat-storage/src/commands.rs @@ -612,7 +612,7 @@ impl FlatStorageCommand { MoveFlatHeadMode::Forward { new_flat_head_height } => { let header = chain_store.get_block_header_by_height(new_flat_head_height)?; println!("Moving flat head for shard {shard_uid} forward to header: {header:?}"); - flat_storage.update_flat_head(header.hash(), true)?; + flat_storage.update_flat_head(header.hash())?; } MoveFlatHeadMode::Back { blocks } => { println!("Moving flat head for shard {shard_uid} back by {blocks} blocks"); diff --git a/tools/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index 9a007ae4818..4667745888d 100644 --- a/tools/fork-network/src/cli.rs +++ b/tools/fork-network/src/cli.rs @@ -273,7 +273,7 @@ impl ForkNetworkCommand { flat_storage_manager.create_flat_storage_for_shard(shard_uid).unwrap(); let flat_storage = flat_storage_manager.get_flat_storage_for_shard(shard_uid).unwrap(); - flat_storage.update_flat_head(&desired_block_hash, true).unwrap(); + flat_storage.update_flat_head(&desired_block_hash).unwrap(); let chunk_extra = chain.get_chunk_extra(&desired_block_hash, &shard_uid).unwrap(); let state_root = chunk_extra.state_root(); tracing::info!(?shard_id, ?epoch_id, ?state_root); @@ -332,7 +332,7 @@ impl ForkNetworkCommand { let runtime = NightshadeRuntime::from_config(home_dir, store.clone(), &near_config, epoch_manager) .context("could not create the transaction runtime")?; - runtime.get_tries().load_mem_tries_for_enabled_shards(&all_shard_uids).unwrap(); + runtime.get_tries().load_mem_tries_for_enabled_shards(&all_shard_uids, true).unwrap(); let make_storage_mutator: MakeSingleShardStorageMutatorFn = Arc::new(move |prev_state_root| { @@ -381,12 +381,8 @@ impl ForkNetworkCommand { let runtime_config_store = RuntimeConfigStore::new(None); let runtime_config = runtime_config_store.get_config(PROTOCOL_VERSION); - let storage_mutator = StorageMutator::new( - epoch_manager.clone(), - &runtime, - epoch_id.clone(), - prev_state_roots, - )?; + let storage_mutator = + StorageMutator::new(epoch_manager.clone(), &runtime, epoch_id, prev_state_roots)?; let (new_state_roots, new_validator_accounts) = self.add_validator_accounts(validators, runtime_config, home_dir, storage_mutator)?; @@ -791,6 +787,8 @@ impl ForkNetworkCommand { avg_hidden_validator_seats_per_shard: epoch_config.avg_hidden_validator_seats_per_shard, block_producer_kickout_threshold: 0, chunk_producer_kickout_threshold: 0, + chunk_validator_only_kickout_threshold: 0, + target_validator_mandates_per_shard: epoch_config.target_validator_mandates_per_shard, max_kickout_stake_perc: 0, online_min_threshold: epoch_config.online_min_threshold, online_max_threshold: epoch_config.online_max_threshold, diff --git a/tools/mock-node/Cargo.toml b/tools/mock-node/Cargo.toml index ef4501f6d59..31d216a96de 100644 --- a/tools/mock-node/Cargo.toml +++ b/tools/mock-node/Cargo.toml @@ -27,7 +27,7 @@ tokio.workspace = true tracing.workspace = true near-actix-test-utils.workspace = true -near-async.workspace = true +near-time.workspace = true near-chain.workspace = true near-chain-configs.workspace = true near-client.workspace = true diff --git a/tools/mock-node/src/lib.rs b/tools/mock-node/src/lib.rs index 0fbe713a66b..c6b026caf3f 100644 --- a/tools/mock-node/src/lib.rs +++ b/tools/mock-node/src/lib.rs @@ -2,7 +2,6 @@ //! components of the mock network. use anyhow::{anyhow, Context as AnyhowContext}; -use near_async::time; use near_chain::{Block, Chain, ChainStoreAccess, Error}; use near_client::sync::header::MAX_BLOCK_HEADERS; use near_crypto::SecretKey; @@ -305,7 +304,7 @@ impl MockPeer { network_start_height, (0..num_shards).collect(), archival, - 30 * time::Duration::SECOND, + 30 * near_time::Duration::SECOND, ) .await?; let incoming_requests = diff --git a/tools/mock-node/src/main.rs b/tools/mock-node/src/main.rs index f53300fffbe..1fdcaf5ec0d 100644 --- a/tools/mock-node/src/main.rs +++ b/tools/mock-node/src/main.rs @@ -8,7 +8,7 @@ use anyhow::Context; use mock_node::setup::{setup_mock_node, MockNode}; use mock_node::MockNetworkConfig; use near_actix_test_utils::run_actix; -use near_chain_configs::GenesisValidationMode; +use near_chain_configs::{GenesisValidationMode, MutableConfigValue}; use near_crypto::{InMemorySigner, KeyType}; use near_jsonrpc_client::JsonRpcClient; use near_network::tcp; @@ -107,7 +107,7 @@ fn main() -> anyhow::Result<()> { let home_dir = Path::new(&args.chain_history_home_dir); let mut near_config = nearcore::config::load_config(home_dir, GenesisValidationMode::Full) .context("Error loading config")?; - near_config.validator_signer = None; + near_config.validator_signer = MutableConfigValue::new(None, "validator_signer"); near_config.client_config.min_num_peers = 1; let signer = InMemorySigner::from_random("mock_node".parse().unwrap(), KeyType::ED25519); near_config.network_config.node_key = signer.secret_key; diff --git a/tools/mock-node/src/setup.rs b/tools/mock-node/src/setup.rs index 4d7d972befa..99dba008def 100644 --- a/tools/mock-node/src/setup.rs +++ b/tools/mock-node/src/setup.rs @@ -2,7 +2,6 @@ use crate::{MockNetworkConfig, MockPeer}; use anyhow::Context; -use near_async::time::Clock; use near_chain::types::RuntimeAdapter; use near_chain::ChainStoreUpdate; use near_chain::{Chain, ChainGenesis, ChainStore, ChainStoreAccess, DoomslugThresholdMode}; @@ -17,6 +16,7 @@ use near_primitives::state_part::PartId; use near_primitives::state_sync::get_num_state_parts; use near_primitives::types::{BlockHeight, NumShards, ShardId}; use near_store::test_utils::create_test_store; +use near_time::Clock; use nearcore::{NearConfig, NightshadeRuntime, NightshadeRuntimeExt}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::cmp::min; @@ -351,7 +351,7 @@ mod tests { gen_account_from_alphabet(&mut rng, b"abcdefghijklmn"), 5 * NEAR_BASE, signer0.public_key.clone(), - &signer0, + &signer0.into(), block.header.hash, ); spawn_interruptible( diff --git a/tools/ping/Cargo.toml b/tools/ping/Cargo.toml index b752b290368..ab1216215e5 100644 --- a/tools/ping/Cargo.toml +++ b/tools/ping/Cargo.toml @@ -21,7 +21,7 @@ prometheus.workspace = true tokio.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-jsonrpc.workspace = true near-network.workspace = true near-o11y.workspace = true @@ -29,7 +29,6 @@ near-primitives.workspace = true [features] nightly = [ - "near-async/nightly", "near-jsonrpc/nightly", "near-network/nightly", "near-o11y/nightly", @@ -37,7 +36,6 @@ nightly = [ "nightly_protocol", ] nightly_protocol = [ - "near-async/nightly_protocol", "near-jsonrpc/nightly_protocol", "near-network/nightly_protocol", "near-o11y/nightly_protocol", diff --git a/tools/ping/src/csv.rs b/tools/ping/src/csv.rs index d7d9115cc5b..47f0610bde9 100644 --- a/tools/ping/src/csv.rs +++ b/tools/ping/src/csv.rs @@ -1,4 +1,3 @@ -use near_async::time; use near_primitives::network::PeerId; use near_primitives::types::AccountId; use std::fs::{File, OpenOptions}; @@ -74,7 +73,7 @@ impl LatenciesCsv { &mut self, peer_id: &PeerId, account_id: Option<&AccountId>, - latency: time::Duration, + latency: near_time::Duration, ) -> io::Result<()> { let id = account_id.map_or_else(|| format!("{}", peer_id), |a| format!("{}", a)); write!( diff --git a/tools/ping/src/lib.rs b/tools/ping/src/lib.rs index 6d89d2e24ac..5701e0f4549 100644 --- a/tools/ping/src/lib.rs +++ b/tools/ping/src/lib.rs @@ -2,7 +2,6 @@ use actix_web::cookie::time::ext::InstantExt as _; use actix_web::{web, App, HttpServer}; use anyhow::Context; pub use cli::PingCommand; -use near_async::time; use near_network::raw::{ConnectError, Connection, DirectMessage, Message, RoutedMessage}; use near_network::types::HandshakeFailureReason; use near_primitives::hash::CryptoHash; @@ -26,16 +25,16 @@ struct PingStats { pongs_received: usize, // TODO: these latency stats could be separated into time to first byte // + time to last byte, etc. - min_latency: time::Duration, - max_latency: time::Duration, - average_latency: time::Duration, + min_latency: near_time::Duration, + max_latency: near_time::Duration, + average_latency: near_time::Duration, } impl PingStats { - fn pong_received(&mut self, latency: time::Duration) { + fn pong_received(&mut self, latency: near_time::Duration) { self.pongs_received += 1; - if self.min_latency == time::Duration::ZERO || self.min_latency > latency { + if self.min_latency == near_time::Duration::ZERO || self.min_latency > latency { self.min_latency = latency; } if self.max_latency < latency { @@ -51,7 +50,7 @@ type Nonce = u64; #[derive(Debug, Eq, PartialEq)] struct PingTarget { peer_id: PeerId, - last_pinged: Option, + last_pinged: Option, } impl PartialOrd for PingTarget { @@ -81,7 +80,7 @@ impl Ord for PingTarget { struct PingTimeout { peer_id: PeerId, nonce: u64, - timeout: time::Instant, + timeout: near_time::Instant, } impl PartialOrd for PingTimeout { @@ -103,18 +102,18 @@ fn peer_str(peer_id: &PeerId, account_id: Option<&AccountId>) -> String { } const MAX_PINGS_IN_FLIGHT: usize = 10; -const PING_TIMEOUT: time::Duration = time::Duration::seconds(100); +const PING_TIMEOUT: near_time::Duration = near_time::Duration::seconds(100); #[derive(Debug)] struct PingState { stats: PingStats, - last_pinged: Option, + last_pinged: Option, account_id: Option, } struct PingTimes { - sent_at: time::Instant, - timeout: time::Instant, + sent_at: near_time::Instant, + timeout: near_time::Instant, } struct AppInfo { @@ -148,7 +147,7 @@ impl AppInfo { } fn ping_sent(&mut self, peer_id: &PeerId, nonce: u64, chain_id: &str) { - let timestamp = time::Instant::now(); + let timestamp = near_time::Instant::now(); let timeout = timestamp + PING_TIMEOUT; let account_id = self.peer_id_to_account_id(&peer_id); @@ -202,8 +201,8 @@ impl AppInfo { &mut self, peer_id: &PeerId, nonce: u64, - received_at: time::Instant, - ) -> Option<(time::Duration, Option<&AccountId>)> { + received_at: near_time::Instant, + ) -> Option<(near_time::Duration, Option<&AccountId>)> { match self.stats.get_mut(peer_id) { Some(state) => { let pending_pings = self @@ -316,7 +315,7 @@ impl AppInfo { fn handle_message( app_info: &mut AppInfo, msg: Message, - received_at: time::Instant, + received_at: near_time::Instant, latencies_csv: Option<&mut crate::csv::LatenciesCsv>, ) -> anyhow::Result<()> { match msg { @@ -391,7 +390,7 @@ async fn ping_via_node( app_info.add_peer(peer_id.clone(), None); - let clock = time::Clock::real(); + let clock = near_time::Clock::real(); let mut peer = match Connection::connect( &clock, @@ -402,7 +401,7 @@ async fn ping_via_node( genesis_hash, head_height, vec![0], - time::Duration::seconds(recv_timeout_seconds.into())).await { + near_time::Duration::seconds(recv_timeout_seconds.into())).await { Ok(p) => p, Err(ConnectError::HandshakeFailure(reason)) => { match reason { diff --git a/tools/restaked/src/main.rs b/tools/restaked/src/main.rs index 6a5de2b11ee..0f18636b531 100644 --- a/tools/restaked/src/main.rs +++ b/tools/restaked/src/main.rs @@ -86,8 +86,9 @@ fn main() { signer.account_id, key_file.account_id, "Only can stake for the same account as given signer key" ); + let signer = Arc::new(signer.into()); - let user = RpcUser::new(rpc_url, account_id.clone(), Arc::new(signer)); + let user = RpcUser::new(rpc_url, account_id.clone(), signer); loop { let validators = user.validators(None).unwrap(); // Check: diff --git a/tools/speedy_sync/Cargo.toml b/tools/speedy_sync/Cargo.toml index b74b3feacf3..864dc208844 100644 --- a/tools/speedy_sync/Cargo.toml +++ b/tools/speedy_sync/Cargo.toml @@ -12,7 +12,7 @@ publish = false workspace = true [dependencies] -near-async.workspace = true +near-time.workspace = true near-store.workspace = true near-chain-primitives.workspace = true near-primitives.workspace = true diff --git a/tools/speedy_sync/src/main.rs b/tools/speedy_sync/src/main.rs index 7a872637fa9..02ad33e590e 100644 --- a/tools/speedy_sync/src/main.rs +++ b/tools/speedy_sync/src/main.rs @@ -1,5 +1,4 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use near_async::time::Clock; use near_chain::rayon_spawner::RayonAsyncComputationSpawner; use near_chain::types::{ChainConfig, Tip}; use near_chain::{Chain, ChainGenesis, DoomslugThresholdMode}; @@ -18,6 +17,7 @@ use near_primitives::types::EpochId; use near_primitives::utils::index_to_bytes; use near_store::HEADER_HEAD_KEY; use near_store::{DBCol, Mode, NodeStorage, Store, StoreUpdate}; +use near_time::Clock; use nearcore::{NightshadeRuntime, NightshadeRuntimeExt}; use std::fs; use std::path::Path; @@ -255,7 +255,7 @@ fn load_snapshot(load_cmd: LoadCmd) { }, None, Arc::new(RayonAsyncComputationSpawner), - None, + MutableConfigValue::new(None, "validator_signer"), ) .unwrap(); diff --git a/tools/state-parts-dump-check/src/cli.rs b/tools/state-parts-dump-check/src/cli.rs index ea0e3652e32..8ae2d34c534 100644 --- a/tools/state-parts-dump-check/src/cli.rs +++ b/tools/state-parts-dump-check/src/cli.rs @@ -125,7 +125,7 @@ impl SingleCheckCommand { let _check_result = run_single_check_with_3_retries( None, chain_id, - self.epoch_id.clone(), + self.epoch_id, self.epoch_height, self.shard_id, self.state_root, @@ -440,7 +440,6 @@ async fn run_single_check_with_3_retries( let s3_bucket = s3_bucket.clone(); let s3_region = s3_region.clone(); let gcs_bucket = gcs_bucket.clone(); - let epoch_id = epoch_id.clone(); res = run_single_check( status.clone(), chain_id, @@ -554,8 +553,8 @@ async fn check_parts( let mut handles = vec![]; for part_id in 0..num_parts { let chain_id = chain_id.clone(); - let epoch_id = epoch_id.clone(); let external = external.clone(); + let epoch_id = *epoch_id; let handle = tokio::spawn(async move { process_part_with_3_retries( part_id, @@ -613,10 +612,9 @@ async fn check_headers( let start = Instant::now(); let chain_id = chain_id.clone(); - let epoch_id = epoch_id.clone(); let external = external.clone(); - process_header_with_3_retries(chain_id, epoch_id, epoch_height, shard_id, external).await?; + process_header_with_3_retries(chain_id, *epoch_id, epoch_height, shard_id, external).await?; let duration = start.elapsed(); tracing::info!("Time elapsed in downloading and validating the header is: {:?}", duration); @@ -706,7 +704,6 @@ async fn process_part_with_3_retries( let mut res; loop { let chain_id = chain_id.clone(); - let epoch_id = epoch_id.clone(); let external = external.clone(); // timeout is needed to deal with edge cases where process_part awaits forever, i.e. the get_file().await somehow waits forever // this is set to a long duration because the timer for each task, i.e. process_part, starts when the task is started, i.e. tokio::spawn is called, @@ -762,7 +759,6 @@ async fn process_header_with_3_retries( let mut res: Result, tokio::time::error::Elapsed>; loop { let chain_id = chain_id.clone(); - let epoch_id = epoch_id.clone(); let external = external.clone(); let timeout_duration = tokio::time::Duration::from_secs(60); res = timeout( diff --git a/tools/state-parts/Cargo.toml b/tools/state-parts/Cargo.toml index 3265dd758be..48141677696 100644 --- a/tools/state-parts/Cargo.toml +++ b/tools/state-parts/Cargo.toml @@ -21,7 +21,7 @@ time.workspace = true tokio.workspace = true tracing.workspace = true -near-async.workspace = true +near-time.workspace = true near-jsonrpc.workspace = true near-network.workspace = true near-o11y.workspace = true @@ -30,7 +30,6 @@ near-primitives.workspace = true [features] nightly = [ - "near-async/nightly", "near-jsonrpc/nightly", "near-network/nightly", "near-o11y/nightly", @@ -39,7 +38,6 @@ nightly = [ "nightly_protocol", ] nightly_protocol = [ - "near-async/nightly_protocol", "near-jsonrpc/nightly_protocol", "near-network/nightly_protocol", "near-o11y/nightly_protocol", diff --git a/tools/state-parts/src/lib.rs b/tools/state-parts/src/lib.rs index db9f51b591e..616612432eb 100644 --- a/tools/state-parts/src/lib.rs +++ b/tools/state-parts/src/lib.rs @@ -1,12 +1,12 @@ use ::time::ext::InstantExt as _; use anyhow::Context; -use near_async::time::{self, Instant}; use near_network::raw::{ConnectError, Connection, DirectMessage, Message}; use near_network::types::HandshakeFailureReason; use near_primitives::hash::CryptoHash; use near_primitives::network::PeerId; use near_primitives::types::{BlockHeight, ShardId}; use near_primitives::version::ProtocolVersion; +use near_time::Instant; use sha2::Digest; use sha2::Sha256; use std::collections::HashMap; @@ -16,7 +16,7 @@ use std::net::SocketAddr; pub mod cli; struct AppInfo { - pub requests_sent: HashMap, + pub requests_sent: HashMap, } impl AppInfo { @@ -28,7 +28,7 @@ impl AppInfo { fn handle_message( app_info: &mut AppInfo, msg: &Message, - received_at: time::Instant, + received_at: near_time::Instant, ) -> anyhow::Result<()> { match &msg { Message::Direct(DirectMessage::VersionedStateResponse(response)) => { @@ -90,7 +90,7 @@ async fn state_parts_from_node( assert!(start_part_id < num_parts && num_parts > 0, "{}/{}", start_part_id, num_parts); let mut app_info = AppInfo::new(); - let clock = time::Clock::real(); + let clock = near_time::Clock::real(); let mut peer = match Connection::connect( &clock, @@ -101,7 +101,7 @@ async fn state_parts_from_node( genesis_hash, head_height, vec![0], - time::Duration::seconds(recv_timeout_seconds.into())).await { + near_time::Duration::seconds(recv_timeout_seconds.into())).await { Ok(p) => p, Err(ConnectError::HandshakeFailure(reason)) => { match reason { @@ -137,7 +137,7 @@ async fn state_parts_from_node( let msg = DirectMessage::StateRequestPart(shard_id, block_hash, part_id); tracing::info!(target: "state-parts", ?target, shard_id, ?block_hash, part_id, ttl, "Sending a request"); result = peer.send_message(msg).await.with_context(|| format!("Failed sending State Part Request to {:?}", target)); - app_info.requests_sent.insert(part_id, time::Instant::now()); + app_info.requests_sent.insert(part_id, near_time::Instant::now()); tracing::info!(target: "state-parts", ?result); if result.is_err() { break; diff --git a/tools/state-viewer/Cargo.toml b/tools/state-viewer/Cargo.toml index 20e662547aa..7de2eea99e6 100644 --- a/tools/state-viewer/Cargo.toml +++ b/tools/state-viewer/Cargo.toml @@ -35,7 +35,7 @@ thiserror.workspace = true tracing.workspace = true yansi.workspace = true -near-async.workspace = true +near-time.workspace = true near-chain-configs.workspace = true near-chain.workspace = true near-client.workspace = true @@ -56,13 +56,17 @@ near-test-contracts.workspace = true testlib.workspace = true [features] -sandbox = ["node-runtime/sandbox", "near-chain/sandbox", "near-client/sandbox"] +sandbox = [ + "near-chain/sandbox", + "near-client/sandbox", + "near-o11y/sandbox", + "node-runtime/sandbox", +] protocol_feature_nonrefundable_transfer_nep491 = [ "near-primitives/protocol_feature_nonrefundable_transfer_nep491", ] nightly = [ - "near-async/nightly", "near-chain-configs/nightly", "near-chain/nightly", "near-client/nightly", @@ -79,7 +83,6 @@ nightly = [ "testlib/nightly", ] nightly_protocol = [ - "near-async/nightly_protocol", "near-chain-configs/nightly_protocol", "near-chain/nightly_protocol", "near-client/nightly_protocol", diff --git a/tools/state-viewer/src/apply_chain_range.rs b/tools/state-viewer/src/apply_chain_range.rs index 54b5e8bace2..5639251919a 100644 --- a/tools/state-viewer/src/apply_chain_range.rs +++ b/tools/state-viewer/src/apply_chain_range.rs @@ -246,7 +246,7 @@ fn apply_block_from_range( ApplyChunkBlockContext::from_header( block.header(), prev_block.header().next_gas_price(), - prev_block.shards_congestion_info(), + prev_block.block_congestion_info(), ), &receipts, chunk.transactions(), @@ -272,7 +272,7 @@ fn apply_block_from_range( ApplyChunkBlockContext::from_header( block.header(), block.header().next_gas_price(), - block.shards_congestion_info(), + block.block_congestion_info(), ), &[], &[], @@ -355,7 +355,7 @@ fn apply_block_from_range( let flat_storage = flat_storage_manager.get_flat_storage_for_shard(shard_uid).unwrap(); let store_update = flat_storage.add_delta(delta).unwrap(); store_update.commit().unwrap(); - flat_storage.update_flat_head(&block_hash, true).unwrap(); + flat_storage.update_flat_head(&block_hash).unwrap(); // Apply trie changes to trie node caches. let mut fake_store_update = store.store_update(); @@ -607,13 +607,14 @@ mod test { let epoch_length = 4; let (store, genesis, mut env) = setup(epoch_length); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -649,13 +650,14 @@ mod test { let epoch_length = 4; let (store, genesis, mut env) = setup(epoch_length); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); diff --git a/tools/state-viewer/src/apply_chunk.rs b/tools/state-viewer/src/apply_chunk.rs index 9f12d4c66fb..c0ffb7468ee 100644 --- a/tools/state-viewer/src/apply_chunk.rs +++ b/tools/state-viewer/src/apply_chunk.rs @@ -153,7 +153,7 @@ pub(crate) fn apply_chunk( ), gas_price, random_seed: hash("random seed".as_ref()), - congestion_info: prev_block.shards_congestion_info(), + congestion_info: prev_block.block_congestion_info(), }, &receipts, transactions, @@ -482,7 +482,6 @@ pub(crate) fn apply_receipt( #[cfg(test)] mod test { - use near_async::time::Clock; use near_chain::{ChainStore, ChainStoreAccess, Provenance}; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; @@ -495,6 +494,7 @@ mod test { use near_primitives::utils::get_num_seats_per_shard; use near_store::genesis::initialize_genesis_state; use near_store::test_utils::create_test_store; + use near_time::Clock; use nearcore::NightshadeRuntime; use rand::rngs::StdRng; use rand::SeedableRng; @@ -508,7 +508,7 @@ mod test { height, from.parse().unwrap(), to.parse().unwrap(), - signer, + &signer.clone().into(), 100, hash, ); @@ -564,7 +564,7 @@ mod test { let hash = *block.hash(); let chunk_hashes = block.chunks().iter().map(|c| c.chunk_hash()).collect::>(); - let epoch_id = block.header().epoch_id().clone(); + let epoch_id = *block.header().epoch_id(); env.process_block(0, block, Provenance::PRODUCED); @@ -652,7 +652,7 @@ mod test { let hash = *block.hash(); let prev_hash = *block.header().prev_hash(); let chunk_hashes = block.chunks().iter().map(|c| c.chunk_hash()).collect::>(); - let epoch_id = block.header().epoch_id().clone(); + let epoch_id = *block.header().epoch_id(); env.process_block(0, block, Provenance::PRODUCED); diff --git a/tools/state-viewer/src/cli.rs b/tools/state-viewer/src/cli.rs index 78438b8dd72..9a6e71924b7 100644 --- a/tools/state-viewer/src/cli.rs +++ b/tools/state-viewer/src/cli.rs @@ -1,15 +1,17 @@ use crate::commands::*; +use crate::congestion_control::CongestionControlCmd; use crate::contract_accounts::ContractAccountFilter; use crate::rocksdb_stats::get_rocksdb_stats; use crate::trie_iteration_benchmark::TrieIterationBenchmarkCmd; -use crate::latest_witnesses::LatestWitnessesCmd; +use crate::latest_witnesses::StateWitnessCmd; use near_chain_configs::{GenesisChangeConfig, GenesisValidationMode}; use near_primitives::account::id::AccountId; use near_primitives::hash::CryptoHash; use near_primitives::sharding::ChunkHash; use near_primitives::trie_key::col; use near_primitives::types::{BlockHeight, ShardId}; +use near_primitives_core::types::EpochHeight; use near_store::{Mode, NodeStorage, Store, Temperature}; use nearcore::{load_config, NearConfig}; use std::path::{Path, PathBuf}; @@ -65,6 +67,9 @@ pub enum StateViewerSubCommand { /// Print `EpochInfo` of an epoch given by `--epoch_id` or by `--epoch_height`. #[clap(alias = "epoch_info")] EpochInfo(EpochInfoCmd), + /// Regenerates epoch info based on previous epoch. + #[clap(alias = "epoch_analysis")] + EpochAnalysis(EpochAnalysisCmd), /// Looks up a certain partial chunk. #[clap(alias = "partial_chunks")] PartialChunks(PartialChunksCmd), @@ -95,9 +100,22 @@ pub enum StateViewerSubCommand { /// View trie structure. #[clap(alias = "view_trie")] ViewTrie(ViewTrieCmd), - /// Print observed ChunkStateWitnesses at the given block height (and shard id). - /// Observed witnesses are only saved when `save_latest_witnesses` is set to true in config.json. - LatestWitnesses(LatestWitnessesCmd), + /// Tools for manually validating state witnesses. + /// + /// First, dump some of the latest stored state witnesses to a directory + /// using the `dump` command. Supports selecting by given height, shard + /// and epoch id, or pretty-printing on screen. + /// Note that witnesses are only stored when `save_latest_witnesses` is + /// set to true in config.json. + /// + /// Second, validate a particular state witness from a file using the + /// `validate` command. + #[clap(subcommand)] + StateWitness(StateWitnessCmd), + + /// Tools for printing and recalculating the congestion information. + #[clap(subcommand)] + CongestionControl(CongestionControlCmd), } impl StateViewerSubCommand { @@ -143,6 +161,7 @@ impl StateViewerSubCommand { StateViewerSubCommand::DumpStateRedis(cmd) => cmd.run(home_dir, near_config, store), StateViewerSubCommand::DumpTx(cmd) => cmd.run(home_dir, near_config, store), StateViewerSubCommand::EpochInfo(cmd) => cmd.run(near_config, store), + StateViewerSubCommand::EpochAnalysis(cmd) => cmd.run(near_config, store), StateViewerSubCommand::PartialChunks(cmd) => cmd.run(near_config, store), StateViewerSubCommand::Receipts(cmd) => cmd.run(near_config, store), StateViewerSubCommand::Replay(cmd) => cmd.run(near_config, store), @@ -155,7 +174,8 @@ impl StateViewerSubCommand { StateViewerSubCommand::ViewChain(cmd) => cmd.run(near_config, store), StateViewerSubCommand::ViewTrie(cmd) => cmd.run(store), StateViewerSubCommand::TrieIterationBenchmark(cmd) => cmd.run(near_config, store), - StateViewerSubCommand::LatestWitnesses(cmd) => cmd.run(near_config, store), + StateViewerSubCommand::StateWitness(cmd) => cmd.run(home_dir, near_config, store), + StateViewerSubCommand::CongestionControl(cmd) => cmd.run(home_dir, near_config, store), } } } @@ -486,6 +506,36 @@ impl EpochInfoCmd { } } +#[derive(clap::Args)] +pub struct EpochAnalysisCmd { + /// Start height of the epochs to analyse. + #[clap(long)] + start_height: EpochHeight, + /// Epoch analysis mode. + #[clap(subcommand)] + mode: EpochAnalysisMode, +} + +#[derive(clap::Subcommand)] +pub enum EpochAnalysisMode { + /// Regenerate epoch infos based on previous epoch, assert that epoch info + /// generation is replayable. + /// TODO (#11476): doesn't work when start epoch height is <= 1053 because + /// it will try to generate epoch with height 1055 and fail. + CheckConsistency, + /// Generate epoch infos as if latest `PROTOCOL_VERSION` was used since the + /// start epoch height. + /// TODO (#11477): doesn't work for start epoch height <= 544 because of + /// `EpochOutOfBounds` error. + Backtest, +} + +impl EpochAnalysisCmd { + pub fn run(self, near_config: NearConfig, store: Store) { + print_epoch_analysis(self.start_height, self.mode, near_config, store); + } +} + #[derive(clap::Parser)] pub struct PartialChunksCmd { #[clap(long)] diff --git a/tools/state-viewer/src/commands.rs b/tools/state-viewer/src/commands.rs index 957e3e978d4..f81b40c9f86 100644 --- a/tools/state-viewer/src/commands.rs +++ b/tools/state-viewer/src/commands.rs @@ -1,11 +1,13 @@ use crate::apply_chain_range::apply_chain_range; -use crate::cli::ApplyRangeMode; +use crate::cli::{ApplyRangeMode, EpochAnalysisMode}; use crate::contract_accounts::ContractAccount; use crate::contract_accounts::ContractAccountFilter; use crate::contract_accounts::Summary; +use crate::epoch_info::iterate_and_filter; use crate::state_dump::state_dump; use crate::state_dump::state_dump_redis; use crate::tx_dump::dump_tx_from_block; +use crate::util::{load_trie, load_trie_stop_at_height, LoadTrieMode}; use crate::{apply_chunk, epoch_info}; use anyhow::Context; use bytesize::ByteSize; @@ -24,7 +26,8 @@ use near_epoch_manager::EpochManagerHandle; use near_epoch_manager::{EpochManager, EpochManagerAdapter}; use near_primitives::account::id::AccountId; use near_primitives::apply::ApplyChunkReason; -use near_primitives::block::{Block, BlockHeader}; +use near_primitives::block::Block; +use near_primitives::epoch_manager::epoch_info::EpochInfo; use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardLayout; use near_primitives::shard_layout::ShardUId; @@ -34,9 +37,9 @@ use near_primitives::state_record::state_record_to_account_id; use near_primitives::state_record::StateRecord; use near_primitives::trie_key::col::COLUMNS_WITH_ACCOUNT_ID_IN_KEY; use near_primitives::trie_key::TrieKey; -use near_primitives::types::{chunk_extra::ChunkExtra, BlockHeight, ShardId, StateRoot}; +use near_primitives::types::{chunk_extra::ChunkExtra, BlockHeight, EpochId, ShardId}; use near_primitives::version::PROTOCOL_VERSION; -use near_primitives_core::types::Gas; +use near_primitives_core::types::{Balance, EpochHeight, Gas}; use near_store::flat::FlatStorageChunkView; use near_store::flat::FlatStorageManager; use near_store::test_utils::create_test_store; @@ -46,12 +49,11 @@ use nearcore::NightshadeRuntimeExt; use nearcore::{NearConfig, NightshadeRuntime}; use node_runtime::adapter::ViewRuntimeAdapter; use serde_json::json; -use std::collections::BinaryHeap; use std::collections::HashMap; +use std::collections::{BTreeMap, BinaryHeap}; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::sync::Arc; use yansi::Color::Red; @@ -106,7 +108,7 @@ pub(crate) fn apply_block( ApplyChunkBlockContext::from_header( block.header(), prev_block.header().next_gas_price(), - prev_block.shards_congestion_info(), + prev_block.block_congestion_info(), ), &receipts, chunk.transactions(), @@ -131,7 +133,7 @@ pub(crate) fn apply_block( ApplyChunkBlockContext::from_header( block.header(), block.header().next_gas_price(), - prev_block.shards_congestion_info(), + prev_block.block_congestion_info(), ), &[], &[], @@ -516,7 +518,7 @@ fn chunk_extras_equal(l: &ChunkExtra, r: &ChunkExtra) -> bool { // edit with v3: To avoid too many explicit combinations, use wildcards for // versions >= 3. The compiler will still notice the missing `(v1, new_v)` // combinations. - match (l, r) { + match (&l, &r) { (ChunkExtra::V1(l), ChunkExtra::V1(r)) => return l == r, (ChunkExtra::V2(l), ChunkExtra::V2(r)) => return l == r, (ChunkExtra::V3(l), ChunkExtra::V3(r)) => return l == r, @@ -613,7 +615,7 @@ pub(crate) fn print_chain( chain_store.get_block_header(header.prev_hash()).unwrap().clone(); if let Ok(epoch_id) = epoch_manager.get_epoch_id_from_prev_block(header.prev_hash()) { - cur_epoch_id = Some(epoch_id.clone()); + cur_epoch_id = Some(epoch_id); match epoch_manager.is_next_block_epoch_start(header.prev_hash()) { Ok(true) => { println!("{:?}", account_id_to_blocks); @@ -896,12 +898,205 @@ pub(crate) fn print_epoch_info( ); } +pub(crate) fn print_epoch_analysis( + epoch_height: EpochHeight, + mode: EpochAnalysisMode, + near_config: NearConfig, + store: Store, +) { + let epoch_manager = + EpochManager::new_from_genesis_config(store.clone(), &near_config.genesis.config) + .expect("Failed to start Epoch Manager"); + + let epoch_ids = iterate_and_filter(store, |_| true); + let epoch_infos: HashMap> = HashMap::from_iter( + epoch_ids + .into_iter() + .map(|epoch_id| (epoch_id, epoch_manager.get_epoch_info(&epoch_id).unwrap())), + ); + let epoch_heights_to_ids = BTreeMap::from_iter( + epoch_infos.iter().map(|(epoch_id, epoch_info)| (epoch_info.epoch_height(), *epoch_id)), + ); + let min_epoch_height = epoch_height; + let max_stored_epoch_height = *epoch_heights_to_ids.keys().max().unwrap(); + // We can analyze only epochs without last two because these are not + // finalized yet, so we don't have next next epoch info stored for them. + let max_epoch_height = max_stored_epoch_height.saturating_sub(2); + + let epoch_heights_to_infos = + BTreeMap::from_iter(epoch_infos.values().map(|e| (e.epoch_height(), e))); + let epoch_heights_to_validator_infos = + BTreeMap::from_iter(epoch_heights_to_ids.iter().filter_map(|(&epoch_height, epoch_id)| { + // Filter out too small epoch heights due to #11477. + if epoch_height < min_epoch_height { + return None; + } + // Filter out too big epoch heights because they may not be + // finalized yet. + if epoch_height > max_epoch_height { + return None; + } + Some((epoch_height, epoch_manager.get_epoch_validator_info(epoch_id).unwrap())) + })); + + // The parameters below are required for the next next epoch generation. + // For `CheckConsistency` mode, they will be overridden in the loop. + // For `Backtest` mode, they will stay the same and override information + // stored on disk. + let mut next_epoch_info = + epoch_heights_to_infos.get(&min_epoch_height.saturating_add(1)).unwrap().as_ref().clone(); + let mut next_next_epoch_config = + epoch_manager.get_config_for_protocol_version(PROTOCOL_VERSION).unwrap(); + let mut has_same_shard_layout; + let mut epoch_protocol_version; + let mut next_next_protocol_version; + + // Print data header. + match mode { + EpochAnalysisMode::CheckConsistency => { + println!("HEIGHT | VERSION | STATE SYNCS"); + } + EpochAnalysisMode::Backtest => { + println!("epoch_height,original_protocol_version,state_syncs,min_validator_num,diff_validator_num,min_stake,diff_stake,rel_diff_stake"); + // Start from empty assignment for correct number of shards. + *next_epoch_info.chunk_producers_settlement_mut() = + vec![vec![]; next_next_epoch_config.shard_layout.shard_ids().collect_vec().len()]; + // This in fact sets maximal number of all validators to 100 for + // `StatelessValidationV0`. + // Needed because otherwise generation fails at epoch 1327 with + // assertion `stake < threshold` in + // chain/epoch-manager/src/validator_selection.rs:227:13. + // Probably has something to do with extreme case where all + // proposals are selected. + next_next_epoch_config.validator_selection_config.num_chunk_validator_seats = 100; + } + } + + // Each iteration will generate and print *next next* epoch info based on + // *next* epoch info for `epoch_height`. This follows epoch generation + // logic in the protocol. + for (epoch_height, epoch_info) in + epoch_heights_to_infos.range(min_epoch_height..=max_epoch_height) + { + let next_epoch_height = epoch_height.saturating_add(1); + let next_next_epoch_height = epoch_height.saturating_add(2); + let next_epoch_id = epoch_heights_to_ids.get(&next_epoch_height).unwrap(); + let next_next_epoch_id = epoch_heights_to_ids.get(&next_next_epoch_height).unwrap(); + let epoch_summary = epoch_heights_to_validator_infos.get(epoch_height).unwrap(); + let next_epoch_config = epoch_manager.get_epoch_config(next_epoch_id).unwrap(); + let original_next_next_protocol_version = epoch_summary.next_next_epoch_version; + + match mode { + EpochAnalysisMode::CheckConsistency => { + // Retrieve remaining parameters from the stored information + // about epochs. + next_epoch_info = + epoch_heights_to_infos.get(&next_epoch_height).unwrap().as_ref().clone(); + next_next_epoch_config = + epoch_manager.get_epoch_config(next_next_epoch_id).unwrap(); + has_same_shard_layout = + next_epoch_config.shard_layout == next_next_epoch_config.shard_layout; + epoch_protocol_version = epoch_info.protocol_version(); + next_next_protocol_version = original_next_next_protocol_version; + } + EpochAnalysisMode::Backtest => { + has_same_shard_layout = true; + epoch_protocol_version = PROTOCOL_VERSION; + next_next_protocol_version = PROTOCOL_VERSION; + } + }; + + // Use "future" information to generate next next epoch which is stored + // in DB already. Epoch info generation doesn't modify it. + let stored_next_next_epoch_info = + epoch_heights_to_infos.get(&next_next_epoch_height).unwrap(); + let rng_seed = stored_next_next_epoch_info.rng_seed(); + + let next_next_epoch_info = near_epoch_manager::proposals_to_epoch_info( + &next_next_epoch_config, + rng_seed, + &next_epoch_info, + epoch_summary.all_proposals.clone(), + epoch_summary.validator_kickout.clone(), + stored_next_next_epoch_info.validator_reward().clone(), + stored_next_next_epoch_info.minted_amount(), + epoch_protocol_version, + next_next_protocol_version, + has_same_shard_layout, + ) + .unwrap(); + + // Compute difference between chunk producer assignments. + let next_assignment = next_epoch_info.chunk_producers_settlement(); + let next_next_assignment = next_next_epoch_info.chunk_producers_settlement(); + + let mut next_validator_to_shard = HashMap::>::default(); + for (i, validator_ids) in next_assignment.iter().enumerate() { + for validator_id in validator_ids { + let validator = next_epoch_info.get_validator(*validator_id).take_account_id(); + next_validator_to_shard.entry(validator).or_default().push(i); + } + } + let mut state_syncs = 0; + let mut next_next_validator_to_shard = HashMap::::default(); + let mut stakes: HashMap = HashMap::default(); + let mut validator_num: HashMap = HashMap::default(); + for (i, validator_ids) in next_next_assignment.iter().enumerate() { + for validator_id in validator_ids { + let validator = next_next_epoch_info.get_validator(*validator_id); + let account_id = validator.account_id().clone(); + *stakes.entry(i).or_insert(0) += validator.stake(); + *validator_num.entry(i).or_insert(0) += 1; + if !next_validator_to_shard + .get(&account_id) + .is_some_and(|shards| shards.contains(&i)) + { + state_syncs += 1; + } + next_next_validator_to_shard.insert(account_id, i); + } + } + + let min_stake = stakes.values().min().unwrap(); + let max_stake = stakes.values().max().unwrap(); + + // Process generated epoch info. + match mode { + EpochAnalysisMode::CheckConsistency => { + // Print stats on screen. + println!( + "{next_next_epoch_height: >6} | {original_next_next_protocol_version: >7} | {state_syncs: >11}", + ); + // Check that the generated epoch info is the same as the stored one. + assert_eq!( + stored_next_next_epoch_info.as_ref(), + &next_next_epoch_info, + "Unequal epoch info at height {epoch_height}" + ); + } + EpochAnalysisMode::Backtest => { + // Print csv-style stats on screen. + println!( + "{next_next_epoch_height},{original_next_next_protocol_version},{state_syncs},{},{},{},{},{}", + validator_num.values().min().unwrap(), + validator_num.values().max().unwrap() - validator_num.values().min().unwrap(), + min_stake, + max_stake - min_stake, + ((max_stake - min_stake) as f64) / (*max_stake as f64) + ); + // Use the generated epoch info for the next iteration. + next_epoch_info = next_next_epoch_info; + } + } + } +} + fn get_trie(store: Store, hash: CryptoHash, shard_id: u32, shard_version: u32) -> Trie { let shard_uid = ShardUId { version: shard_version, shard_id }; let trie_config: TrieConfig = Default::default(); let shard_cache = TrieCache::new(&trie_config, shard_uid, true); let trie_storage = TrieCachingStorage::new(store, shard_cache, shard_uid, true, None); - Trie::new(Rc::new(trie_storage), hash, None) + Trie::new(Arc::new(trie_storage), hash, None) } pub(crate) fn view_trie( @@ -951,91 +1146,6 @@ pub(crate) fn view_trie_leaves( Ok(()) } -enum LoadTrieMode { - /// Load latest state - Latest, - /// Load prev state at some height - Height(BlockHeight), - /// Load the prev state of the last final block from some height - LastFinalFromHeight(BlockHeight), -} - -fn load_trie( - store: Store, - home_dir: &Path, - near_config: &NearConfig, -) -> (Arc, Arc, Vec, BlockHeader) { - load_trie_stop_at_height(store, home_dir, near_config, LoadTrieMode::Latest) -} - -fn load_trie_stop_at_height( - store: Store, - home_dir: &Path, - near_config: &NearConfig, - mode: LoadTrieMode, -) -> (Arc, Arc, Vec, BlockHeader) { - let chain_store = ChainStore::new( - store.clone(), - near_config.genesis.config.genesis_height, - near_config.client_config.save_trie_changes, - ); - - let epoch_manager = EpochManager::new_arc_handle(store.clone(), &near_config.genesis.config); - let runtime = - NightshadeRuntime::from_config(home_dir, store, near_config, epoch_manager.clone()) - .expect("could not create the transaction runtime"); - let head = chain_store.head().unwrap(); - let last_block = match mode { - LoadTrieMode::LastFinalFromHeight(height) => { - // find the first final block whose height is at least `height`. - let mut cur_height = height + 1; - loop { - if cur_height >= head.height { - panic!("No final block with height >= {} exists", height); - } - let cur_block_hash = match chain_store.get_block_hash_by_height(cur_height) { - Ok(hash) => hash, - Err(_) => { - cur_height += 1; - continue; - } - }; - let last_final_block_hash = - *chain_store.get_block_header(&cur_block_hash).unwrap().last_final_block(); - let last_final_block = chain_store.get_block(&last_final_block_hash).unwrap(); - if last_final_block.header().height() >= height { - break last_final_block; - } else { - cur_height += 1; - continue; - } - } - } - LoadTrieMode::Height(height) => { - let block_hash = chain_store.get_block_hash_by_height(height).unwrap(); - chain_store.get_block(&block_hash).unwrap() - } - LoadTrieMode::Latest => chain_store.get_block(&head.last_block_hash).unwrap(), - }; - let shard_layout = epoch_manager.get_shard_layout(&last_block.header().epoch_id()).unwrap(); - let state_roots = last_block - .chunks() - .iter() - .map(|chunk| { - // ChunkExtra contains StateRoot after applying actions in the block. - let chunk_extra = chain_store - .get_chunk_extra( - &head.last_block_hash, - &ShardUId::from_shard_id_and_layout(chunk.shard_id(), &shard_layout), - ) - .unwrap(); - *chunk_extra.state_root() - }) - .collect(); - - (epoch_manager, runtime, state_roots, last_block.header().clone()) -} - fn format_hash(h: CryptoHash, show_full_hashes: bool) -> String { let mut hash = h.to_string(); if !show_full_hashes { @@ -1066,7 +1176,7 @@ pub(crate) fn contract_accounts( let storage = TrieDBStorage::new(store.clone(), shard_uid); // We don't need flat state to traverse all accounts. let flat_storage_chunk_view = None; - Trie::new(Rc::new(storage), state_root, flat_storage_chunk_view) + Trie::new(Arc::new(storage), state_root, flat_storage_chunk_view) }); filter.write_header(&mut std::io::stdout().lock())?; @@ -1350,7 +1460,7 @@ impl std::fmt::Debug for StateStatsAccount { #[cfg(test)] mod tests { use near_chain::types::RuntimeAdapter; - use near_chain_configs::Genesis; + use near_chain_configs::{Genesis, MutableConfigValue}; use near_client::test_utils::TestEnv; use near_crypto::{InMemorySigner, KeyFile, KeyType}; use near_epoch_manager::EpochManager; @@ -1416,8 +1526,13 @@ mod tests { // Check that `send_money()` actually changed state. assert_ne!(chunk_extras[0].state_root(), chunk_extras[1].state_root()); - let near_config = - NearConfig::new(Config::default(), genesis, KeyFile::from(&signer), None).unwrap(); + let near_config = NearConfig::new( + Config::default(), + genesis, + KeyFile::from(&signer), + MutableConfigValue::new(None, "validator_signer"), + ) + .unwrap(); let (_epoch_manager, _runtime, state_roots, block_header) = crate::commands::load_trie(store, home_dir, &near_config); assert_eq!(&state_roots[0], chunk_extras[1].state_root()); diff --git a/tools/state-viewer/src/congestion_control.rs b/tools/state-viewer/src/congestion_control.rs new file mode 100644 index 00000000000..2f6f7a6d5d8 --- /dev/null +++ b/tools/state-viewer/src/congestion_control.rs @@ -0,0 +1,225 @@ +use rand::Rng; +use std::path::Path; + +use near_chain::types::RuntimeAdapter; +use near_chain::{ChainStore, ChainStoreAccess}; +use near_epoch_manager::EpochManagerAdapter; +use near_primitives::hash::CryptoHash; +use near_primitives::receipt::{DataReceipt, Receipt, ReceiptEnum, ReceiptV1}; +use near_primitives::types::{ShardId, StateChangeCause, StateRoot}; +use near_store::trie::receipts_column_helper::{DelayedReceiptQueue, TrieQueue}; +use near_store::{ShardTries, ShardUId, Store, TrieUpdate}; +use nearcore::NearConfig; +use node_runtime::bootstrap_congestion_info; + +use crate::util::load_trie; + +/// A set of commands for inspecting and debugging the congestion control +/// feature. The typical scenarios are: +/// 1) Run the print command and the bootstrap command and compare the results. +/// 2) Run the prepare-benchmark command and the bootstrap command to see how +/// long it takes to bootstrap the congestion info. Please note that this +/// will corrupt the database and it should not be used on production nodes. +#[derive(clap::Subcommand)] +pub enum CongestionControlCmd { + /// Print the congestion information. + Print(PrintCmd), + /// Run the congestion info bootstrapping. + Bootstrap(BootstrapCmd), + /// Load the trie with receipts and print new state roots that can be used + /// for benchmarking the bootstrapping logic. Please note that running this + /// command will corrupt the database. + PrepareBenchmark(PrepareBenchmarkCmd), +} + +impl CongestionControlCmd { + pub(crate) fn run(&self, home_dir: &Path, near_config: NearConfig, store: Store) { + match self { + CongestionControlCmd::Print(cmd) => cmd.run(&near_config, store), + CongestionControlCmd::Bootstrap(cmd) => cmd.run(home_dir, &near_config, store), + CongestionControlCmd::PrepareBenchmark(cmd) => cmd.run(home_dir, &near_config, store), + } + } +} + +#[derive(clap::Parser)] +pub struct PrintCmd {} + +impl PrintCmd { + pub(crate) fn run(&self, near_config: &NearConfig, store: Store) { + let chain_store = ChainStore::new( + store, + near_config.genesis.config.genesis_height, + near_config.client_config.save_trie_changes, + ); + let head = chain_store.head().unwrap(); + let block = chain_store.get_block(&head.last_block_hash).unwrap(); + + for chunk_header in block.chunks().iter() { + let congestion_info = chunk_header.congestion_info(); + println!( + "{:?} - {:?} - {:?}", + chunk_header.shard_id(), + chunk_header.prev_state_root(), + congestion_info + ); + } + } +} + +#[derive(clap::Parser)] +pub struct BootstrapCmd { + #[arg(long)] + shard_id: Option, + #[arg(long)] + state_root: Option, +} + +impl BootstrapCmd { + pub(crate) fn run(&self, home_dir: &Path, near_config: &NearConfig, store: Store) { + let (epoch_manager, runtime, state_roots, block_header) = + load_trie(store, home_dir, near_config); + + let shard_id_state_root_list = match (self.shard_id, self.state_root) { + (None, None) => state_roots.into_iter().enumerate().collect(), + (Some(shard_id), Some(state_root)) => vec![(shard_id as usize, state_root)], + _ => { + panic!("Both shard_id and state_root must be provided"); + } + }; + + let &prev_hash = block_header.prev_hash(); + for (shard_id, state_root) in shard_id_state_root_list { + let shard_id = shard_id as ShardId; + Self::run_impl( + epoch_manager.as_ref(), + runtime.as_ref(), + prev_hash, + shard_id, + state_root, + ); + } + } + + fn run_impl( + epoch_manager: &dyn EpochManagerAdapter, + runtime: &dyn RuntimeAdapter, + prev_hash: CryptoHash, + shard_id: ShardId, + state_root: StateRoot, + ) { + let shard_id = shard_id as u64; + let epoch_id = epoch_manager.get_epoch_id_from_prev_block(&prev_hash).unwrap(); + let protocol_config = runtime.get_protocol_config(&epoch_id).unwrap(); + let runtime_config = protocol_config.runtime_config; + let trie = runtime.get_trie_for_shard(shard_id, &prev_hash, state_root, true).unwrap(); + + let start_time = std::time::Instant::now(); + let congestion_info = bootstrap_congestion_info(&trie, &runtime_config, shard_id).unwrap(); + let duration = start_time.elapsed(); + + println!("{:?} - {:?} - {:?}", shard_id, congestion_info, duration); + } +} + +#[derive(clap::Parser)] +pub struct PrepareBenchmarkCmd { + // How many receipts should be added to the delayed receipts queue. + // By default insert 10k receipts. + #[arg(long, default_value = "10000")] + receipt_count: u32, + // The size in bytes of each receipt. + // By default each receipts is 10kB. + #[arg(long, default_value = "10000")] + receipt_size: u32, + // If set to true the command will not ask for confirmation. + #[arg(long, default_value = "false")] + yes: bool, +} + +impl PrepareBenchmarkCmd { + fn run(&self, home_dir: &Path, near_config: &NearConfig, store: Store) { + if !self.should_run() { + println!("aborted"); + return; + } + + let (epoch_manager, runtime, state_roots, block_header) = + load_trie(store, home_dir, near_config); + + let prev_hash = block_header.prev_hash(); + let epoch_id = epoch_manager.get_epoch_id_from_prev_block(prev_hash).unwrap(); + + for (shard_id, &state_root) in state_roots.iter().enumerate() { + println!("old - {:?} - {:?}", shard_id, state_root); + let shard_id = shard_id as u64; + let shard_uid = epoch_manager.shard_id_to_uid(shard_id, &epoch_id).unwrap(); + + let tries = runtime.get_tries(); + + let state_root = self.add_receipts(tries, shard_uid, state_root); + println!("new - {:?} - {:?}", shard_id, state_root); + } + } + + fn should_run(&self) -> bool { + println!("WARNING: This command will corrupt the database."); + + if self.yes { + return true; + } + println!("WARNING: To bypass this confirmation use the --yes flag."); + println!("WARNING: Do you want to continue? [y/N]"); + + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let input = input.trim().to_lowercase(); + input == "y" || input == "yes" + } + + fn add_receipts( + &self, + tries: ShardTries, + shard_uid: ShardUId, + state_root: StateRoot, + ) -> StateRoot { + let trie = tries.get_trie_for_shard(shard_uid, state_root); + let mut trie_update = TrieUpdate::new(trie); + let mut queue = DelayedReceiptQueue::load(&trie_update).unwrap(); + + for _ in 0..self.receipt_count { + let receipt = self.create_receipt(); + queue.push(&mut trie_update, &receipt).unwrap(); + } + + trie_update.commit(StateChangeCause::UpdatedDelayedReceipts); + let (_, trie_changes, _) = trie_update.finalize().unwrap(); + + let mut store_update = tries.store_update(); + let new_state_root = tries.apply_all(&trie_changes, shard_uid, &mut store_update); + store_update.commit().unwrap(); + + new_state_root + } + + fn create_receipt(&self) -> Receipt { + let predecessor_id = "predecessor".parse().unwrap(); + let receiver_id = "receiver".parse().unwrap(); + + let mut rng = rand::thread_rng(); + + let mut data = vec![0; self.receipt_size as usize]; + rng.fill(&mut data[..]); + + // Use the hash of the data as the id for things. + let id = CryptoHash::hash_bytes(&data); + let data_id = id; + let receipt_id = id; + + let receipt = DataReceipt { data_id, data: Some(data) }; + let receipt = ReceiptEnum::Data(receipt); + let receipt = ReceiptV1 { predecessor_id, receiver_id, receipt_id, receipt, priority: 0 }; + + Receipt::V1(receipt) + } +} diff --git a/tools/state-viewer/src/contract_accounts.rs b/tools/state-viewer/src/contract_accounts.rs index dabc88dcf0a..11d109fc570 100644 --- a/tools/state-viewer/src/contract_accounts.rs +++ b/tools/state-viewer/src/contract_accounts.rs @@ -492,7 +492,7 @@ impl ContractAccountFilter { mod tests { use super::{ContractAccount, ContractAccountFilter, Summary}; use borsh::BorshSerialize; - use near_crypto::{InMemorySigner, Signer}; + use near_crypto::InMemorySigner; use near_primitives::hash::CryptoHash; use near_primitives::receipt::{ActionReceipt, Receipt, ReceiptEnum, ReceiptV0}; use near_primitives::transaction::{ diff --git a/tools/state-viewer/src/epoch_info.rs b/tools/state-viewer/src/epoch_info.rs index 35089c0a5a3..9638abc2895 100644 --- a/tools/state-viewer/src/epoch_info.rs +++ b/tools/state-viewer/src/epoch_info.rs @@ -46,7 +46,7 @@ pub(crate) fn print_epoch_info( epoch_manager.get_epoch_info(head_block_info.epoch_id()).unwrap().epoch_height(); let mut epoch_infos: Vec<(EpochId, Arc)> = epoch_ids .iter() - .map(|epoch_id| (epoch_id.clone(), epoch_manager.get_epoch_info(epoch_id).unwrap())) + .map(|epoch_id| (*epoch_id, epoch_manager.get_epoch_info(epoch_id).unwrap())) .collect(); // Sorted output is much easier to follow. epoch_infos.sort_by_key(|(_, epoch_info)| epoch_info.epoch_height()); diff --git a/tools/state-viewer/src/latest_witnesses.rs b/tools/state-viewer/src/latest_witnesses.rs index 05b75e859a7..634a99ac900 100644 --- a/tools/state-viewer/src/latest_witnesses.rs +++ b/tools/state-viewer/src/latest_witnesses.rs @@ -1,59 +1,149 @@ +use std::path::{Path, PathBuf}; use std::rc::Rc; -use clap::Parser; -use near_chain::ChainStore; +use near_chain::runtime::NightshadeRuntime; +use near_chain::stateless_validation::processing_tracker::ProcessingDoneTracker; +use near_chain::{Chain, ChainGenesis, ChainStore, DoomslugThresholdMode}; +use near_epoch_manager::shard_tracker::{ShardTracker, TrackedConfig}; +use near_epoch_manager::EpochManager; +use near_primitives::stateless_validation::ChunkStateWitness; use near_primitives::types::EpochId; use near_store::Store; +use near_time::Clock; use nearcore::NearConfig; +use nearcore::NightshadeRuntimeExt; -#[derive(Parser)] -pub struct LatestWitnessesCmd { - /// Block height +#[derive(clap::Subcommand)] +pub enum StateWitnessCmd { + /// Dumps some of the latest stored state witnesses. + Dump(DumpWitnessesCmd), + /// Validates given state witness. + Validate(ValidateWitnessCmd), +} + +impl StateWitnessCmd { + pub(crate) fn run(&self, home_dir: &Path, near_config: NearConfig, store: Store) { + match self { + StateWitnessCmd::Dump(cmd) => cmd.run(near_config, store), + StateWitnessCmd::Validate(cmd) => cmd.run(home_dir, near_config, store), + } + } +} + +#[derive(clap::Parser)] +pub struct DumpWitnessesCmd { + /// Select received witnesses only with given block height. #[arg(long)] height: Option, - - /// Shard id + /// Select only witnesses for given shard id. #[arg(long)] shard_id: Option, - - /// Epoch Id + /// Select only witnesses for given epoch. #[arg(long)] epoch_id: Option, + #[clap(subcommand)] + /// Mode of dumping state witnesses. + mode: DumpWitnessesMode, +} - /// Pretty-print using the "{:#?}" formatting. - #[arg(long)] - pretty: bool, - - /// Print the raw &[u8], can be pasted into rust code - #[arg(long)] - binary: bool, +#[derive(clap::Subcommand)] +enum DumpWitnessesMode { + /// Pretty-print on screen using the "{:#?}" formatting. + Pretty, + /// Saves the raw &[u8] of each witness to the given directory. + Binary { output_dir: PathBuf }, } -impl LatestWitnessesCmd { +impl DumpWitnessesCmd { pub(crate) fn run(&self, near_config: NearConfig, store: Store) { let chain_store = Rc::new(ChainStore::new(store, near_config.genesis.config.genesis_height, false)); - let witnesses = chain_store - .get_latest_witnesses(self.height, self.shard_id, self.epoch_id.clone()) - .unwrap(); + let witnesses = + chain_store.get_latest_witnesses(self.height, self.shard_id, self.epoch_id).unwrap(); println!("Found {} witnesses:", witnesses.len()); + if let DumpWitnessesMode::Binary { ref output_dir } = self.mode { + if !output_dir.exists() { + std::fs::create_dir_all(output_dir).unwrap(); + } + } + for (i, witness) in witnesses.iter().enumerate() { println!( - "#{} (height: {}, shard_id: {}, epoch_id: {:?}):", + "#{} (height: {}, shard_id: {}, epoch_id: {:?})", i, witness.chunk_header.height_created(), witness.chunk_header.shard_id(), witness.epoch_id ); - if self.pretty { - println!("{:#?}", witness); - } else if self.binary { - println!("{:?}", borsh::to_vec(witness).unwrap()); - } else { - println!("{:?}", witness); + match self.mode { + DumpWitnessesMode::Pretty => { + println!("{:#?}", witness); + println!(""); + } + DumpWitnessesMode::Binary { ref output_dir } => { + let file_name = format!( + "witness_{}_{}_{}_{}.bin", + witness.chunk_header.height_created(), + witness.chunk_header.shard_id(), + witness.epoch_id.0, + i + ); + let file_path = output_dir.join(file_name); + std::fs::write(&file_path, borsh::to_vec(witness).unwrap()).unwrap(); + println!("Saved to {:?}", file_path); + } } - println!(""); } } } + +#[derive(clap::Parser)] +pub struct ValidateWitnessCmd { + /// File with state witness saved as raw &[u8]. + #[arg(long)] + input_file: PathBuf, +} + +impl ValidateWitnessCmd { + pub(crate) fn run(&self, home_dir: &Path, near_config: NearConfig, store: Store) { + let encoded_witness: Vec = + std::fs::read(&self.input_file).expect("Failed to read file"); + let witness: ChunkStateWitness = borsh::BorshDeserialize::try_from_slice(&encoded_witness) + .expect("Failed to deserialize witness"); + let chain_genesis = ChainGenesis::new(&near_config.genesis.config); + let epoch_manager = + EpochManager::new_arc_handle(store.clone(), &near_config.genesis.config); + let runtime_adapter = + NightshadeRuntime::from_config(home_dir, store, &near_config, epoch_manager.clone()) + .expect("could not create the transaction runtime"); + let shard_tracker = ShardTracker::new( + TrackedConfig::from_config(&near_config.client_config), + epoch_manager.clone(), + ); + // TODO(stateless_validation): consider using `ChainStore` instead of + // `Chain`. + let chain = Chain::new_for_view_client( + Clock::real(), + epoch_manager.clone(), + shard_tracker, + runtime_adapter.clone(), + &chain_genesis, + DoomslugThresholdMode::TwoThirds, + false, + ) + .unwrap(); + let processing_done_tracker = ProcessingDoneTracker::new(); + let waiter = processing_done_tracker.make_waiter(); + chain + .shadow_validate_state_witness( + witness, + epoch_manager.as_ref(), + runtime_adapter.as_ref(), + Some(processing_done_tracker), + ) + .unwrap(); + waiter.wait(); + println!("Validation finished. Use `RUST_LOG=debug` to see validation result"); + } +} diff --git a/tools/state-viewer/src/lib.rs b/tools/state-viewer/src/lib.rs index aa782764701..28e3ffec384 100644 --- a/tools/state-viewer/src/lib.rs +++ b/tools/state-viewer/src/lib.rs @@ -4,6 +4,7 @@ mod apply_chain_range; mod apply_chunk; pub mod cli; mod commands; +mod congestion_control; mod contract_accounts; mod epoch_info; mod latest_witnesses; @@ -14,5 +15,6 @@ mod state_dump; mod state_parts; mod trie_iteration_benchmark; mod tx_dump; +mod util; pub use cli::StateViewerSubCommand; diff --git a/tools/state-viewer/src/state_dump.rs b/tools/state-viewer/src/state_dump.rs index e79a6bcc23b..7612702f4b4 100644 --- a/tools/state-viewer/src/state_dump.rs +++ b/tools/state-viewer/src/state_dump.rs @@ -297,7 +297,7 @@ mod test { use near_chain::{ChainStoreAccess, Provenance}; use near_chain_configs::genesis_validate::validate_genesis; use near_chain_configs::test_utils::TESTING_INIT_STAKE; - use near_chain_configs::{Genesis, GenesisChangeConfig}; + use near_chain_configs::{Genesis, GenesisChangeConfig, MutableConfigValue}; use near_client::test_utils::TestEnv; use near_client::ProcessTxResponse; use near_crypto::{InMemorySigner, KeyFile, KeyType, PublicKey, SecretKey}; @@ -356,10 +356,13 @@ mod test { public_key: PublicKey::empty(KeyType::ED25519), secret_key: SecretKey::from_random(KeyType::ED25519), }, - Some(Arc::new(InMemoryValidatorSigner::from_random( - "test".parse().unwrap(), - KeyType::ED25519, - ))), + MutableConfigValue::new( + Some(Arc::new( + InMemoryValidatorSigner::from_random("test".parse().unwrap(), KeyType::ED25519) + .into(), + )), + "validator_signer", + ), ) .unwrap(); @@ -394,13 +397,14 @@ mod test { let epoch_length = 4; let (store, genesis, mut env, near_config) = setup(epoch_length, PROTOCOL_VERSION, false); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -448,7 +452,7 @@ mod test { let genesis_hash = *env.clients[0].chain.genesis().hash(); let signer0 = - InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0").into(); let tx00 = SignedTransaction::from_actions( 1, "test0".parse().unwrap(), @@ -465,20 +469,20 @@ mod test { "test0".parse().unwrap(), &signer0, TESTING_INIT_STAKE, - signer0.public_key.clone(), + signer0.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx00, false, false), ProcessTxResponse::ValidTx); assert_eq!(env.clients[0].process_tx(tx01, false, false), ProcessTxResponse::ValidTx); let signer1 = - InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx1 = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer1, TESTING_INIT_STAKE, - signer1.public_key.clone(), + signer1.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx1, false, false), ProcessTxResponse::ValidTx); @@ -535,13 +539,14 @@ mod test { let epoch_length = 4; let (store, genesis, mut env, near_config) = setup(epoch_length, PROTOCOL_VERSION, false); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -586,13 +591,14 @@ mod test { let epoch_length = 4; let (store, genesis, mut env, near_config) = setup(epoch_length, PROTOCOL_VERSION, false); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -720,7 +726,7 @@ mod test { 1, "test1".parse().unwrap(), "test0".parse().unwrap(), - &signer, + &signer.into(), 1, genesis_hash, ); @@ -744,10 +750,13 @@ mod test { public_key: PublicKey::empty(KeyType::ED25519), secret_key: SecretKey::from_random(KeyType::ED25519), }, - Some(Arc::new(InMemoryValidatorSigner::from_random( - "test".parse().unwrap(), - KeyType::ED25519, - ))), + MutableConfigValue::new( + Some(Arc::new( + InMemoryValidatorSigner::from_random("test".parse().unwrap(), KeyType::ED25519) + .into(), + )), + "validator_signer", + ), ) .unwrap(); @@ -791,13 +800,14 @@ mod test { .runtimes(vec![nightshade_runtime]) .build(); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); @@ -812,10 +822,13 @@ mod test { public_key: PublicKey::empty(KeyType::ED25519), secret_key: SecretKey::from_random(KeyType::ED25519), }, - Some(Arc::new(InMemoryValidatorSigner::from_random( - "test".parse().unwrap(), - KeyType::ED25519, - ))), + MutableConfigValue::new( + Some(Arc::new( + InMemoryValidatorSigner::from_random("test".parse().unwrap(), KeyType::ED25519) + .into(), + )), + "validator_signer", + ), ) .unwrap(); let head = env.clients[0].chain.head().unwrap(); @@ -858,13 +871,14 @@ mod test { let (store, genesis, mut env, near_config) = setup(epoch_length, PROTOCOL_VERSION, false); let genesis_hash = *env.clients[0].chain.genesis().hash(); - let signer = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let signer = + InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1").into(); let tx = SignedTransaction::stake( 1, "test1".parse().unwrap(), &signer, TESTING_INIT_STAKE, - signer.public_key.clone(), + signer.public_key(), genesis_hash, ); assert_eq!(env.clients[0].process_tx(tx, false, false), ProcessTxResponse::ValidTx); diff --git a/tools/state-viewer/src/state_parts.rs b/tools/state-viewer/src/state_parts.rs index 9d530a980ee..742d7319219 100644 --- a/tools/state-viewer/src/state_parts.rs +++ b/tools/state-viewer/src/state_parts.rs @@ -1,6 +1,5 @@ use crate::epoch_info::iterate_and_filter; use borsh::{BorshDeserialize, BorshSerialize}; -use near_async::time::Clock; use near_chain::{Chain, ChainGenesis, ChainStoreAccess, DoomslugThresholdMode}; use near_client::sync::external::{ create_bucket_readonly, create_bucket_readwrite, external_storage_location, @@ -18,6 +17,7 @@ use near_primitives::types::{EpochId, StateRoot}; use near_primitives_core::hash::CryptoHash; use near_primitives_core::types::{BlockHeight, EpochHeight, ShardId}; use near_store::{PartialStorage, Store, Trie}; +use near_time::Clock; use nearcore::{NearConfig, NightshadeRuntime, NightshadeRuntimeExt}; use std::ops::Range; use std::path::{Path, PathBuf}; @@ -270,7 +270,7 @@ impl EpochSelection { epoch_info.epoch_height() == *epoch_height }); assert_eq!(epoch_ids.len(), 1, "{:#?}", epoch_ids); - epoch_ids[0].clone() + epoch_ids[0] } EpochSelection::BlockHash { block_hash } => { let block_hash = CryptoHash::from_str(block_hash).unwrap(); diff --git a/tools/state-viewer/src/trie_iteration_benchmark.rs b/tools/state-viewer/src/trie_iteration_benchmark.rs index fec06b06d40..ea6c211fdb3 100644 --- a/tools/state-viewer/src/trie_iteration_benchmark.rs +++ b/tools/state-viewer/src/trie_iteration_benchmark.rs @@ -1,5 +1,3 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Instant}; - use near_chain::{ChainStore, ChainStoreAccess}; use near_epoch_manager::EpochManager; use near_primitives::shard_layout::ShardLayout; @@ -12,6 +10,11 @@ use near_primitives::trie_key::trie_key_parsers::{ use near_primitives_core::types::ShardId; use near_store::{ShardUId, Store, Trie, TrieDBStorage}; use nearcore::NearConfig; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Instant; #[derive(Clone)] pub enum TrieIterationType { @@ -147,7 +150,7 @@ impl TrieIterationBenchmarkCmd { let state_root = chunk_header.prev_state_root(); let storage = TrieDBStorage::new(store.clone(), shard_uid); let flat_storage_chunk_view = None; - Trie::new(Rc::new(storage), state_root, flat_storage_chunk_view) + Trie::new(Arc::new(storage), state_root, flat_storage_chunk_view) } fn iter_trie(&self, trie: &Trie) { diff --git a/tools/state-viewer/src/util.rs b/tools/state-viewer/src/util.rs new file mode 100644 index 00000000000..f2ddf2f045e --- /dev/null +++ b/tools/state-viewer/src/util.rs @@ -0,0 +1,90 @@ +use std::{path::Path, sync::Arc}; + +use near_chain::{types::Tip, Block, BlockHeader, ChainStore, ChainStoreAccess}; +use near_epoch_manager::{EpochManager, EpochManagerAdapter, EpochManagerHandle}; +use near_primitives::types::{BlockHeight, StateRoot}; +use near_store::{ShardUId, Store}; +use nearcore::{NearConfig, NightshadeRuntime, NightshadeRuntimeExt}; + +pub enum LoadTrieMode { + /// Load latest state + Latest, + /// Load prev state at some height + Height(BlockHeight), + /// Load the prev state of the last final block from some height + LastFinalFromHeight(BlockHeight), +} + +pub fn load_trie( + store: Store, + home_dir: &Path, + near_config: &NearConfig, +) -> (Arc, Arc, Vec, BlockHeader) { + load_trie_stop_at_height(store, home_dir, near_config, LoadTrieMode::Latest) +} + +pub fn load_trie_stop_at_height( + store: Store, + home_dir: &Path, + near_config: &NearConfig, + mode: LoadTrieMode, +) -> (Arc, Arc, Vec, BlockHeader) { + let chain_store = ChainStore::new( + store.clone(), + near_config.genesis.config.genesis_height, + near_config.client_config.save_trie_changes, + ); + + let epoch_manager = EpochManager::new_arc_handle(store.clone(), &near_config.genesis.config); + let runtime = + NightshadeRuntime::from_config(home_dir, store, near_config, epoch_manager.clone()); + let runtime = runtime.expect("could not create the transaction runtime"); + + let head = chain_store.head().unwrap(); + let block = match mode { + LoadTrieMode::LastFinalFromHeight(height) => { + get_last_final_from_height(height, &head, &chain_store) + } + LoadTrieMode::Height(height) => { + let block_hash = chain_store.get_block_hash_by_height(height).unwrap(); + chain_store.get_block(&block_hash).unwrap() + } + LoadTrieMode::Latest => chain_store.get_block(&head.last_block_hash).unwrap(), + }; + let shard_layout = epoch_manager.get_shard_layout(&block.header().epoch_id()).unwrap(); + let mut state_roots = vec![]; + for chunk in block.chunks().iter() { + let shard_uid = ShardUId::from_shard_id_and_layout(chunk.shard_id(), &shard_layout); + let chunk_extra = chain_store.get_chunk_extra(&head.last_block_hash, &shard_uid).unwrap(); + let state_root = *chunk_extra.state_root(); + state_roots.push(state_root); + } + + (epoch_manager, runtime, state_roots, block.header().clone()) +} + +/// find the first final block whose height is at least `height`. +fn get_last_final_from_height(height: u64, head: &Tip, chain_store: &ChainStore) -> Block { + let mut cur_height = height + 1; + loop { + if cur_height >= head.height { + panic!("No final block with height >= {} exists", height); + } + let cur_block_hash = match chain_store.get_block_hash_by_height(cur_height) { + Ok(hash) => hash, + Err(_) => { + cur_height += 1; + continue; + } + }; + let last_final_block_hash = + *chain_store.get_block_header(&cur_block_hash).unwrap().last_final_block(); + let last_final_block = chain_store.get_block(&last_final_block_hash).unwrap(); + if last_final_block.header().height() >= height { + break last_final_block; + } else { + cur_height += 1; + continue; + } + } +} diff --git a/tracing/Cargo.toml b/tracing/Cargo.toml index 275a4dbf84b..85a99cf66ab 100644 --- a/tracing/Cargo.toml +++ b/tracing/Cargo.toml @@ -3,7 +3,7 @@ name = "near-tracing" version = "0.0.0" authors = ["Near Inc "] edition = "2021" -rust-version = "1.78.0" +rust-version = "1.79.0" repository = "https://github.com/near/nearcore" license = "MIT OR Apache-2.0" diff --git a/tracing/Dockerfile b/tracing/Dockerfile index fbd445c8fc1..7391f235406 100644 --- a/tracing/Dockerfile +++ b/tracing/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.78-buster as builder +FROM rust:1.79-buster as builder ADD Cargo.toml /build/Cargo.toml ADD Cargo.lock /build/Cargo.lock diff --git a/utils/mainnet-res/res/mainnet_genesis.json b/utils/mainnet-res/res/mainnet_genesis.json index ded3caf5812..bc122e90be5 100644 --- a/utils/mainnet-res/res/mainnet_genesis.json +++ b/utils/mainnet-res/res/mainnet_genesis.json @@ -16,6 +16,8 @@ "min_gas_price": "1000000000", "block_producer_kickout_threshold": 90, "chunk_producer_kickout_threshold": 90, + "chunk_validator_only_kickout_threshold": 80, + "target_validator_mandates_per_shard": 68, "online_min_threshold": [ 90, 100 diff --git a/utils/near-cache/benches/cache.rs b/utils/near-cache/benches/cache.rs index 0708f8de622..755f6a6c2c7 100644 --- a/utils/near-cache/benches/cache.rs +++ b/utils/near-cache/benches/cache.rs @@ -1,13 +1,15 @@ #[macro_use] extern crate bencher; +use std::num::NonZeroUsize; + use bencher::Bencher; use lru::LruCache; use near_cache::SyncLruCache; fn bench_lru(bench: &mut Bencher) { bench.iter(|| { - let mut cache = LruCache::new(10000); + let mut cache = LruCache::new(NonZeroUsize::new(10000).unwrap()); for _x in 0..1000000 { let a = rand::random::(); let b = rand::random::(); diff --git a/utils/near-cache/src/cell.rs b/utils/near-cache/src/cell.rs index 783a0b6fc5f..1061b940172 100644 --- a/utils/near-cache/src/cell.rs +++ b/utils/near-cache/src/cell.rs @@ -3,6 +3,7 @@ use std::borrow::Borrow; use std::cell::RefCell; use std::convert::Infallible; use std::hash::Hash; +use std::num::NonZeroUsize; /// A wrapper around `LruCache` to provide shared `&` access to content. pub struct CellLruCache { @@ -16,7 +17,7 @@ where { /// Creats a new `LRU` cache that holds at most `cap` items. pub fn new(cap: usize) -> Self { - Self { inner: RefCell::new(LruCache::::new(cap)) } + Self { inner: RefCell::new(LruCache::::new(NonZeroUsize::new(cap).unwrap())) } } /// Returns the number of key-value pairs that are currently in the cache. @@ -30,7 +31,7 @@ where } /// Return the value of they key in the cache otherwise computes the value and inserts it into - /// the cache. If the key is already in the cache, they gets gets moved to the head of + /// the cache. If the key is already in the cache, they get moved to the head of /// the LRU list. pub fn get_or_put(&self, key: K, f: F) -> V where @@ -70,7 +71,7 @@ where pub fn pop(&self, key: &Q) -> Option where - lru::KeyRef: Borrow, + K: Borrow, Q: Hash + Eq + ?Sized, { self.inner.borrow_mut().pop(key) @@ -80,7 +81,7 @@ where /// Moves the key to the head of the LRU list if it exists. pub fn get(&self, key: &Q) -> Option where - lru::KeyRef: Borrow, + K: Borrow, Q: Hash + Eq + ?Sized, { self.inner.borrow_mut().get(key).cloned() diff --git a/utils/near-cache/src/sync.rs b/utils/near-cache/src/sync.rs index 44b5cea1aed..4b971c9a655 100644 --- a/utils/near-cache/src/sync.rs +++ b/utils/near-cache/src/sync.rs @@ -1,6 +1,7 @@ use lru::LruCache; use std::convert::Infallible; use std::hash::Hash; +use std::num::NonZeroUsize; use std::sync::Mutex; /// A wrapper around `LruCache`. This struct is thread safe, doesn't return any references to any @@ -16,7 +17,7 @@ where { /// Creats a new `LRU` cache that holds at most `cap` items. pub fn new(cap: usize) -> Self { - Self { inner: Mutex::new(LruCache::::new(cap)) } + Self { inner: Mutex::new(LruCache::::new(NonZeroUsize::new(cap).unwrap())) } } /// Returns the number of key-value pairs that are currently in the cache. @@ -30,7 +31,7 @@ where } /// Return the value of they key in the cache otherwise computes the value and inserts it into - /// the cache. If the key is already in the cache, they gets gets moved to the head of + /// the cache. If the key is already in the cache, they get moved to the head of /// the LRU list. pub fn get_or_put(&self, key: K, f: F) -> V where diff --git a/utils/stdx/src/lib.rs b/utils/stdx/src/lib.rs index ecf027e1f13..91fda83817f 100644 --- a/utils/stdx/src/lib.rs +++ b/utils/stdx/src/lib.rs @@ -9,9 +9,11 @@ pub fn split_array( xs: &[u8; N], ) -> (&[u8; L], &[u8; R]) { - #[allow(clippy::let_unit_value)] - let () = AssertEqSum::::OK; - + const { + if N != L + R { + panic!() + } + }; let (left, right) = xs.split_at(L); (left.try_into().unwrap(), right.try_into().unwrap()) } @@ -20,8 +22,11 @@ pub fn split_array( pub fn split_array_mut( xs: &mut [u8; N], ) -> (&mut [u8; L], &mut [u8; R]) { - #[allow(clippy::let_unit_value)] - let () = AssertEqSum::::OK; + const { + if N != L + R { + panic!() + } + }; let (left, right) = xs.split_at_mut(L); (left.try_into().unwrap(), right.try_into().unwrap()) @@ -38,8 +43,11 @@ pub fn join_array( left: [u8; L], right: [u8; R], ) -> [u8; N] { - #[allow(clippy::let_unit_value)] - let () = AssertEqSum::::OK; + const { + if N != L + R { + panic!() + } + }; let mut res = [0; N]; let (l, r) = res.split_at_mut(L); @@ -56,8 +64,11 @@ fn test_join() { /// Splits a slice into a slice of N-element arrays. // TODO(mina86): Replace with [T]::as_chunks once that’s stabilised. pub fn as_chunks(slice: &[T]) -> (&[[T; N]], &[T]) { - #[allow(clippy::let_unit_value)] - let () = AssertNonZero::::OK; + const { + if N == 0 { + panic!() + } + }; let len = slice.len().checked_div(N).expect("static assert above ensures N ≠ 0"); let (head, tail) = slice @@ -104,15 +115,3 @@ fn test_as_chunks() { as_chunks_exact::<2, _>(&[0, 1, 2, 3, 4]) ); } - -/// Asserts, at compile time, that `S == A + B`. -struct AssertEqSum; -impl AssertEqSum { - const OK: () = assert!(S == A + B); -} - -/// Asserts, at compile time, that `N` is non-zero. -struct AssertNonZero; -impl AssertNonZero { - const OK: () = assert!(N != 0); -}